カテゴリーアーカイブ PHP

著者:杉浦

【Laravel】Bladeの制御構文の探し方

 LaravelのBladeドキュメントは丁寧でわかりやすいですが、わかりやすさのために切り捨てられている部分もあります。例えばBladeの制御構文がいくつか抜け落ちています。この記事では探し方を紹介します。探し方が分かればバージョンが変わっても平気です。
Bladeテンプレート 5.7 Laravel
 探すことをプログラムに任せる直接的な解決策があります。そういうプラグインのあるエディタを使用してそのプラグインを入れましょう。PhpStormならばBlade Supportがあり次の画像の様に、色付けと予測を行ってくれます。
 Blade Support – Plugins | JetBrains

 Bladeの元を探そうとしてもクラスやメソッドの呼び出しを辿って見つけることは難しいです。というのもLaravelはそのソースコード内で特定の名前のクラス、メソッドをまとめて呼び出したり、その場でPHPの構文を作ったりすることで根本のコードを小さく保っているためです。例えば次の様なコードが現れます。

    /**
     * Register the engine resolver instance.
     *
     * @return void
     */
    public function registerEngineResolver()
    {
        $this->app->singleton('view.engine.resolver', function () {
            $resolver = new EngineResolver;

            // Next, we will register the various view engines with the resolver so that the
            // environment will resolve the engines needed for various views based on the
            // extension of view file. We call a method for each of the view's engines.
            foreach (['file', 'php', 'blade'] as $engine) {
                $this->{'register'.ucfirst($engine).'Engine'}($resolver);
            }

            return $resolver;
        });
    }

 このためLaravelの中を探す時はそれっぽいクラス名、メソッド名を用いた正規表現によって隠された呼び出し元を探す方法が有効です。肝心のBlade構文を字句解析(大雑把いえば文字列を意味のある字句に区切る処理)している部分は次です。この部分はViewのProvider周りから呼び出されています。

// vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.phpの内部
    /**
     * All of the available compiler functions.
     *
     * @var array
     */
    protected $compilers = [
        'Comments',
        'Extensions',
        'Statements',
        'Echos',
    ];
    
    
    /** 省略 **/
    
    /**
     * Parse the tokens from the template.
     *
     * @param  array  $token
     * @return string
     */
    protected function parseToken($token)
    {
        [$id, $content] = $token;

        if ($id == T_INLINE_HTML) {
/** ここから */
            foreach ($this->compilers as $type) {
                $content = $this->{"compile{$type}"}($content);
            }
/** ここまでが重要 */
        }

        return $content;
    }

 $this->{“compile{$type}”}($content);を満たすメソッドで字句解析をしています。これを満たすメソッドで@以下を解析しているのが次のcompileStatementsです。

    /**
     * Compile Blade statements that start with "@".
     *
     * @param  string  $value
     * @return string
     */
    protected function compileStatements($value)
    {
        return preg_replace_callback(
            '/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', function ($match) {
                return $this->compileStatement($match);
            }, $value
        );
    }

    /**
     * Compile a single Blade @ statement.
     *
     * @param  array  $match
     * @return string
     */
    protected function compileStatement($match)
    {
        if (Str::contains($match[1], '@')) {
            $match[0] = isset($match[3]) ? $match[1].$match[3] : $match[1];
        } elseif (isset($this->customDirectives[$match[1]])) {
            $match[0] = $this->callCustomDirective($match[1], Arr::get($match, 3));
/** ここから*/
        } elseif (method_exists($this, $method = 'compile'.ucfirst($match[1]))) {
            $match[0] = $this->$method(Arr::get($match, 3));
        }
/** ここまで重要 ucfirstは最初の一文字が大文字なら小文字にする関数*/
        return isset($match[3]) ? $match[0] : $match[0].$match[2];
    }

 到達しました。BladeCompilerのインスタンスに引っ付いているcompileHogeなメソッドがあれば@hogeが動作するということです。後はBladeCompilerに生えているメソッド一覧を見て終わりです。


 多いです。使いこなせている人はいるのでしょうか。

  • この記事いいね! (0)
著者:杉浦

【Laravel】Laravel5.6以降のヘルパ関数abortは結構自由

 LaravelにはHTTP例外を投げるヘルパ関数abortが用意されています。

abort関数は、例外ハンドラによりレンダーされるであろう、HTTP例外を投げます。

abort(403);

例外のレスポンステキストと、カスタムヘッダを指定することもできます。

abort(403, 'Unauthorized.', $headers);

ヘルパ 5.7 Laravel#method-abort

 abort(403);と一言記述するのみで権限エラーである403のエラーページにクライアントを飛ばせます。この使い方が最も主で伝統的な使い方です。しかしもう少し複雑なHTTPレスポンスを返したい場合もあります。
 使い方が書かれている公式の翻訳ドキュメントにおいて、abort関数はステータスコードとメッセージとヘッダを指定するだけの簡易な関数に見えますが、実際はもっと自由です。ヘルパ関数abortは/vendor/laravel/framework/src/Illuminate/Foundation/helpers.phpにあるabort関数を呼び出しています。その呼び出される部分が次です。

// Laravel5.7版のコード
if (! function_exists('abort')) {
    /**
     * Throw an HttpException with the given data.
     *
     * @param  \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Support\Responsable|int     $code
     * @param  string  $message
     * @param  array   $headers
     * @return void
     *
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    function abort($code, $message = '', array $headers = [])
    {
        if ($code instanceof Response) {
            throw new HttpResponseException($code);
        } elseif ($code instanceof Responsable) {
            throw new HttpResponseException($code->toResponse(request()));
        }

        app()->abort($code, $message, $headers);
    }
}

 注目する部分はPHPDocの$codeで指定されている型と関数が始まった直後のif文部分です。これは$codeにはステータスコードになるintのみでなく\Symfony\Component\HttpFoundation\Responseを継承するか\Illuminate\Contracts\Support\Responsableを実装したクラスのインスタンスを引数にすれば、HTTPレスポンスとして適した形で例外の結果をクライアントに返します。
 PHPのinstanceofは完全にイコールのインスタンスのみでなく継承元、実装元の場合でも真になります。
PHP: 型演算子 – Manual

 このinstanceofの性質からabort関数は例えばこのように使えます。

abort(resnponse()->json("I'm a teapot", 418));

 ExceptionクラスやHandlerクラスを気にすることなく例外の結果をJSON型のHTTPレスポンスとしてクライアントに投げられます。
 LaravelにはインタフェースIlluminate\Contracts\Support\Jsonableがあり、これを実装したクラスはJSON化が担保され、至る所でJSONとして扱えます。JsonableはModelやCollectionを始めとして多くのもので実装されています。このため次の様なこともできます。

// User.php
class User extends Model implements Responsable
{
    public function isOne()
    {
        return $this->getKey() === 1;
    }
    public function toResponse($request)
    {
        return response()->json($this);
    }
}


// 適当な別ファイル
$a = User::inRandomOrder();
$a->isOne() ?: abort($a);

 Modelの場合、プロパティがJSON化されます。abortに渡されたUserインスタンスのtoResponseメソッドが動作し、response()->json($this)によりJSON化されたUserインスタンスがHTTPレスポンスとして返されます。
 この変更はLaravel5.6時に追加されました。
allow giving a response to abort · laravel/framework@4e29889

  • この記事いいね! (0)
著者:杉浦

PhpStormのダイアグラム機能によるクラス図自動生成

 コードを読む時、しばしばクラス間の関係が記述されたクラス図が欲しくなる時があります。あると大変助かるのですが、大体そういったドキュメントはありません。PhpStormにはダイアグラム機能がついており、これはクラス図を始めとしたいくつかの図を自動生成する機能です。
ダイアグラムの操作 – 公式ヘルプ | PhpStorm
例えば、Laravelのソースコードの一部であるHTTPフォルダ以下のクラス図は次の様になります。

 図の矢印の意味は、矢印元は矢印先を参照して作られています、というもので、互いの依存関係を表しています。青の濃い矢印は直接の継承、緑の破線矢印はインタフェースの実装、白の破線ひし形矢印はトレイトの包含です。ただファイルを見るだけでは結構な量があり全貌をつかむまで時間がかかりがちですが、これならばすぐわかります。次の図はクラス図にしたHTTPフォルダ以下のフォルダ構造の木です。気持ち程度しか関係性が読み取れません。

 クラス図の例はPHPでしたが次の様にデータベース、JavaScriptなどにも対応しており、レイアウトのテンプレートも様々です。

  • この記事いいね! (0)
著者:杉浦

【PHP】PHPDocからドキュメントページを作成するphpDocumenterの紹介

phpDocumentor
phpDocumentor/phpDocumentor2: Documentation Generator for PHP
 phpDocumentorはPHPDocを読み取り、PHPDocに従ってドキュメントを表すHTMLページを作成してくれます。インストールは次の画像やphpDocumentorの右側参照、要はvendor/bin以下にphpDocumentorの実行ファイルが置ければ何でもよいです。

 実行方法は次の画像の通りPHPを介して実行。コマンドは

php [phpDocumenter実行ファイル] -d [対象ファイル、フォルダのパス] -t [出力先のパス]

です。

 こうして出来上がったページが次の画像です。この様にnamespaceに従った階層でPHPDocに従ってメソッド、プロパティとその説明などが記述されます。
 
 自動生成で相当リッチなページが作られます。注意点としてリッチすぎてPHPDocを厳格に扱っていない場合、空欄による穴あきだらけになる点があります。

  • この記事いいね! (0)
著者:杉浦

【PHP】is_iterable()===falseでもforeachできるオブジェクト

 foreachは繰り返し要素を参照する制御構造です。次図の様に配列の中身を繰り返して扱う時によく使われます。

PHP: foreach – Manual
 is_iterableは変数の内容が反復可能な値であることを確認する関数です。次図の様に与えられた変数の型が配列などの数え上げが可能な型であるか否かを返しています。

PHP: is_iterable – Manual
 is_iterableは反復可能な値であることを確認する関数であり、foreachは繰り返し要素を参照する構文です。であるならば、ある変数$hogeに関してis_iterable($hoge)===falseの時、foreach($hoge as $h)が出来ないように思えます。しかしPHPはちょっと違います。

 is_iterable($hoge)===falseでありながらもforeachが実行されてvar_dumpの出力が表示されています。この現象の理由の一つはオブジェクトの反復処理の定義にあります。公式のマニュアルには次の様にあります。

PHP 5 は、たとえば foreach 命令などによる反復処理を可能とするよう オブジェクトを定義する手段を提供します。 デフォルトで、 全ての アクセス権限がある プロパティは、反復処理に使用することができます。

PHP: オブジェクトの反復処理 – Manual

 これが原因でオブジェクトはis_iterable===falseであってもforeachできます。foreachで参照する要素はアクセス可能なプロパティ全てです。
 foreachできるにもかかわらずis_iterable===falseになる原因はis_iterableが実際にやっている内容が上の画像の端的な説明と異なっている点にあります。実際の挙動においてis_iterableは反復可能な値であるか否かでなく、擬似型iterableを満たすか否かを判定しています。

変数の内容が iterable 疑似型で許容されること、すなわち array、あるいは Traversable を実装したオブジェクトであることを確認する。
PHP: is_iterable – Manual

 まさしく、is iterable?、と訊いているわけですね。
 上記二つの原因によって、iterable型を満たさないオブジェクトは反復処理が可能であるがis_iterable===trueになりません。正直直感的でない仕様であり、少しでもマシにしようとRFCを提出した人もいます。
PHP: rfc:iterable-stdclass

  • この記事いいね! (0)
著者:杉浦

【PHP】組み込み関数number_formatで現れる浮動小数点数

 PHPにはnumber_formatなる組み込み関数があります。

number_format — 数字を千位毎にグループ化してフォーマットする

number_format ( float $number[, int $decimals = 0] ) : string

PHP: number_format – Manual

 number_formatは整数に区切りのカンマを入れる目的で主に使われます。これだけならば問題はないのですが時には小数を扱いたい時もあります。number_formatそのままでは少数を出力することはできません。表示を行うには第2引数に表示する少数の桁数を記述する必要があります。

 この表示する少数の桁数の指定は少数が途切れた後でも続きます。

 少数部を16桁表示する様に命令したところ、number_formatから返された文字列から読み取れる値が意図した値である1400.05と異なります。この原因には浮動小数点数が絡んでいます。
 number_formatの引数にあるfloat numberがこの現象の主な原因です。この説明にある様にnumber_formatの第1引数はいかなる値であってもfloat型、浮動小数点数に型変換されます。この変換された浮動小数点数を指定されたフォーマットに沿って返す関数がnumber_formatです。浮動小数点数はある大きさのビット(大体32ビットや64ビット)を符号部(sign)、仮数部(flaction)、指数部(exponent)に分けて、ある値をflaction^(sign*exponent))という式によって表す数です。

 浮動小数点数のこの方式は広い範囲の値を表現できますが、正確な少数を表すことがほとんどできません。最も近いそれらしい値を出力して四捨五入で見かけを整えることで対応しています。関数number_formatもこの例にもれません。1400.05はnumber_format内部では1400.05に近い何らかの数として扱われています。このため指定する少数の桁数を増やすと次の様にもっとずれていきます。

 四捨五入を行っているので次の動作も起きます。

 元の数値を文字列と扱える場合、次の様に解決できます。

 少数部を切り分けて、特定の長さになる様に右側を0埋め、最後に合体させて完成です。

  • この記事いいね! (0)
takahashi 著者:takahashi

FuelPHPで今開いているページのコントローラー/アクションを取得する方法

・コントローラー名を取得したいとき(コントローラークラス名)

echo Request::main()->controller;
//ex.: Controller_Hogehoge

・アクション名を取得したいとき

echo Request::active()->action:
//ex.: huga

なお、コントローラーを取る方法として、Uriクラスを使って

 echo Request::main()->uri->segment(1);  //ベースURLから1番目の値をとる

とすることもできるようですが、index(/)などの場合、取得できる値がNULLになってしまう問題があるため、個人的には最初の方法で取得したうえで正規表現などで加工した方が確実かな、と思います。

参考:
[PHP][FuelPHP]FuelPHPで気になるあの情報の取り出し方 – ECサイト運営開発記

  • この記事いいね! (0)
著者:杉浦

array_uniqueとsuper_unique

 PHPにはarray_uniqueという組み込み関数があります。
PHP: array_unique – Manual
 効果は次の引用の通りシンプルで配列の中身をユニークなものにします。

array_unique
( array $array
[, int $sort_flags = SORT_STRING
] ) : array

array を入力とし、値に重複のない新規配列を返します。

引用:PHP: array_unique – Manual

 この第二引数sort_flagsはsortなどとついていますが、実質的にcompare_flagsともいうべきものです。array_uniqueは次の通りに配列の中身の値を扱って比較します。

  • SORT_REGULAR – 通常の比較
    (型変換をしない) を行います
  • SORT_NUMERIC – 数値として比較します
  • SORT_STRING – 文字列として比較します
  • SORT_LOCALE_STRING
    現在のロケールにもとづいて文字列として比較します。

引用:PHP: array_unique – Manual

 ちなみにデフォルトはSORT_STRINGです。数値、文字列以外はSORT_REGULARでそれっぽく動きます。

注意:

array_unique() は、
多次元配列での使用を想定したものではないことに注意しましょう。

引用:PHP: array_unique – Manual

 それっぽいといった通り、多重配列であったり配列の中身の型がまちまちだった場合、array_uniqueは意図しない挙動を起こしだします。この問題は昔からあった様で解決策はPHP: array_unique – Manual #97285にありました。

recursive array unique for multiarrays

<?php
function super_unique($array)
{
  $result = array_map("unserialize", array_unique(array_map("serialize", $array)));

  foreach ($result as $key => $value)
  {
    if ( is_array($value) )
    {
      $result[$key] = super_unique($value);
    }
  }

  return $result;
}

 やっていることは配列の中身をserialize(値の保存可能な表現を生成)し、配列の中身を一旦全て文字列に変換、array_uniqueを実行、unserializeで元の型に復元、です。このsuper_uniqueを用いることでより直感的なarray_uniqueの挙動を実現できます。

  • この記事いいね! (0)
著者:杉浦

PHPStormでartisanコマンドの予測入力をする

 PHPフレームワークLaravelにはartisanコマンドがあります。artisanはアプリケーション開発全体で役に立つ、数多くのコマンドを提供しています。例えば必須ないしあった方がよいコードを持ったモデル、テスト、ミドルウェアの作成、データベースのまとまった操作、Laravelが今持つことになるルーティングリストの表示です。もちろん他にも様々なものがあります。
 artisanの一覧を見ることはphp artisan listからできますが、いちいち確認するのは面倒です。PHPStormのコマンドラインツールにはSymfonyのコマンドライン用クラス\Symfony\Component\Console\Command\Commandを元に作成されたコマンドのコマンドライン入力とサポートするプラグインCommand Line Tool Supportがあります。LaravelのartisanコマンドはSymfonyのCommandクラスから継承されて作られています。次の図はCommandクラスの継承先のリストの一部です。各aritsanコマンドの名が冠されたクラスが並んでいます。
 
 プラグインは設定->プラグインのマーケットプレイスから検索できます。インストールできたなら設定->ツール->コマンド・ライン・ツールのサポートを選び、追加からTool Based on Symfony Consoleを選択、php実行ファイルとartisanファイルのパスを設定して完成です。


 これでartisanコマンドの予測ができるようになりました。artisanを介することでnamespaceや必須メソッド、継承元などをいちいち記述せずとも各基本クラスのひな型を作ることができ、データベースの操作もまとめてでき、開発がはかどります。

  • この記事いいね! (0)
村上 著者:村上

【PHP】Slim3フレームワークのCSRF対策ミドルウェア「Slim-Csrf」の導入方法

勘違いから若干手こずったので、備忘録としてまとめ。
PHPフレームワーク「Slim3」でCSRF対策を行うためのミドルウェア「Slim-Csrf」の導入方法です。

GitHub のページはこちらから。

GitHub – slimphp/Slim-Csrf: Slim Framework CSRF protection middleware
https://github.com/slimphp/Slim-Csrf

サンプルコードも掲載されているので、分かりやすいはずなのですが…よくわからない勘違いをしたせいで、少し悩んでしまいました。

 

では、実装方法手順について。
なお、開発環境は「Slim-Skeleton」で作成したプロジェクトを使用しています。

まず下記のコマンドを実行して「Slim-Csrf」をインストールします。

composer require slim/csrf

インストールが完了したら、src/middleware.php を開き、下記のコードを追加します。

$container['csrf'] = function ($c) {
    return new \Slim\Csrf\Guard;
};
$app->add($container->get('csrf'));

上記を追加したら、次は src/routes.php を開いて、まずは下記のコードを追加します。

$app->get('/[任意のパス]', function (Request $request, Response $response, array $args) {
    $args['csrf_name'] = $request->getAttribute('csrf_name');
    $args['csrf_value'] = $request->getAttribute('csrf_value');

    // Render index view
    return $this->renderer->render($response, 'index.phtml', $args);
    // 任意のテンプレートに変更してもOK
});

こちらは、送信元のページです。
2、3行目のように、変数 $args に値を格納すれば、6行目で指定しているテンプレートでその値をしようできます。
上記コードでは、csrf_namecsrf_value に格納するランダムな値を生成し、それをテンプレートに渡しています。

次は、テンプレートファイルを開きます。
私の場合は、templates/index.phtml を使用します。
追加するコードは下記のとおり。なお、Form 部分だけを抜粋しています。


<form action="[送信先のパス]" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="csrf_name" value="<?=$csrf_name ?>">
    <input type="hidden" name="csrf_value" value="<?=$csrf_value ?>">
    <input type="file" name="picture" accept="image/*">
    <input type="submit" name="submit" value="送信">
</form>

重要なのが 2、3行目で、上の src/routes.php で生成した変数 csrf_name と csrf_value の値を、同名の input タグ(hidden)の value に格納しています。
これらの値を一緒に送ることで、他サイトから等の不正な書き込みを検知・防止することができます。

最後に、送信先の処理を、src/routes.php に記述します。

$app->post('[送信先のパス]', function (Request $request, Response $response, array $args) {
    $postParams = $request->getParsedBody();
    $uploadedFiles = $request->getUploadedFiles();
    $picture = $uploadedFiles['picture'];
    if ($picture->getError() === UPLOAD_ERR_OK) {
        // 送信されたファイルに行う処理
    }
});

といっても、生成したランダムな値のチェックは自動でやってくれるので、成功した場合は上記で記述した処理が実行されるようになっています。
そのため、こちらには送信したデータの処理内容だけを書けばOKです。

上記のコードでは、Form で画像データを送っているので、その送信されたファイルデータを取得しています。
アップロード等は、適宜行ってください。
なお、Slim3 のドキュメントにもファイルのアップロードに関する項目がありますので、参考にしてください。
リンクはこちら。

Uploading files using POST forms – Slim Framework
http://www.slimframework.com/docs/v3/cookbook/uploading-files.html

必要なコードは以上です。

 

以上、「Slim-Csrf」ミドルウェアを使用した、CSRF対策でした。
Slim-Skeleton で作成したプロジェクトがベースになっていますが、恐らく流用はできるはず。
参考にしていただければ幸いです。

  • この記事いいね! (0)