タグ「PHP」の 記事 5 件中 1 ~ 5 件を表示しています。

Laravel 学習中だがモデルに関して感じたことを書く。

xxxRaw メソッド

O/R マッパーは確かに便利なのだがちょっと凝ったことをしようとすると SQL だと簡単に書けるのに O/R マッパーでどういう風に表現するのか分からずに実装できないという問題がある。 CakePHP などは顕著な問題で特にアソシエーションなどを使用すると正しいはずなのに何故か動かないというコードになりがちだ。 しかも COUNT(*)SUM(*) を書きたいだけでも $query->func()->xxx() などという訳のわからない記法を要求される。 これが覚えられない。

Laravel だと DB::raw(), selectRaw()whereRaw(), orderByRaw() などの低レベルに書けるタイプのメソッドが用意されており、これを使うと対象部分だけ SQL 直で書くことができる。 単純な機構だがとても便利だ。 例えば以下のような感じだ:

/**
 * 年月ごとの記事数を返却する.
 *
 * @return \Illuminate\Http\JsonResponse
 */
public function countYearMonth(): JsonResponse
{
    $posts = Post::query()
        ->groupBy(['year', 'month'])
        ->orderByDesc('year')
        ->orderByDesc('month')
        ->get(['year', 'month', DB::raw('COUNT(id) AS count')]); // DB:raw() で SQL 直書き
    return Response::json($posts);
}

これは覚えやすい。 「何故既存の O/R マッパーは使いにくいのか」がよく分かっている人が設計した感じがする。

何もしないと取得結果がすべて string になってしまう

クエリを投げて DB からモデルに取得した結果が INTEGER カラムであったとしてもすべて string になってしまう。 これだと毎回変換しなければならず使いにくい。 これに関しては【Laravel】DBから取得した値を$castsで型変換する【Attribute Casting】に記載がある通り、モデルに型変換したいカラムを明示的に指定する。

class Post extends Model
{
    use SoftDeletes;

    // このように定義しておけば year, month, day, count は取得時に integer にキャストされる
    protected $casts = ['year' => 'integer', 'month' => 'integer', 'day' => 'integer', 'count' => 'integer'];
}

上記の例で、例えば countposts テーブル内に存在しないカラムだったとしても問題ないし、DB::raw('COUNT(id) AS count') で取った結果がちゃんと integer で返ってくる。 この辺りは便利なのか不便なのかちょっとよく分からなかった。

Composer の設定を同期してしまう

タイトルに PhpStorm と書いたが私は今 IntelliJ IDEA Ultimate を使用しているのでそちらで再現した問題である。 だが検索すると PhpStorm でも同様の問題に遭ったという投稿が見受けられるので同様と思われる。

PhpStorm (IntelliJ IDEA Ultimate) では PHP language level (PHP バージョン) を設定することができる。 これによって例えば PHP 7.1 などに設定して PHP 7.1 の文法 (nullable なタイプヒンティングなど) を使ってもエディタ上でエラーにならない。 だが、一旦 IDE を再起動してしまうと何故か元に戻ってしまい文法エラー表示されてしまう。 これは何なのだろう、と調べてみたがデフォルトで composer.json の設定と同期されてしまうからだった。

対処

以下のどちらかを実施する:

1. composer.json の言語レベルを見直す

composer.json の以下の箇所を使用したい PHP バージョンに書き換えればよい:

"require": {
    "php": ">=7.1",
}

2. composer.json と IDE 設定の同期を止める

これに関しては IntelliJ のサポートフォーラムに投稿があった のでこの通り実施する。

That looks like IDE is synchronising these settings with your composer.json. This happens on project opening or when that file change is detected. "Settings/Preferences | Languages & Frameworks | PHP | Composer" -- uncheck appropriate option there ("Synchronize IDE Settings with composer.json").

ということで Settings -> Preference -> Languages & Frameworks -> PHP -> Composer の「Synchronize IDE Settings with composer.json」という設定を外せばよい。

CakePHP 3 ではアソシエーションを設定し関連データを取得することが出来る。 CakePHP 2 以前もあった仕組みであるが hasMany 若しくは belongsToMany で関連付けたデータを contain にして where で絞り込もうとすると失敗する:

$this->find()
    ->contain(['Comments'])  // Post has many comments とする
    ->where(['Comments.type' => 1]);  // 指定できそうに見えるが失敗!!

この PostbelongsTo の場合は内部的に INNER JOIN 若しくは OUTER JOIN された SQL が発行されるので大丈夫なのだが hasMany 若しくは belongsToMany複数の SQL に分割して発行されるので、最初の SQL に Comments が存在せず、条件を指定してもそれが最初の SQL に対し指定されてしまい「存在しないカラムに対し条件を指定している」ことになり失敗してしまう:

# 内部的にこのように分けて発行されるので最初の SQL には Comments は存在しない!!
SELECT * FROM articles;
SELECT * FROM comments WHERE article_id IN (1, 2, 3, 4, 5);

これを避けるために contain に対し以下のようにクロージャを渡すことにより 2 つ目の SQL に対し条件を渡すことが出来る:

$query = $articles->find()->contain([
    'Comments' => function ($q) {
       return $q->where(['Comments.type' => 1]);
    }
]);

つまり contain している Entity のアソシエーションがどの関係であるかを思い浮かべて適切なコードを書かなければならない。 この手の O/R マッパーを使用する際はしばしば内部で吐かれる SQL を dump しながら挙動を確認しなければならないのが辛い所ではある。

普段は Vagrant 上に Composer をインストールしていた

昨今の PHP 開発では Composer は必須であるが私はいつも Vagrant 上の VM に Composer をインストールしており vagrant ssh した上で composer install / update するなどしていた。 それでも勿論良いのだが PhpStorm 上に Vagrant や Composer のメニューが有り PhpStorm から出ずに vagrant up から vagrant ssh していろいろやったり composer update したりが可能になっている。 このメニューを使用するには PhpStorm 側にローカルの Composer の実行パスを教える必要がある。 つまりローカルに Composer をインストールする必要があった。 そして Composer を使うには PHP が必要となるので PHP をインストールして Composer をインストールする手順を記す。

macOS の場合はほぼ Linux の場合と同じなのでやりやすいが Windows の場合は意外と困る。 あと PHP をインストールするのは Xampp を使うと楽なのだが余計な Apache や MariaDB はインストールしたくないので素の PHP をインストールすることとする。

PHP インストール

Windows 用の PHP バイナリをダウンロードするサイトがあるのでここからダウンロードする。 バージョンは使いたいものでいいと思うが、現時点での最新は PHP 7.1 なのでそれをダウンロード。 Non Thread Safe か Thread Safe のどちらがいいのかというところだが Apache を使う場合は Thread Safe にせよとの事らしい。 コマンドラインから使うだけの分にはどちらでも良さそうだ。

ダウンロードした zip を適当な位置に解凍する。 自分は C:\php71 にした。

その後コマンドラインから実行する為に PATH を通す。 よくある手順だがコントロールパネルの「システムの詳細設定」からの「環境変数」を押下。 自分だけに適用したい場合は「ユーザー環境変数」、ユーザ全員に適用したい場合は「システム環境変数」の PATH を選択する。 そこに先程 PHP をコピーしたディレクトリ C:\php71 を追記する。

php.ini

さて、用意した PHP にはまだ php.ini が無い。 だが元となる php.ini-development が置いてあるので、それをコピーして php.ini にリネームする。 ちなみに本番環境では php.ini-production を使うが、今回の場合は実行時にエラーメッセージが表示される development の方がいい。

そして php.ini の以下の場所のコメントアウトを外す (; を削除する):

; Windows だと ext ディレクトリに DLL が全て入っている
extension_dir = "ext"

; CakePHP 3 インストールに必要
extension=php_intl.dll
extension=php_mbstring.dll

; Composer インストールに必要
extension=php_openssl.dll

コマンドプロンプトを開き php -v などと叩き、正しく PHP バージョンが表示されることを確認する。

Composer

Composer のダウンロードページComposer-Setup.exe をダウンロードして実行する。 ここでローカル PHP のパスを聞かれるので、先程インストールしたものを教える。

コマンドプロンプトを開き composer と叩き正しく Composer のコマンドリストが表示されるのを確認する。

インストールが完了すると Composer が C:\ProgramData\ComposerSetup\bin\composer に入る (ProgramData は隠しフォルダなのでエクスプローラで普通にたどると見えないので注意)。 後は PhpStorm 側でこのパスを教えるなどすれば良い。

Vagrant

Vagrant が何かに関しては検索すれば幾らでも情報が出てくるのでここでは述べない。

まずは Vagrant をインストールする。 公式からダウンロードしてインストールすれば良い。 VirtualBox も入っていなければインストールしておく。

Ubuntu 16.04 LTS に関しては公式に box が提供されているのでそれを使用する:

vagrant init ubuntu/xenial64

これでカレントディレクトリに VagrantFile が出来るので、この中の以下の行のコメントアウトを外す:

" VM 上の 80 番ポートへのアクセスをホスト側の 8080 番ポートに変換する設定. 8080 が使用済なら適宜変更する
config.vm.network "forwarded_port", guest: 80, host: 8080

その後以下のコマンドを叩いて VM を起動する:

vagrant up --provider virtualbox

以下のコマンドで VM に SSH 接続する:

vagrant ssh

試しに Apache を導入し動作確認を行う:

sudo apt install apache2

http://localhost:8080/ にアクセスし、正しく Ubuntu の Apache テストページが表示されるのを確認する。

PHP 7.0

PHP 7.0 の環境を構築する。 Ubuntu 16.04 における PHP 7.0 関連の項目を検索するには以下のコマンドを叩く:

apt search php7.0

まとめて必要そうなのを入れてしまう:

sudo apt install libapache2-mod-php7.0 php7.0 php7.0-cli php7.0-intl php7.0-json php7.0-mbstring php7.0-sqlite3
/* sudo apt install php7.0-mysql */
/* sudo apt install php7.0-pgsql */

デフォルトの DocumentRoot が /var/www/html なのでそこに試しに PHP ファイルを置いてみる:

cd /var/www/html
sudo mv index.html index.html.old
sudo vi index.php

index.php の内容は以下とする:

<?php
phpinfo();

http://localhost:8080/ にアクセスし、正しく phpinfo が表示されるのを確認する。

DocumentRoot を /vagrant/xxx にする

デフォルトの DocumentRoot のままだとホスト側に /vagrant がマウントされる仕組みを活かすことが出来ないので変更する。 /vagrant/xxx だが xxx の部分は各自適当なプロジェクト名とする:

 sudo vi /etc/apache2/sites-available/000-default.conf

以下 DocumentRoot を編集しアクセス許可を与える:

DocumentRoot /vagrant/xxx
<Directory /vagrant/xxx>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
</Directory>

テスト用のページを用意しておく。 先程の phpinfo 再利用でよいだろう:

mkdir /vagrant/xxx
sudo mv /var/www/html/index.php /vagrant/xxx
sudo service apache2 restart

http://localhost:8080/ にアクセスし、正しく phpinfo が表示されるのを確認した上で DOCUMENT_ROOT/vagrant/xxx になっているのを確認する。

mod_rewrite 有効化

CakePHP 3 では mod_rewrite を使用しているが Ubuntu の Apache のデフォルトでは有効になっていないので以下で有効にしておく:

sudo a2enmod rewrite
sudo service apache2 restart

CakePHP 3 インストール

先程の index.php は不要なので消しておく:

rm index.php

CakePHP 3 のインストールに関しては公式のドキュメントが素晴らしいのでこれに従っておけば問題ない。 ただ composer create-projectzipunzip が必要なようなので以下で入れておく:

sudo apt install zip unzip

あと composer create-project で出来るプロジェクトがディレクトリに含まれているので (この例だと /vagrant/xxx のこと) /vagrant 直下で composer create-project するのがいい。

尚 DB ばデフォルトで MySQL を使用するように config/app.php に書かれているので例えば SQLite を使うつもりでそのまま http://localhost:8080 にアクセスしても「MySQL のドライバーが見つからない」といったエラーになってしまう。 これに関しては config/app.php を以下のように SQLite 用に直せば良い:

'Datasources' => [
    'default' => [
        'className' => 'Cake\Database\Connection',
        'driver' => 'Cake\Database\Driver\Sqlite',  // Sqlite にする
        'persistent' => false,
        'host' => 'localhost',
        'username' => '',  // 空にする
        'password' => '',  // 空にする
        'database' => 'xxx.sqlite',  // 適当な SQLite ファイル名を書く
    ... (省略) ...
    'test' => [
        'className' => 'Cake\Database\Connection',
        'driver' => 'Cake\Database\Driver\Sqlite',  // こっちも Sqlite にしないと駄目
        'persistent' => false,
        'host' => 'localhost',
        'username' => '',  // 空にする
        'password' => '',  // 空にする
        'database' => 'test.sqlite',  // 適当な SQLite ファイル名を書く
    ... (省略) ...

これで http://localhost:8080 にアクセスしカラフルな Get the Ovens Ready ページが表示されれば開発環境構築完了である。