20190507のPHPに関する記事は11件です。

RedHatがサポートしてるphpのバージョンが分かりづらかったけど分かったのでメモ

  • 最新のphpサポートはここに書いてあるけど、RedHatは最新のphpをサポートしてるわけではない
  • RedHatの公式サイトにある通り、RHEL7はphp5.4系をサポートし続けるし、RHEL6はphp5.3をサポートし続ける
    • 本家phpがサポート終わっても脆弱性が出た際などにREHLはパッチを提供し続ける
      • 安定稼働のため
  • RHEL7でサポートされたphp7などを使いたい場合は、Red Hat Software Collections Productを利用すると良いが、デフォルトでバンドルされてるバージョンと比べるとサポート期間が短い
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

演算子.と+の優先順位が変わる

PHP7.3現在、演算子+-.の優先順位は同じです。
01.png

すなわち左から右に評価されます。

    echo 1 . 2 + 3 . 4;

    echo ((( 1 . 2 ) + 3 ) . 4 ) ; // これと同じ

マニュアルでもわざわざ例を挙げて解説しています。
02.png

さて2019年3月にChange the precedence of the concatenation operatorというRFCが提出されました
2019/05/07現在は投票中のステータスですが賛成多数で、PHP8で上記の動作は変わることになりそうです。
演算子の追加削除はよくあることですが、優先順位の変更というのは他言語含めてもなかなか見ることのないレアなイベントではないでしょうか。

Change the precedence of the concatenation operator

Introduction

+-、そして.は長年にわたる問題です。
それは左から右に解釈されます。

echo "sum: " . $a + $b;

// こう解釈される
echo ("sum: " . $a) + $b;

// こうではない
echo "sum :" . ($a + $b);

このRFCでは、この動作をより直感的に、問題が出にくくなるようにすることを目的としています。

Proposal

現在、+-.の各演算子は優先順位が同じです。
これらは、単純に左から右に評価されます。

これは直感に反します。
一般に、数字ではない文字列を足したり引いたりすることはほとんどありません。
PHPが整数を文字列にシームレスに変換できることを考えると、文字列連結が先に来ることが望ましいでしょう。

従って、このRFCでは.に、+-より低い優先順位を与えることを提案します。
具体的には、新しい優先順位は<<>>のすぐ下になります。

Backward Incompatible Changes

.の後に、括弧を使わず+-を使用している式全てが影響を受けます。
例として、"3" . "5" + 742ではなく"312"になります。

これは、予告や警告なしに出力が変更されるという点で微妙な動作変更ですが、コードを静的解析して、この問題が発生する箇所を全て見つけることは簡単にできます。
私の知るかぎり、この問題が発生することは稀であり、そして大抵は最初から間違っています。

NikitaがMLで言及したように、既存のOSSへの影響は事実上ありません。
見つかったものは全て単なるバグです。
つまり、全体的に影響は非常に小さいということになります。

Proposed PHP Version

PHP7.4でE_DEPRECATEDを発生させ、PHP8で動作を変更します。

投票

投票開始は2019/04/30、投票終了は2019/05/14です。
2019/05/07現在、PHP8で動作変更する提案は賛成23反対3で、ほぼ確実に受理されます。
PHP7.4でDeprecatedにする提案は賛成23反対4で、こちらもおそらく受理されます。

NikitaのML

またNikitaか。

Composerパッケージの上位2000件を調査したところ、影響を受けるコードは僅か5件しかありませんでした
しかも5件とも修正後の優先順位を想定したコードで、つまり現状ではバグっています。

$this->errors->add( 'unknown_upgrade_error_' . $errors_count + 1, $string );

例を一つあげると、上記コードはadd('unknown_upgrade_error_5', $string)のような文字列を与えたいのだと思われますが、実際はadd(1, $string)になります。

今回のRFCが通ると、想定していたであろう動作にエンバグすることになります。

感想

考えてみたら、むしろどうして今まで同じだったんだ、って感じですね。
優先順位が同じであるという仕様と、文字列と数値を自由に行き来できるという仕様が相まって、PHPで.+を同時に使った演算は、ぱっと見から予想できない結果になることがありました。
今回のRFCが通ると、そのあたりの動作がすっきりすることになります。

もっとも、そういったややこしい演算には普通は括弧を使っているから、実害は全くないはずですけどね。

ところでDeprecateにする提案に"The second (secondary) voting requires a 50%+1 majority."って書いてあるんだけど、50%の投票は廃棄されたんじゃなかったのか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

wp-adminでWP APIをJSから扱う

wp-adminからWP APIをJSからコールする方法を試したので覚書。

WP APIをコールする際の注意点

認証が必要なAPIは、X-WP-Nonceをヘッダーで送る必要がある。

wp-api.jsのimport

nonceの生成などをよしなにしてくれるのでimportしておく。
ついでにPOSTリクエストにjqueryを使うのでこちらもいれておく。

add_action('wp_enqueue_scripts', 'add_scripts' );
add_action('admin_enqueue_scripts', 'add_scripts' );

function add_scripts() {
  if ( is_user_logged_in() ) {
    wp_enqueue_script( 'jquery' );
    wp_enqueue_script( 'wp-api' );
    wp_enqueue_script( "custom_js", plugins_url( 'libs/scripts.js', __FILE__ ), array( 'jquery', 'wp-api' ) );
  }
}

libs/scripts.js

リクエスト処理部分。
wp-api.jsを入れていると、window.wpApiSettingsという値がwp-adminでは定義されている様子。
これにWP APIのパスとnonceが入っているので使う。

const settings = window.wpApiSettings
const requestObj = {
    url: settings.root + 'wp/v2/posts',
    method: 'POST',
    beforeSend: function ( xhr ) {
      xhr.setRequestHeader( 'X-WP-Nonce', settings.nonce );
    },
    body: { /* post params */}
}
jQuery.ajax( requestObj )
  .then( result => console.log( result ) )

wp-api.jsをちゃんと使えばjQueryもいらない気はする。
ただ、Backbone.js触ったことがないのでそれはまた時間ができたときにでも。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PHP]IN句のプレースホルダーを配列から作成する

動的なプレースホルダの生成方法ってどう書くんだっけなと毎回ググってる気がする。。

やりたいこと

あるデータ配列を元にプレースホルダーSQL文を作成したい

結果

$sql = 'WHERE column IN (' . implode(',', array_fill(0, count($data), '?')) . ')';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ストアドプロシージャを jQuery + PHPで呼び出してみた

はじめに

・トランザクション処理をストアドプロシージャにまとめることでソースをすっきりさせたい衝動にかられ、ストアドプロシージャを呼び出してみた。
・いままでクライアント側にSQLをベタ打ちしていてセキュリティーの問題がやばいみたいなので...

開発環境

・サーバー構成
 WindowsServr2012R2
 Apache + SQLServer2012 + PHP
 
・クライアント構成
 JavaScript + jQuery

実装コード

・クライアントからのAPIの呼び出し

classDatabase.js
//=========================================
// Database クラス
// データベースに関する命令をまとめる
//=========================================
const DatabaseAsyncOn = true;
const DatabaseAsyncOff = false;

function Database(accountName, userName, async) {
    this.accountName = "dev"; // accountName;
    this.userName = "dev"; // userName;
    this.async = false; // async;
}
Database.prototype = {
    //=========================================
    // SELECT メソッド(prepareを使用したもの)
    // DBからSQLを使ってデータをGETする
    //=========================================
    getProcedures: function(mappingName, prepare) {
        var returnData = null;
        $.ajax({
            type: "POST",
            url: "../../common/php/getProcedures.php",
            data: {
                "accountName": this.accountName,
                "userName": this.userName,
                "mappingName": mappingName,
                "prepare[]": prepare
            },
            dataType: "json",
            async: this.async,
            // 成功したときの処理
            success: function(data) {
                returnData = data;
                console.log('\n\nmappingName: ' + mappingName);
                console.log('parametter: ' + prepare);
                console.log('data: ' + data);
            },
            // エラー処理
            error: function(data) {
                returnData = false;
                console.log('\n\nmappingName: ' + mappingName);
                console.log('parametter: ' + prepare);
                console.log('data: ' + data);
            }
        });
        return returnData;
    }
};
main.js
    // APIの呼び出し
    // マッピング:200402
    self.midokuMember = self.database.getProcedures(
            'mapping200402',[
                self.companyID,
                eventNo,
                today
            ]
    );

サーバー側
 ストアドプロシージャ呼び出し用のAPI

setProcedures.php
<?php
  $accountName = $_POST["accountName"];
  $userName    = $_POST["userName"];
  $mappingName = $_POST["mappingName"];
  $prepare     = $_POST["prepare"];
  $org_timezone = date_default_timezone_get();
  date_default_timezone_set('Asia/Tokyo');
  $dsn = 'sqlsrv:server=【IPアドレス】;database=' . $accountName;
  $password = '【DB接続パスワード】';
  try {
    $dbh = new PDO($dsn, $userName, $password);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE , PDO::FETCH_ASSOC);
    //////////////////////////////////////////////
    // セット処理
    //////////////////////////////////////////////
    // ステートメント準備
    $stringPrepare = "{CALL ".$mappingName."(";
    foreach ($prepare as $key => $value) {
      # code...
      $stringPrepare = $stringPrepare."?,";
    }
    if (count($prepare) != 0) {
      $stringPrepare = substr($stringPrepare, 0, -1);   // 最後尾の文字を削除する
    }
    $stringPrepare = $stringPrepare.")}";
    error_log($stringPrepare);
    $stmt = $dbh->prepare($stringPrepare);
    for ($i=0; $i < count($prepare); $i++) {
      # code...
      $stmt->bindParam($i+1, $prepare[$i], PDO::PARAM_STR);
    }
    $stmt -> execute();
    echo "\nPDO::errorInfo():\n";
    print_r($dbh->errorInfo());
    $dbh = null;
    date_default_timezone_set($org_timezone);
  }
  catch (PDOException $e) {
    echo 'Error : ',  $e->getMessage(), "\n";
    die('MSSQL Serer Connect Error');
  }
?>

呼び出し元のストアドプロシージャ

mapping200409.sql
USE [dev]
GO
/****** Object:  StoredProcedure [dbo].[mapping200402]    Script Date: 2018/12/26 12:10:09 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[mapping200402]
  @companyID nvarchar(4),
  @eventNo int,
  @today nvarchar(10)
AS
BEGIN TRY
    BEGIN TRANSACTION        --トランザクションの開始
        BEGIN
            -- マッピング2:未読者の取得
            SELECT MK_smartPhone.companyID,
                   MK_smartPhone.staffID,
                   MK_staff.name
            FROM   MK_smartPhone INNER JOIN MK_staff ON 
                   MK_smartPhone.companyID = MK_staff.companyID AND 
                   MK_smartPhone.staffID = MK_staff.staffID LEFT OUTER JOIN
                   MK_history ON 
                   MK_smartPhone.companyID = MK_history.companyID AND 
                   MK_smartPhone.staffID = dbo.MK_history.staffID
            WHERE  (MK_smartPhone.name <> N'card') AND 
                   ((MK_history.category = 2) AND 
                   (MK_history.flag = 0) AND 
                   (MK_history.companyID = @companyID) AND 
                   (MK_history.externNo = @eventNo) AND 
                   (MK_staff.retireDate > @today OR
                    MK_staff.retireDate IS NULL OR
                    MK_staff.retireDate = '') OR
                   (MK_history.flag IS NULL))
            GROUP BY MK_smartPhone.companyID,
                     MK_smartPhone.staffID, 
                     MK_staff.name, 
                     MK_history.externNo
            ORDER BY MK_smartPhone.staffID          
        END
    COMMIT TRANSACTION       --トランザクションを確定
END TRY


--例外処理
BEGIN CATCH
    ROLLBACK TRANSACTION     --トランザクションを取り消し
    PRINT ERROR_MESSAGE()    --エラー内容を戻す
    PRINT 'ROLLBACK TRANSACTION'
END CATCH

RETURN

まとめ

・SQLを別ファイルに分離してソースの読みやすさ管理のしやすさの向上が図れた。
・言語ごとに得意分野があるのでそれぞれ適材適所で使用していきたい。※違う言語が紛れているとソースが読みづらくなる

 下記の様な使い分けがよいと改めて思った。
①JavaScript・・・画面周り、APIの呼び出し(ビジネスロジックは書かない)
②PHP・・・ビジネスロジックだけを書く
③ストアドプロシージャ・・・トランザクションの処理(ビジネスロジックは書かない)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP おみくじメモ

rand()を知った時におみくじ作れるんじゃね?と思って作ってみた。(当たり前ですが、すでにあるものは無視して自分で考えて作ることを意識してます。)
ただ、それだけ。

入力画面

index.php

    1から9までの数字を入れてGOボタンを押してね!

    <form method="post" action="result.php">
      <input type="text" name="fig">
      <input type="submit" value="GO">
    </form>

結果を表示する

result.php


    $fig = $_POST['fig']; 
    $result = rand(1,9); 

    if ($fig == $result)
    {
      echo "当たりだよ。おめでとう!";
    } 
    else 
    {
      echo "外れだよ。ごめんね!";
    }

    echo '<br />';

一応動作はしたので、メモ程度に載せておきます。

余談

実務で学ばないとどのようなイレギュラーがあるかわからないし、対処法もわからないのでこんなイレギュラーがあるよって人いたらコメントください。
それについて勉強しますので。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php-master-changes 2019-05-06

今日は FFI のバグ修正、テストの修正、不要テストの削除、ドキュメントの修正、JIT の比較処理の生成コードの修正があった!

2019-05-06

cmb69: Properly initialize variable

bukka: Fix OpenSSL online test for ca context

peter279k: Remove duplicate getimagesize_variation_003 test

petk: Remove duplicate test file

petk: Remove phpextdist

petk: [ci skip] Move wddx to dedicated section

petk: Rename uppercase JPG file to lowercase jpg

petk: Fix double incremenation in ffi

dstogov: Improved code for unordered comparison

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel のテストであるクラスのプロパティをテストから書き換えて初期化するには

この記事について

半分個人メモです。

ある特殊なテストを行いたくて、private なプロパティをテストメソッドから書き換えて実行する方法を探っていたところ、以下のやり方を思いついたので書き残しておきます。

はじめに

概要

あるクラスが config から読み取った値を private なプロパティにセットして初期化していて、その値を、あるテストメソッドでのみ、特殊な環境変数で上書きして実行したい、というケースに遭遇した際、リフレクションと無名クラスで対応できそうだったのでやってみました。

環境

  • PHP: 7.3.2
  • Laravel: 5.8.13
  • PHPUnit: 7.5.8

詳細

とあるクラス

<?php

namespace App\Services;

class SomeService
{
    private $config;

    public function __construct()
    {
        $this->config = config('services.some.config');
    }

    public function doSomething()
    {
        return $this->config;
    }
}

テストクラス

<?php

namespace Tests\Feature\Services;

use App\Services\SomeService;
use Tests\TestCase;

class SomeServiceTest extends TestCase
{
    public function testDoSomethingInGeneralEnvironment()
    {
        $service = app(SomeService::class);

        $this->assertEquals('hoge', $service->doSomething());
    }

    public function testDoSomethingInSpecialEnvironment()
    {
        $service = new class extends SomeService {
            public function __construct()
            {
                $class = new \ReflectionClass(SomeService::class);
                $property = $class->getProperty('config');
                $property->setAccessible(true);
                $property->setValue($this, env('SOME_SERVICE_CONFIG_OVERWRITTEN'));
            }
        };

        $this->assertEquals('fuga', $service->doSomething());
    }
}

config/services.php

    'some' => [
        'config' => env('SOME_SERVICE_CONFIG'),
    ],

.env

SOME_SERVICE_CONFIG=hoge
SOME_SERVICE_CONFIG_OVERWRITTEN=fuga

SOME_SERVICE_CONFIG_OVERWRITTEN は、.env.testing とか phpunit.xml とかに書いてもいいかもしれないです(テストでしか使わないやつなので)。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows環境のVSCodeでxdebugでremote_connect_back=1でリモートデバッグを行いたいときの設定(プロキシ環境)

概要

ドはまりしてしまったVSCodeのxdebugの設定について記載します。
VSCodeをつかって社内LANでつながっている開発サーバにxdebugをセットアップし、リモートデバッグをしようとしました。

サーバ側の設定(php.ini)やクライアント側の設定(VSCode)の設定は割愛します。
(ページ下部の参考にしてほしいサイトを確認してください)

ホストを設定してからのリモートでバッグ

xdebug.remote_host=172.20.1.30

上記であればうまくいきましたが、

xdebug.remote_connect_back=1

これだとだめでした。デバッグがとまりませんでした。なぜ。

結論

結論から言うと、プロキシのせいでした。
プロキシを通って開発サーバにアクセスという経路を通っていたため、remote_connect_backで許可する範囲を超えてしまっており、リモートデバッグができていませんでした。

プロキシを通さずに開発サーバにつなげる設定箇所は下記のとおりです。
(もちろん環境によってはできないこともあります。可能かどうかはネットワークに詳しい人に構成を確認してもらいましょう。だめならremote_hostで設定すればいいだけの話です。めんどくさいですし忘れやすいところですが。)
image.png
ここにチェックをする、もしくは
image.png
ここに例外のホスト記述を行います。
(「172.20.*」など)

原因がプロキシでなさそうな方へ

下記を確認してみてください。参考にしてほしいサイトです。とても参考になります。

[PHP] Xdebug のリモートデバッグ、理解していますか?
https://qiita.com/castaneai/items/d5fdf577a348012ed8af

【PHP】リモートマシンのデバッグを、「複数人で」やる【Xdebug×DBGpProxy】
https://qiita.com/takudo/items/35d2fd3c6e9846715bf4

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel5のRequestクラス覚え書き

Webアプリケーションフレームワーク LaravelIlluminate\Http\Request の書き方メモ。リファレンス的なもの。

Controller
namespace App\Http\Controllers;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function store(Request $request)
    {
        $name = $request->input('name');
        //
    }
}
View
@extends('layouts.app')
@section('body')
<form method="POST" action="{{ route('XXXX') }}">
  @csrf
  <input type="text" name="name" value="">
  // 
</form>
@stop

入力の取得

  • 動的プロパティで入力を取得
    $name = $request->name;

  • 全入力を連想配列で取得
    $input = $request->all();

  • アップロードファイルを除いて、全入力を連想配列で取得
    $input = $request->input();

  • すべてのクエリストリングを連想配列で取得(上記のallやinputにもクエリストリングは含まれる)
    $input = $request->query();

  • アップロードファイルの取得
    $file = $request->file('csv');

  • "name" の入力を取得
    $name = $request->input('name');

  • デフォルト値を指定して取得
    $request->input('name', 'Tom');

  • "name" と "password" のみ取得
    $input = $request->only('name', 'password');

  • "password" 以外を取得
    $input = $request->except('password');

  • リクエストに値が存在するか
    $request->has('name')

  • すべて存在するか
    $request->has(['name', 'email'])

  • リクエストに値が存在し、かつ空でないか
    $request->filled('name')

フラッシュデータ(次のリクエストの間だけ利用できるデータ)の保存

  • すべてをフラッシュデータに保存
    $request->flash();

  • "name" と "email" のみフラッシュデータに保存
    $request->flashOnly(['name', 'email']);

  • "password" を除いてフラッシュデータに保存
    $request->flashExcept('password');

セッション

  • "key" をセッションから取得
    $request->session()->get('key');

  • デフォルト値を指定してセッションから取得
    $request->session()->get('key', 'default');

その他

  • リクエストURIを返す
    $uri = $request->path();

  • HTTPメソッド名(GET、POST等)を返す
    $method = $request->method();

  • POSTメソッドか
    $request->isMethod('POST')

  • リクエストURIがパターンに合致するか
    $request->is('admin/*')

  • User-Agentを返す
    $request->header('User-Agent');

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelでLeague/Csvを使ってアップロードされたcsvを読み込みDBに保存する

環境

Laravel 5.6
League/Csv 9.2

前提

  • Qiita初投稿です、お手柔らかにお願いします。
  • 今回はバリデーションについては実装、言及しません。

導入

League/Csvをインストールする。
composer dump-autoloadは多分必要。

composer require league/csv
composer dump-autoload

今回ぶち込みたいCSV

company_name name email
hoge株式会社 佐藤 exapmle1@example.com
foo株式会社 山田 exapmle2@example.com

実装

UserController.php
<?php

namespace App\Http\Controllers;

use App\Eloquent\User;
use Illuminate\Http\Request;
use League\Csv\Reader;
use League\Csv\Statement;

class UserController extends Controller
{

    // 省略

    public function importCSV(Request $request, Statement $stmt, User $user)
    {
        $file_path = $request->file('file')->getPathname();
        // ReaderはDIできないらしい。
        $csv = Reader::createFromPath($file_path, 'r')->setHeaderOffset(0);
        $records = $stmt->process($csv);
        $data = [];

        // 後ほど解説。
        foreach ($records as $record) {
            $record['created_at'] = now();
            $record['updated_at'] = now();
            $data[] = $record;
        }
        $user->insert($data);

        return redirect()->route('user.index');
    }
}

解説

ここでリクエストされたfileの一時保管パス?的なのを取得する。

$file_path = $request->file('file')->getPathname();

そしてここ、

foreach ($records as $record) {
            $record['created_at'] = now();
            $record['updated_at'] = now();
            $data[] = $record;
        }
        $user->insert($data);

$recordsには

ResultSet {#375 ▼
  #records: LimitIterator {#372 ▶}
  #header: array:4 [▶]
}

というオブジェクトが入っているのでforeachで取得していくとなんと、各recordには

[ "company_name" => "hoge株式会社"
  "name" => "佐藤",
  "email" => "example1@example.com"
]

などの配列が入っているというわけ。

Laravelだとinsertの引数に各レコードを値にもつ多次元配列を渡すことで複数レコードまとめて挿入できる。
しかし、insertは「created_at」「updated_at」が更新されないので、foreachで回しながら入れている。

おまけ

今回のroute

web.php
Route::post('/users/csv', "UserController@importCSV")->name('user.importCSV');

今回のview

users.blade.php
  {{ Form::open(['url' => route('user.importCSV'), 'method' => 'POST', 'class' => '', 'files' => true]) }}

  <div class='form-group'>
    <input type="file" name="file" value="">
  </div>

  <button type="submit">csv読み込み</button>

  {{ Form::close() }}

応用編

大切なことはすべて公式ドキュメントが教えてくれた。
9.0のドキュメント↓
https://github.com/thephpleague/csv/blob/master/docs/9.0/index.md

<?php
use League\Csv\Reader;
use League\Csv\Statement;

$csv = Reader::createFromPath('/path/to/your/csv/file.csv', 'r');

// headerは何行目か。
$csv->setHeaderOffset(0);

$header = $csv->getHeader();
//return [0 => "企業"
//  1 => "名前"
//  2 => "メールアドレス"]

// 10行飛ばして25行とる
$stmt = (new Statement())
    ->offset(10)
    ->limit(25);
$stmt->process($csv);

まとめ

2019年でPHPでcsvをいじりたいってなると、goodbyか、このLeague/Csvの二強な感じがしていて、今回書きやすそうだしGitHubのスターもダントツでLeague/Csvの方が高かったのでこちらを採用しました。
今回は使わなかったのですが、goodbyは省メモリを売りにしているそうなのでメモリを気にする量のcsvを裁くときは視野に入れたいですね。
結局、Laravelが好きです。

引用

https://github.com/thephpleague/csv/blob/master/docs/9.0/index.md

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む