カテゴリーアーカイブ PHP

著者:杉浦

【Laravel】【Docker】Laravel用Dockerパッケージlaradocの紹介

 Dockerは仮想マシンを用意するツールです。何かと便利なのですが、仮想マシン中のソフトそれぞれについての知識がそれなりに必要になります。知識なしのままデフォルト設定から離れるとバグを生みがちです。
If it works in Docker, it works on CircleCI – CircleCI
 そうなるとDockerのデフォルト設定でなくLaravel用の安全な設定が欲しくなります。Laradocはそういった要素のパッケージです。Laradock
 サーバ機能、各データベース、jenkinsなどCIツール、Laravel推奨パッケージ、などが詰まっています。パッケージに命令を発する。docker-composeファイルは次の様な巻物になっています。この中から探すと大体メジャーなソフトやパッケージが見つかります。また、扱いやすい様に高頻度で変えられるパラメータなどが変数化されており、簡単安心に基本的な使い方ができます。
 
 日本語のチュートリアルは色々な人が詳しく書いているのがたくさんあるのでググれば目的のものが大体あります。

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

【Laravel】任意のseederを動かすartisanコマンドを追加するlaravel-artisan-seeder-selectorの紹介

 Laravelにはシーディングという仕組みがあります。

シーダ(初期値設定)クラスを使用し、テストデーターをデーターベースに設定するシンプルな方法もLaravelには備わっています。全シーダクラスはdatabase/seedsに保存します。シーダクラスには好きな名前を付けられます。しかしUsersTableSeederなどのような分かりやすい規則に従ったほうが良いでしょう。デフォルトとしてDatabaseSeederクラスが定義されています。このクラスからcallメソッドを使い他の初期値設定クラスを呼び出すことで、シーディングの順番をコントロールできます。
データベース:シーディング 5.8 Laravel

 シーディングはartisan db:seedコマンドを叩くだけでデータベースに初期データを挿入する仕組みで、テストデータの生成やマスタなどの初期データの生成のために使われます。この二つの生成はテスト環境用、本番環境用と明確に使いたい状況の異なる生成です。そのためシーダークラスを分けてつくり両立させます。大体DatabaseSeederクラスに本番環境用データSeederの呼び出しメソッド、謎のテストやデプロイ用らしきクラスにテストデータ生成用メソッドが含まれています。これを–class=[className]オプションで使い分けます。
 –class=[クラス名]オプションは便利なのですがクラス名を知らなければならないという障壁があります。laravel-artisan-seeder-selectorはその障壁すら取り除きます。
Asperhsu/laravel-artisan-seeder-selector: Artisan command for DB Seeder selector
 インストール方法はapp/Console/Commands以下にSeederSelector.php1ファイルをコピペするだけです。もはやスニペットですからそんなものです。そうするとartisan db:seed-selectコマンドが実行できるようになり、次の様にクラス名の代わりに番号で選択する画面が出せます。

 db:seedは手作業で打つことの多いコマンドでもありますからGUI的に使えるこういったコードはありがたいです。

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

【Laravel】【Vue】propsを使わずVue上で組んだページ部にLaravelのBladeから値を表示する

 LaravelからVueに値を渡す時によくやる手法は次です。

        <upload-image
            v-bind:dist-url="{{json_encode(route('hoge.update'))}}"
            v-bind:hoge="{{json_encode($hoge)}}"
            v-bind:fuga="{{json_encode($fuga)}}"
            ...
            ...
        ></upload-image>
<template>
  <div>
    {{ hoge }}
    {{ fuga }}
    ...
    ...
  </div>
</template>
export default {
  props: {
    distUrl: {type: String, default: ''},
    hoge: {type: String, required: true},
    fuga: {type: String, required: true},
    ...
    ...
  },

 引数をjson化してpropsとして渡し、Vue内のテンプレートに埋め込みます。やり方はシンプルなのですが情報が増えるにつれpropsの肥大化がひどいことになります。なんらかのオブジェクトにまとめるのも手ですが、それはそれでバリデーションが辛くなります。この問題の回答の一つがVueのslotの仕組みを使うことです。
スロット — Vue.js
 slotは指定された位置のHTMLコードをVueコンポーネントを呼び出す階層から決定するための仕組みです。そのため先ほどの例ならば次の様に書けます。

        <upload-image
            v-bind:dist-url="{{json_encode(route('hoge.update'))}}"
        >
          <div slot="slot-no-namae">
              {{ $hoge }}
              {{ $fuga }}
              ...
              ...
          </div>
        </upload-image>
<template>
  <div>
    <slot name="slot-no-namae"/>
  </div>
</template>
export default {
  props: {
    distUrl: {type: String, default: ''},
  },

 Blade上のHTMLエスケープエコーの短縮構文{{}}を実行してHTMLコードを生成。それのHTMLコードをslotに割り当てます。これにより、propsを介することなくPHP内部で扱っていた変数の値を持つHTMLコードをVue上で組んでwebページのテンプレート内に組み込めました。propした値を後から変更しない、Vue内の関数で参照する予定が無い、といった値は大体この方法に組み込むとスッキリしつつ問題のないコードを作るのに貢献してくれます。

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

【PHP】一度の組み込み関数呼び出しでvar_dump()を二回呼び出す

 多分たまたまC言語実装がそうなっているだけなのでしょうが、タイトル通りの現象が起きます。起きるのは次の通りArrayAccessを実装した際のemptyです。

 ArrayAccessの説明には次の様にあります。

ArrayAccess::offsetGet

(PHP 5, PHP 7)

ArrayAccess::offsetGetオフセットを取得する

説明

abstract public ArrayAccess::offsetGet
( mixed $offset
) : mixed

指定したオフセットの値を返します。

このメソッドは、オフセットが empty() かどうかを調べる際に実行されます。

ArrayAccess::offsetExists

(PHP 5, PHP 7)

ArrayAccess::offsetExistsオフセットが存在するかどうか

説明

abstract public ArrayAccess::offsetExists
( mixed $offset
) : bool

オフセットが存在するかどうかを返します。

このメソッドが実行されるのは、ArrayAccess
を実装したオブジェクト上で isset() あるいは
empty() を使用した場合です。

注意:

empty() を使用すると ArrayAccess::offsetGet()
がコールされ、ArrayAccess::offsetExists()
TRUE を返すかどうかで空かどうかを判断します。

 要するにemptyの真偽をArrayAccess::offsetExists && ArrayAccess::offsetGetという式で判断しています。このため最初の画像の様にArrayAccess::offsetExistsとArrayAccess::offsetGetにvar_dumpを仕込むとemptyと一度組み込み関数を使っただけで2か所のvar_dumpが走りました。
 ArrayAccess::offsetExists && ArrayAccess::offsetGetという表現から予想できるように短絡評価をしています。そもそもnullの場合はArrayAccess::offsetExistsのみを実行して終わりです。
 

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

【PHP】のパスワード関数

 高級言語というものは大体何かしらパッと使うための組み込みなりパッケージなり用意されているため、あーだこーだ悩んで車輪の再発明をするよりもググった方が早い話がよくあります。PHPのパスワードもその一つです。

PHP: パスワードのハッシュ – Manual

PHP: Password Hashing 関数 – Manual

 これは攻撃を通すことが困難なパスワード処理を呼び出す関数群です。PHPにはこういった組み込み関数がいくつもあります。(ドキュメントを作っている人達自体把握しきれていない節がありますが。PHP: Imagick::getImageArtifact – Manualには(No version information available, might only be in Git)とあります。mightには思わず突っ込みが走りますね。)
 この組み込み関数を使えば復号化、総当たりを始めとした攻撃への対策を素人考えで行ったり、玄人になるくらい勉強するよりずっと早く楽にセキュアなプログラムを作れます。
 Laravelのログインも最後にはこのパスワードハッシュ関連の関数を使ってパスワード生成、認証を行っています。

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

【Laravel】Laravelのレスポンスは暗黙の型変換が強い

 Laravelはコントローラが何を返そうがサーバがクライアントにレスポンスを返せる様にがんばります。暗黙の型変換を行うPHPの鑑ですね。
 Laravelはミドルウェア、ルートバインディングをなんやかんや通って、コントローラのメソッドを実行、返り値をレスポンスにします。
 肝心のレスポンスはvendor/laravel/framework/src/Illuminate/Routing/Router.phpのtoResponseメソッドで整形されます。

    /**
     * Static version of prepareResponse.
     *
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @param  mixed  $response
     * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     */
    public static function toResponse($request, $response)
    {
        if ($response instanceof Responsable) {
            $response = $response->toResponse($request);
        }

        if ($response instanceof PsrResponseInterface) {
            $response = (new HttpFoundationFactory)->createResponse($response);
        } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
            $response = new JsonResponse($response, 201);
        } elseif (! $response instanceof SymfonyResponse &&
                   ($response instanceof Arrayable ||
                    $response instanceof Jsonable ||
                    $response instanceof ArrayObject ||
                    $response instanceof JsonSerializable ||
                    is_array($response))) {
            $response = new JsonResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {
            $response = new Response($response);
        }

        if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
            $response->setNotModified();
        }

        return $response->prepare($request);
    }

 $responseの型を判定し、適したメソッドを呼ぶか、適したオブジェクトに変換してます。見るからにレスポンスにできるものはレスポンスにする、という貪欲さが見えます。とりあえず特長的なのは普通にhogehogeResponseという型で判定していない部分である次の条件文です。

        } elseif (! $response instanceof SymfonyResponse &&
                   ($response instanceof Arrayable ||
                    $response instanceof Jsonable ||
                    $response instanceof ArrayObject ||
                    $response instanceof JsonSerializable ||
                    is_array($response))) {
            $response = new JsonResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {

 とりあえずjsonにできるならなんでもいい、arrayでもいい、という条件文です。これのおかげでControllerの中でのアダプターを作る手間がいくらか省けます。

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

【PHP】「Trying to get property of non-object」エラーが発生する原因と対処法

前回に引き続き、PHP で発生したエラーの対処法についてです。
今回は「Trying to get property of non-object」というエラーです。

検索したところ、全く同じエラーにお悩みの方を発見。

エラー”Trying to get property of non-object”を解決したい|teratail
https://teratail.com/questions/76134

 

で、こちらの記事によると、エラーの原因は存在しないオブジェクトのプロパティを呼び出そうとしているためのようです。
もしくは、null の値を参照した時にも同様のエラーが発生数するとのこと。

そのため、このエラーの対処法としては、下記のあたりを調べてみるのが良いかと思います。

  1. 呼び出そうとしているプロパティ名が正しいか?
  2. オブジェクトがに値が存在するか?

2 については、isset()isnull() の関数で、使用しようとしているオブジェクトをチェックすることをおすすめします。

 

以上、「Trying to get property of non-object」エラーが発生する原因とその対処法についてでした。
タイプミスでプロパティ名を間違えるとかはうっかりやらかしそうなので、皆様も十分お気を付けください。

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

【Laravel】Eloquent自動生成ライブラリeloquent-model-generatorの紹介

krlove/eloquent-model-generator: Eloquent Model Generator
 LaravelのEloquentモデルをORM(オブジェクト関係マッピング)として自在動かすにはDB中の1テーブルに対して1モデルを定義、更にテーブル間の関係をメソッドに記述、とする必要があります。これはいつも同じことを記述する上、大量に必要となるため面倒です。eloquent-model-generatorはこれを解決します。
 eloquent-model-generatorはDB中の定義からEloquentモデルを生成するライブラリです。例によってcomposerでインストール、

composer require krlove/eloquent-model-generator --dev

 artisanから起動できます。基本は次です。

# モデル名を指定(テーブル名はモデル名の複数形)
php artisan krlove:generate:model User
# モデル名とテーブル名を指定
php artisan krlove:generate:model User --table-name=user

 これだけでリレーション、fillableをはじめとした様々なオプション付きのモデルが生成されます。
 テーブル名らをコピペして、コマンドを量産して叩くだけでも便利ですがconfigを使うとさらに便利になります。

<?php

return [
    'namespace' => 'App\Models', // namespaceの冠詞
    'base_class_name' => \Illuminate\Database\Eloquent\Model::class, // Modelの継承元
    'output_path' => '/full/path/to/output/directory', // Modelの出力先ディレクトリ
    'no_timestamps' => true, // created_at, updated_atを使わないか使うか
    'date_format' => 'U', // datetimeカラムからデータを取得する時のフォーマット。nullだとCarbon
    'connection' => 'other-connection', // 不明
    'backup' => true, // 元々あるモデルを壊さないか否か
];

 Eloquent共通の設定が出来ます。
 さらに、この記事を書いている時点(2019/06/10)のプルリクに全てのモデルを生成するコマンドがあります。
Create Command To Generate All Models by tprj29 · Pull Request #46 · krlove/eloquent-model-generator
 このプルリクにあるコマンドファイルをコ用いて、 artisan krlove:generate:all-modelsを実行すると、DB中のテーブルを全て読み取り、全てに対応するEloquentモデルが生成されます。
 さらにこれをartisan ide-helper:modelsartisan test-factory-helper:generateと組み合わせることで素のEloquentが更に充実します。

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

【PHP】「Error: Undefined offset: 2」が発生する原因と対処法

今回遭遇したPHPの「Error: Undefined offset: 2」というエラーとその対処法について。
初めてみたエラーだったので少し身構えましたが、そこまで難しいものではありませんでした。

なお、今回参考にさせていただいた記事はこちらから。

「Notice: Undefined offset」と表示されるとき|PHPプログラミングの教科書 [php1st.com]
https://php1st.com/569

 

上記の記事によると、まずこのエラーが発生した原因は「配列で存在しない位置のインデックスを参照しようとしたこと」らしいです。
例えば、要素が 2つしかない配列の、存在しない 3番目を参照しようとしたときですね。
コードを確認したところ、確かに空の配列から 2番目の要素を取得しようとしていました。
そのため、配列が空でないかをチェックするか、確実に配列に要素が入るようにするなどの対応を行えば解決できました。

 

以上、PHP で「Error: Undefined offset: 2」が発生した時の対処法でした。
ケアレスミスなので、気を付けたいです。

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

【Laravel】モデル結合ルートを使ってコントローラの記述を簡略化

 サーバサイドプログラムのリクエスト処理部は、あるURLに対するリクエストを受け取り、リクエストに応じたデータを探して、もしなければ404を返し、と処理を行う必要があります。これを素で書くと例えば次の様になります。

// ルーティングファイル
Route::post('user/{id}', 'UserController@update');


// コントローラファイル
class UserController {
    public function update(){
        $id = request()->id ?: abort(404);
        $user = User::findOrFail($id);
        
        // $userに関連するレコードを更新する処理
        
        return $user;
    }
}

 Laravelのモデル結合ルートはこの処理を簡略化します。使い方は次の通りの名前と型の指定です。

// ルーティングファイル
Route::post('user/{user}', 'UserController@update');


// コントローラファイル
class UserController {
    public function update(User $user){
        // $userに関連するレコードを更新する処理
        
        return $user;
    }
}

 この様にすると、例えばuser/1365とpostを投げた時、User::findOrFail(1365);と同等の動作が実行されupdate関数にはUser::findOrFail(1365);の返り値のUserインスタンスが渡されます。毎度同じ手順で処理を行う必要がなくなり、楽になります。
ルーティング 5.8 Laravel

 上記まではモデル結合ルートの基本的な使い方であり、Laravelのデフォルトです。これは主キーで見つけるfindと同等の動作を仕組みを用いていますが、実際には主キーでない何らかのカラムでwhereをかけてモデルを取得したいパターンがあります。そのような時には次の様にモデル内でルートのwhereに使うカラムの名前を指定します。

// vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
    /**
     * @inheritDoc
     */
    public function getRouteKeyName()
    {
        return 'モデル結合ルートで使いたいカラム名';
    }

 このgetRouteKeyName()は次の様にルーティング用のクエリ構築メソッドresolveRouteBindingty中で使われています。このメソッドで返されるModelインスタンスがモデル結合ルートによって渡されるModelのインスタンスになります。

// vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
    /**
     * Retrieve the model for a bound value.
     *
     * @param  mixed  $value
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public function resolveRouteBinding($value)
    {
        return $this->where($this->getRouteKeyName(), $value)->first();
    }

 getRouteKeyNameを書き換えることでwhere句の指定するカラム名を書き換えました。もっと深く掘り進めてresolveRouteBindingを書き換えることによって更に大きくモデル結合ルートを改造できます。例えば次です。

    public function resolveRouteBinding($value)
    {
        return $this->where($this->getRouteKeyName(), $value)
            ->where('status', '<>', config('const.status.freeze'))
            ->first();
    }

 凍結状態のレコードが選ばれた時404を返す様にします。ちょっとしたグローバルスコープのパターンです。

65

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