カテゴリーアーカイブ PHP

著者:杉浦

【PHP】namespaceとautoloadの概要と連携

 最近、独自のオートローダを持つPHPプログラムを触ることになったので大雑把にnamespaceとautoloadの概要と連携を紹介します。
 namespace(名前空間)は1ファイルでも有効な変数名、関数名、クラス名等の区分け用の仕組みです。次の画像の様にちょっとしたスクリプトでも機能します。

 大雑把には各定義に接頭辞がついている様なものです。よくIDEのジャンプがnamespaceを元に働きますが、namespaceはファイルの読み込みに直接の関係を持ちません。
 autoloadは大まかには

public function autoload(string $呼び出す対象の名前){
    // なんやかんや処理
    require $呼び出す対象の書かれたファイルのパス;
    // あるいは
    include $呼び出す対象の書かれたファイルのパス;
}

PHP: require – Manual
PHP: include – Manual
をいい感じに動かす機能です。autoloaderによって多数のPHPソースコードファイルの中から必要な分だけPHPソースコードファイルを読み込みます。
 namespaceとautoloadのそれぞれは互いに関係ありません。PSR-4で定められた規約に従ってnamespaceとautoloadは連携します。
PSR-4 autoloader (日本語訳) – Qiita
PSR-4: Autoloader – PHP-FIG
 PSR-4はnamespaceとファイルの配置を定義づける規約です。大雑把にいえばnamespaceの名前、階層をディレクトリ構造と一致させなさい、という規約です。
 新しめのフレームワーク(laravelなど)やオートローダ(composerなど)はこのPSR-4に従っているためnamespaceによってファイル読み込みをしているとも言えます。ファイルを読み込めない時はコーディングをしている人が謎namespaceを定義してしまった時が大半です。逆にPSR-4に従っていない古いオートローダはnamespaceが通じているといってソースコードが読み込めているとは限りません。そういった場合、使われているオートローダのドキュメントなりソースコードなりを読み込む必要があります。
 

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

【PHP】php-cs-fixerやPhpStormが最新のコーディング規約標準勧告PSR-12にもうすぐに対応

 PSR-12はPHPのコーディング規約のベースです。”PHPのコードを記述する時はとりあえずPSR-12に従い、加えてプロジェクト毎に状況に合わせて規約の拡張をする”という使い方が想定されています。PSR-12はPHP5.4時点で定められていたコーディング規約用標準勧告であるPSR-2に代わって定められたものです。現在のPHP最新バージョンは7.3であり更新が必要になりました。
 PSR-12に乗り換えたいのですが、まだまだ対応したLint等が少ないのが現状です。対応しなければ即座にコードがバグるわけでもなし、そんなものです。そんな中あっという間に対応したPHP_CodeSnifferに続いて、php-cs-fixerとPhpStormがPSR-12に対応しそうです。
 Release 3.5.0 · squizlabs/PHP_CodeSniffer
 php-cs-fixerは自動修正機能付きのPHP用Lintです。PSR-2やSymfonyの定めるコーディング規約セットに準じた自動修正をPHPのソースコードに適用できます。現在ver3.0で開発中です。
PSR-12 Support · Issue #4502 · FriendsOfPHP/PHP-CS-Fixer
 ある程度できておりcopmoser.jsonに

     "friendsofphp/php-cs-fixer": "3.0.x-dev",

と記述してcomposer updateすればPSR-12版を試せます。
 PhpStormはPHPを主に対象としたweb系言語のIDEです。こちらもコードのフォーマット機能があり、早期アクセスプログラム対象の2019.3verで既に実装されています。2019.1が5月、.2が8月なので順調にいけば来月辺りにリリースされ安定が担保されるでしょう。
 PhpStorm 2019.3 EAP #5 | PhpStorm Blog

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

LaravelのREST APIの話

素で書こうとするとそこそこ苦労するREST API周りの実装ですが、Laravelにはこのあたりに必要な機能が始めから入っているためかなり楽に作りこむことができます。

Laravelのコントローラーで文字列、もしくは配列をreturnするとjson_encode()しなくても勝手にjsonに変換してくれたり、ステータスコードを付加するのがかなり簡単だったりとほぼそのまま使える状態になっています。

また、URLとコントローラーのメソッドを対応させるルーティングについても、デフォルトでAPI用のルーティングファイル(routes/api.php)が別で用意されており、APIのルーティングだけ、Web部分のルーティングと分離して書くことができます。

api.phpに書かれたルートにアクセスするには

example.com/api/...

のように、urlにapiというディレクトリ名を挟むだけです。

ここまでは非常によくできていて、APIを作る開発者にとっては非常にありがたいのですが、個人的に難点を感じたのがREST API使用時の認証の部分。

簡易的な認証であれば、DBのユーザー情報テーブルに”api_token”というカラムを追加して、このカラムに対してユーザー登録時にapiトークンを自動生成するようにしておけばLaravelのデフォルトのメソッドを使用してapiトークンによる認証を行わせることができるようになっているのですが、このapi_tokenにあたるカラム名をカスタマイズしたりだとか、Twitterのように、トークン+セキュリティトークンの2つで認証させるようにカスタマイズしたい場合などにカスタマイズが簡単にできない点があり、独自に実装してしまった方が早い…という側面があります。

このあたりがもっと改良されてこればより実用的になると思うのですが…

かゆいところに手が届かない感があってつらいところです。

[Laravel]レスポンスをJSON形式で返す方法2つ – Qiita

基本的なルーティング – Laravel 日本語ドキュメント

API認証 – Laravel 日本語ドキュメント

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

【PHP】Union TypesのRFCが受理されそう

 Union Types v2の名でPHPにUnion Typesの導入が可決されそうです。日本時間の2019/10/28 18:00時点で46対3で可決有利。おそらくそのまま受理されるでしょう。
PHP: rfc:union_types_v2
php-rfcs/0000-union-types-v2.md at union-types · nikic/php-rfcs
 Union Typesは大雑把に言えば型の和を示す型です。Union Typesが実装された時、次のように全く異なる型のorを指定できます。例では返り値の型ですが引数など今まで型を使えていたどの部分でも使えます。

public function getNumber() :int|float { // 返り値はint型かfloat型である、という宣言
    return $this->number;
}

 TypeScriptを触るとよく見る記法です。TypeScript以外にも型を定義できる言語ではよく見るらしいです。PHPは暗黙の型変換もあり組み込み関数も何かと寛容なものが多いので細かく縛れるUnion Typesは役立ってくれるでしょう。

// PHP: str_replace - Manualから引用
// @see https://www.php.net/manual/ja/function.str-replace.php
// str_replaceの第一引数はarray, stringを使える。
<?php
// <body text='black'> となります
$bodytag = str_replace("%body%", "black", "<body text='%body%'>");

// Hll Wrld f PHP となります
$vowels = array("a", "e", "i", "o", "u", "A", "E", "I", "O", "U");
$onlyconsonants = str_replace($vowels, "", "Hello World of PHP");

// You should eat pizza, beer, and ice cream every day となります
$phrase  = "You should eat fruits, vegetables, and fiber every day.";
$healthy = array("fruits", "vegetables", "fiber");
$yummy   = array("pizza", "beer", "ice cream");

$newphrase = str_replace($healthy, $yummy, $phrase);

// 2 となります
$str = str_replace("ll", "", "good golly miss molly!", $count);
echo $count;
?>
  • この記事いいね! (1)
著者:杉浦

.envファイルの読み方色々

 .envは環境変数を定義するファイルとしてよく使われているファイル名です。この記事はいくつかの言語、フレームワークにおける.envの読み方の紹介をします。

今どきの環境は.envがあることを前提として作られている機能があります。例えば、PHPのフレームワークであるLaravelやJavaScript実行環境であるNode.jsです。

// Laravel
$version = env('VERSION');
// Node.js
const version = process.env.VERSION;

 dockerに至っては暗黙の裡に取り込んでしまいます

# laradockから引用
# .env
PHP_VERSION=7.2

# docker-compose.yml
### PHP Worker ############################################
    php-worker:
      build:
        context: ./php-worker
        args:
          - PHP_VERSION=${PHP_VERSION}

 AltJSをコンパイルする環境ならば特定の接頭辞の環境変数を定数をあてはめる形で実装されていることが多いです。

// Vue用開発ツールVue CLIならばVUE_APP_ほげほげ
const version = process.env.VUE_APP_VERSION
// Laravel用webpack拡張のLaravel MixならばMIX_ほげほげ
const version = process.env.MIX_VERSION;

 Pythonの様にdotenvというパッケージがある場合もあります(Rubyもそうらしい)。

dotenv_path = join(dirname(__file__), '.env') # .envへのパスを定義
load_dotenv(dotenv_path) # 環境変数に.env中の値を追加
version = environ.get("VERSION") # 環境変数から値を取得

 ちょっと手間ですがbashの中で読むこともできます。

script_dir=$(cd "$(dirname "${BASH_SOURCE:-$0}")" || exit; pwd)
cd "${script_dir}" || exit

eval "$(cat .env <(echo) <(declare -x))"
  • この記事いいね! (0)
著者:杉浦

【Laravel】6.xからの新しいデバッグ用関数ddd

 Laravelにはdump(), dd()の様なデバッグ用にリッチな画面出力関数が用意されています。dddはそれに加わる新たなデバッグ用関数です。
ヘルパ 6.0 Laravel#dd
Laravel Ignition Introduces the ddd() Helper – Laravel News
 Laravel6.xではエラーページ用の機能がLaravel/Ignitionになっています。dddはこのIgnitionに付属する機能です。Ignitionのエラーページは画像の様な白を基調とした画面で今までの黒画面とはガラッと変わりました。タブ毎にデータを分けることで肥大化したデバッグ用情報を読みやすくしています。

Ignition Is the New Error Page for Laravel – Laravel Newsから引用
 ddd関数は画面出力をこのエラーページ同様にリッチなものにします。

 ddd関数を使うことでリクエスト、ログイン情報、発行されたSQLなども合わせて見ることができます。

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

LaravelでIntervention/image で圧縮した画像をファイルに保存せずにバイナリで取得する方法

Laravelで画像ファイルの変換処理を行いたい場合、Intervention/image というComposerパッケージを使用すると非常に楽です。

詳しいセットアップ方法は、下記のサイトを参考にしてみてください。

さて、このIntervention/imageですが、使い方を解説している多くのサイトで、下記のように変換した画像を Intervention/image のsave()メソッドを使った保存方法を紹介しているところが多いです。

...
$image = Image::make(画像ファイルのパス); //画像をパスから取得
$image->resize(500, 500);

...

しかし今回僕が行いたかった処理は”元ファイルを変換して保存する”のではなく、”元画像を圧縮してサムネイル画像を作成してRestAPI経由でbase64でクライアントに投げる”ということをしたかったため、ファイルの保存は不要でした。

そこで色々調べたところ、 Intervention/image にファイルへ保存ではなく、バイナリストリームをそのまま吐き出すメソッドが存在していることを知りました。

Cannot save image intervention image. NotWritableException in Image.php line 138 -StackOverflow

$img = Image::make(画像ファイルのパス)->resize(500,500)->stream('jpg', 50)

このようにすると、$img変数に変換後の写真バイナリデータが吐き出されるため、あとはこのバイナリデータを

$img_base64 = base64_encode ( $img  );

のようにすると、base64に変換されたデータが入手できます。

これで、目的の動作をさせることがきました。

なお、Laravelでは

$image->save(画像の保存先パス);

すると権限の問題でエラーになってしまいます。

 NotWritableException in Hoge.php line 138: Can't write image data to path path/to/image.jpg 

Laravelで変換後のファイルを保存したい場合は、上の方法でバイナリデータを取り出した後、

Storage::put('保存先ファイルパス', $img);

のようにすれば、 Laravel上でも Intervention/image を使用して データを保存することができます。

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

Laravel Eloquent の”fillable”の設定がめんどくさいときは”guarded”が便利

PHPフレームワークのLaravelには、DBと接続してデータをやり取りする仕組みとして”Eloquent”が実装されています。

Eloquentを使用すると、 基本的なDB操作であれば始めからメソッドが用意されているため、Modelに自分でメソッドを書かなくても簡単にデータが取得、保存ができるためとても便利です。

ただし、上記の備え付けのメソッドを使用する際は、DBの設定(テーブル名や主キーなど)をModelに記述する必要があります。

この設定の一つに、どのテーブルへのアクセスを許可するか、という指定があります。

この指定を行っておくことで、本来データを入れてはいけないテーブルに対して、誤ってModelからアクセスしてしまうことを防ぐことができます。

必須の項目なので、何らかの指定は必要になります。

個人的によくサイトで見かける方法で、Model内のクラスのブロックの頭に$fillableという変数を置くものがあります。

class Hoge extends Model {
    ....
    protected $fillable = ['user_name','mail_address','password'];
    ....

fillableでは何を指定すればいいのかというと、”Laravel側から触ってもよいカラム”を指定します。

これでも動作としては問題がないのですが、カラム数の多いテーブルなどが多かったりすると、正直めんどくさくなってきます…

実はもう一つ、操作可能なカラムを指定する方法があります。

$guarded 変数を用いる方法です。

class Hoge extends Model {
    ....
    protected $guarded = ['id'];
    ....

違いとして、fillableが所謂ホワイトリスト方式なのに対し、 guarded はブラックリスト方式です。

つまり、 guarded を指定した場合は”アクセスしてはいけない”カラムだけ記述すればOKです。

アクセスしてはいけないカラムが、アクセス可能なカラムよりも数が少ない場合はこちらの方が楽ですね。

LaravelのModelでは、fillableかguardedのいずれかが指定されていればOKとなっていますので、うまく使い分けて記述量を減らしていきましょう。

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

【PHP】提案中のObject Initializerとオブジェクト初期化の小技

 PHPでObject Initializerという記法が現在提案されています。
 PHP: rfc:object-initializer
 [RFC] Object Initializer – Externals
 [VOTE] Object Initializer – Externals
 その名の通り、オブジェクトの初期化に関する記法です。具体的には次の様な記述をします。

<?php
 
class Customer
{
  public $id;
  public $name;
  private DateTimeImmutable $createdAt;
 
  public function __construct()
  {
    $this->createdAt = new DateTimeImmutable("now");
  }
}
 
class Car
{
  public int $yearOfProduction;
  public string $vin;
}

<?php
 
$customer = new Customer {
  id = 123,
  name = "John Doe",
};
 
$car = new Car {
  yearOfProduction = 2019,
  vin = "1FTFW1CVXAFD54385",
};
// <a href="https://wiki.php.net/rfc/object-initializer">PHP: rfc:object-initializer</a>から引用

 単なる代入によるpublicプロパティの初期化をコンストラクタ外で行おうとする方法です。これによりコンストラクタのコーディング、プロパティの代入忘れ、引数の順番間違いから解放されます。
 これだけだと良いやり方に見えますが、提案の賛成:反対は2019/10/08 17:30(JST)時点で3:17です。投票に理由をコメントする必要はないのでスレを読み取って想像するしかありませんが、コンストラクタと共存すると怪しかったり、__setを介する直接代入でない部分の扱いだったり、今のPHPでも手間をかければ似たようなことができたりで賛成者は少ないです。

 PHPの既にある仕様で多数のプロパティを初期化する方法は多々あります。フレームワークを利用するとさらに増えます。自分がよく使うのは連想配列を用いたパターンです。

<?php
class Pos {
    public function __construct($attributes)
    {
        $this->lat = $attributes['lat'];
        $this->lng = $attributes['lng'];
        $this->x = $attributes['x'] ?? null;
        $this->y = $attributes['y'] ?? null;
    }
}

new Pos([
    'lat' => 35.658577,
    'lng' => 139.745451
]);

 連想配列にまとめることで引数を抑え、連想配列であるため引数の意味を理解しやすい、というものです。このやり方にはIDEの自動補完がまるで働かないという点で問題がありますが、程よく手軽に使える点と他クラスのarray変換ですぐ結び付けられる点で使用しています。他にもstdClassを使ったパターンやインタフェースとDTO(DataTransfarObject)を使うやり方もあります。後者は大がかりなものを扱う時、重用できます。

<?php
class Pos {
    public function __construct($attributes)
    {
        $this->lat = $attributes->lat;
        $this->lng = $attributes->lng;
    }
}

$args = (object)[
    'lat' => 35.658577,
    'lng' => 139.745451
];
new Pos($args);
interface PosArgsContract {
    public $lat;
    public $lng;
}
class Pos {
    public function __construct(PosArgsContract $attributes)
    {
        $this->lat = $attributes->lat;
        $this->lng = $attributes->lng;
    }
}

 初期化とは少し違いますがLaravelのFormRequestとEloquentをつなぐやり方として次のワンライナーがあります。

public function create(UserCreateRequest $request) {
    $user = User::create($request->validated());
}

 validatedメソッドの返り値はバリデーションを通過したリクエストのボディ全ての連想配列です。リクエストのキーをテーブルのキーと一致させることによって上記の記述でブラウザのリクエストからデータベースへのSQL発行まで一気に繋げられます。しかもバリデーション通過済みなので安全な値です。

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

【Laravel】EloquentモデルのObserverの活用

 LaravelのEloquentにはObserverという仕組みがあります。ObserverはEloquentのイベント発生によって発火するメソッドの集まりです。
Eloquent:利用の開始 6.0 Laravel#オブザーバ
 Observerは次のようにartisanでボイラープレート的なモノを生成できます。

php artisan make:observer UserObserver --model=User

 以下の様に生成されたクラスをLaravelの起動時処理で登録することによってイベント監視が動作します。

<?php

namespace App\Providers;

use App\Observers\UserObserver;
use App\User;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    //省略
    /**
     * 全アプリケーションサービスの初期起動
     *
     * @return void
     */
    public function boot()
    {
        User::observe(UserObserver::class); // ここでモデル::observe(オブザーバクラス::class)が基本
        Post::observe([PostObserver::class, BBSObserver::class); // 複数でもOK
        foreach (['Post', 'Comment'] as $className) {// 共通のオブザーバを使うならこんな書き方もあり
            /* @var Eloquent $className */
            $className::observe(UpdateTalkedAtObserver::class);
        }
    }
}

 オブザーバの使い時は色々あります。例えば、ある集約全体における更新日時を記録する場合です。
 LaravelのEloquentにはタイムスタンプを自動記録する機能があり、あるレコードを更新した際にそのレコード内の特定のカラム(デフォはupdated_at)に日時を記録してくれます。これだけでも便利なのですが、これだけではシステムの要求を満たせない場合もあります。例えば、あるユーザの情報が複数テーブルに分割されており、いずれかの情報が更新された時にユーザ情報が更新されたとし、その更新日時を記録する必要がある、という場合です。より具体的に次のER図のテーブル構成があるとします。

 ブログへの投稿かコメントを行った時にユーザの最終発言日時を記録する、とします。このような時、次のObserverを作ると要求を満たせます。

class UpdateTalkedAtObserver
{
    public function created(Eloquent $postOrComment) // 宣言した型に依らず、登録したEloquentクラスのインスタンスが渡される
    {
        $postOrComment->user->talked_at = $postOrComment->created_at;
        $postOrComment->user->save();
    }

    public function updated(Eloquent $postOrComment) // 宣言した型に依らず、登録したEloquentクラスのインスタンスが渡される
    {
        $postOrComment->user->talked_at = $postOrComment->updated_at;
        $postOrComment->user->save();
    }
}

 他にもログ取り、検索テーブル用の同期、削除や復元の連鎖など使うと楽になる場面がいくらかあります。また単にモデルのコードが膨れ上がらないための分割としても使えます。

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