- 投稿日:2019-11-29T21:06:20+09:00
【Laravel】独自のDB用クエリ関数を実装する
この記事でやりたいこと
こんな独自のクエリ関数を実装する手順です。
\DB::canConnection(); //(1) \Schema::getColumnDefinitions('user'); //(2) User::getColumnDefinitions(); //(3) \DB::table('user')->getColumnDefinitions(); //(4)はじめに
以下のような関数は、Laravelですでに用意されています。
\DB::statement('drop table users'); \Schema::hasTable('testTable');上記のような形式の、独自の関数を追加する方法です。
実装方法
それでは早速実装してみましょう。
なお、「この記事で書きたいこと」で書いた(1)~(4)の関数ですが、厄介なことに定義する場所は、それぞれ異なります。その点を考慮しておいてください。今回作成するコードのフォルダ構成は、以下のようになります。
ServiceProvider.php
Database/
├ MySqlConnection.php
├ Eloquent/
│ └ MySqlBuilder.php
├ Query/
│ ├ MySqlBuilder.php
│ ├ Grammars
│ │ └ MySqlGrammar.php
│ └ Processors
│ └ MySqlGrammar.php
└ Schema/
├ MySqlBuilder.php
└ Grammars
└ MySqlGrammar.php
Model/
└ CustomBuilderTrait.php※今回はMySQL前提で作成していきます。MySQL以外のデータベースを使用したい場合、複数のクラスを作成してください。
※今回の例では、以下の3つの関数を作成します。
- データベースに接続できるかを判定する関数「canConnection」
- データベースのバージョンを取得する関数「getVersion」
- 指定のテーブルの列の定義を一覧取得する関数「getColumnDefinitions」
実装
- ServiceProviderに、独自のMySqlConnectionを呼び出すための処理を追加します。
<?php namespace CustomBuilder; use Illuminate\Support\ServiceProvider; use Illuminate\Database\Connection; class CustomBuilderServiceProvider extends ServiceProvider { /** * Bootstrap the application services. * * @return void */ public function boot() { // 追加 Connection::resolverFor('mysql', function (...$parameters) { return new Database\MySqlConnection(...$parameters); }); } /** * Register the application services. * * @return void */ public function register() { // } }
- Database\MySqlConnectionを作成します。
この関数では、継承した「MySqlGrammar」などの定義、ならびに「\DB::XXXXXX」形式で呼び出す関数の定義を行います。<?php namespace CustomBuilder\Database; use CustomBuilder\Database\Query\Grammars\MySqlGrammar as QueryGrammar; use CustomBuilder\Database\Schema\Grammars\MySqlGrammar as SchemaGrammar; use CustomBuilder\Database\Schema\MySqlBuilder; use CustomBuilder\Database\Query\MySqlBuilder as QueryBuilder; use CustomBuilder\Database\Eloquent\MySqlBuilder as EloquentBuilder; use CustomBuilder\Database\Query\Processors\MySqlProcessor; use Illuminate\Database\MySqlConnection as BaseConnection; class MySqlConnection extends BaseConnection { /** * Get a schema builder instance for the connection. * * @return Builder */ public function getSchemaBuilder() { if (is_null($this->schemaGrammar)) { $this->useDefaultSchemaGrammar(); } return new MySqlBuilder($this); } /** * Get the default schema grammar instance. * * @return MySqlGrammar */ protected function getDefaultSchemaGrammar() { return $this->withTablePrefix(new SchemaGrammar); } /** * Get the default query grammar instance. * * @return QueryGrammar */ protected function getDefaultQueryGrammar() { return $this->withTablePrefix(new QueryGrammar); } /** * Get the default post processor instance. * * @return MySqlProcessor */ protected function getDefaultPostProcessor() { return new MySqlProcessor; } /** * Get a new query builder instance. * * @return \Illuminate\Database\Query\Builder */ public function query() { return new QueryBuilder( $this, $this->getQueryGrammar(), $this->getPostProcessor() ); } /** * Get a new eloquent query builder instance. * * @return \Illuminate\Database\Eloquent\Builder */ public function eloquentBuilder($query) { return new EloquentBuilder($query); } // \DB::XXXXXX()関数はここで記載 ------------------------------------------- public function canConnection() { try { $this->getSchemaBuilder()->getTableListing(); return true; } catch (\Exception $ex) { return false; } } /** * Get database version. * * @return void */ public function getVersion() { return $this->getSchemaBuilder()->getVersion(); } }
- Database\Schema\MySqlBuilderを作成します。
このクラスに、\Schema::XXXXXX()で呼び出す関数の定義を行います。<?php namespace CustomBuilder\Database\Schema; use Illuminate\Database\Schema\MySqlBuilder as BaseBuilder; class MySqlBuilder extends BaseBuilder { // \Schema::XXXXXX()関数の形式 ------------------------------------------- /** * Get database version. * * @return void */ public function getVersion() { $results = $this->connection->selectFromWriteConnection($this->grammar->compileGetVersion()); return $this->connection->getPostProcessor()->processGetVersion($results); } /** * Get the table listing * * @return array */ public function getTableListing() { $results = $this->connection->selectFromWriteConnection($this->grammar->compileGetTableListing()); return $this->connection->getPostProcessor()->processTableListing($results); } /** * Get column difinitions * * @return array */ public function getColumnDefinitions($table) { $baseTable = $table; $table = $this->connection->getTablePrefix().$table; $results = $this->connection->selectFromWriteConnection($this->grammar->compileColumnDefinitions($table)); return $this->connection->getPostProcessor()->processColumnDefinitions($baseTable, $results); } }
- Database\Schema\Grammars\MySqlGrammarを作成します。
このクラスで、主に独自クラスで実行するSQLを定義します。<?php namespace CustomBuilder\Database\Schema\Grammars; use Illuminate\Database\Schema\Grammars\MySqlGrammar as BaseGrammar; class MySqlGrammar extends BaseGrammar { /** * Compile the query to get version * * @return string */ public function compileGetVersion() { return "select version()"; } /** * Compile the query to show tables * * @return string */ public function compileGetTableListing() { return "show tables"; } /** * Compile the query to get column difinitions * * @return string */ public function compileColumnDefinitions($tableName) { return "show columns from {$this->wrapTable($tableName)}"; } }
- Database\Schema\Grammars\MySqlGrammarを作成します。
このクラスで、主に独自クラスで実行するSQLを定義します。<?php namespace CustomBuilder\Database\Schema\Grammars; use Illuminate\Database\Schema\Grammars\MySqlGrammar as BaseGrammar; class MySqlGrammar extends BaseGrammar { /** * Compile the query to get version * * @return string */ public function compileGetVersion() { return "select version()"; } /** * Compile the query to show tables * * @return string */ public function compileGetTableListing() { return "show tables"; } /** * Compile the query to get column difinitions * * @return string */ public function compileColumnDefinitions($tableName) { return "show columns from {$this->wrapTable($tableName)}"; } }
- Database\Query\Processors\MySqlProcessorを作成します。
このクラスで、SQLを実行して取得した結果を、配列やCollectionに加工するなどを行います。
また、このクラス内の関数によって、各データベース種類(MySQL、SQL Serverなど)によって列名などが異なるものを、すべて揃える目的があります。<?php namespace CustomBuilder\Database\Query\Processors; use Illuminate\Database\Query\Processors\MySqlProcessor as BaseMySqlProcessor; class MySqlProcessor extends BaseMySqlProcessor { /** * Process the results of a get version. * * @param array $results * @return array */ public function processGetVersion($results) { $versionArray = $this->versionAray($results); return $versionArray[0]; } protected function versionAray($results) { $version = collect(collect($results)->first())->first(); return explode('-', $version); } /** * Process the results of a table listing query. * * @param array $results * @return array */ public function processTableListing($results) { return array_map(function ($result) { return collect((object) $result)->first(); }, $results); } /** * Process the results of a Column Definitions query. * * @param array $results * @return array */ public function processColumnDefinitions($tableName, $results) { return collect($results)->map(function ($result) use ($tableName) { return [ 'table_name' => $tableName, 'column_name' => $result->Field, 'type' => $result->Type, 'nullable' => boolval($result->Null), 'virtual' => strtoupper($result->Extra) == 'VIRTUAL GENERATED', ]; })->toArray(); } }
- Database\Query\MySqlBuilderを作成します。
このクラスに、\DB::table('user')::XXXXXX()で呼び出す関数の定義を行います。<?php namespace CustomBuilder\Database\Query; use Illuminate\Database\Query\Builder as BaseBuilder; class MySqlBuilder extends BaseBuilder { // \DB::table('user')::XXXXXX()関数の形式 ------------------------------------------- /** * Get column difinitions * * @return array */ public function getColumnDefinitions() { $results = $this->connection->selectFromWriteConnection($this->grammar->compileColumnDefinitions($this->from)); return $this->connection->getPostProcessor()->processColumnDefinitions($this->from, $results); } }
- また、\Database\Query\Grammars\MySqlGrammarで、上記のDatabase\Query\MySqlBuilderで呼び出すGrammerを作成します。
(\Database\Schema\Grammars\MySqlGrammarとまとめられないかな?という思いもありつつ)<?php namespace CustomBuilder\Database\Query\Grammars; use Illuminate\Database\Query\Grammars\MySqlGrammar as BaseGrammar; class MySqlGrammar extends BaseGrammar { /** * Compile the query to get column difinitions * * @return string */ public function compileColumnDefinitions($tableName) { return "show columns from {$this->wrapTable($tableName)}"; } }
- Database\Eloquent\MySqlBuilderを作成します。
このクラスに、\User::XXXXXX()で呼び出す関数の定義を行います。<?php namespace CustomBuilder\Database\Eloquent; use Illuminate\Database\Eloquent\Builder as BaseBuilder; class MySqlBuilder extends BaseBuilder { // \User::XXXXXX()関数の形式 ------------------------------------------- /** * Get column difinitions * * @return array */ public function getColumnDefinitions() { $table = $this->model->getTable(); $connection = $this->query->connection; $results = $connection->selectFromWriteConnection($connection->getQueryGrammar()->compileColumnDefinitions($table)); return $connection->getPostProcessor()->processColumnDefinitions($table, $results); } }
- Model\CustomBuilderTraitを作成します。
このTraitでは、各Modelで独自のEloquent\Builder、Query\Builderを呼び出すために必要となります。<?php namespace CustomBuilder\Model; trait CustomBuilderTrait { /** * Get a new query builder instance for the connection. * * @return \Illuminate\Database\Query\Builder */ protected function newBaseQueryBuilder() { $connection = $this->getConnection(); return $connection->query(); } /** * Create a new Eloquent query builder for the model. * * @param \Illuminate\Database\Query\Builder $query * @return \Illuminate\Database\Eloquent\Builder|static */ public function newEloquentBuilder($query) { $connection = $this->getConnection(); return $connection->eloquentBuilder($query); } }独自関数を呼び出すModelで、このTraitをuseしてください。
<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use CustomBuilder\Model\CustomBuilderTrait; class User extends Authenticatable { use Notifiable; // 追加 use CustomBuilderTrait; // 略 }まとめ
以上です!
Helperクラス的な関数を作ってもいいですが、出来ればこのように、クエリビルダを拡張することを行いたいですよね。
その場合は是非、こちらのコードを使用してください!ちなみに今回のコードのGitHubはこちらです。
https://github.com/hirossyi73/custom-builder
- 投稿日:2019-11-29T16:27:28+09:00
cakephpのテンプレートにjavascript呼び出しのみのボタンを設置
test.ctp<?php echo $this->Html->link('clickme', 'javascript:hoge();void(0)', ['class' => 'fuga']); ?> // class無しの場合 <?php echo $this->Html->link('clickme', 'javascript:hoge();void(0)'); ?> <script> function hoge() { alert('HOGE'); } </script>
- 投稿日:2019-11-29T16:22:36+09:00
Laravel-Excelで複数行のヘッダーを無視したい
Laravel-Excelで複数行のヘッダーを無視したい時
ドキュメントをみたけれど、書いてなかったので、Issueに書いてあったことを記録しておきます。
例えば、ヘッダー行(1行目)を飛ばすのはオプションでありそうですが、複数行目(例:6行目からデータ行が始まっているので、)飛ばしたい場合、いかのように書いてやればいいです。
WithStartRow
を使用します。use Maatwebsite\Excel\Concerns\WithStartRow; class UsersImport implements ToModel, WithStartRow { /** * @return int */ public function startRow(): int { return 6; } }
- 投稿日:2019-11-29T15:15:12+09:00
VSCodeでPHP+Vagrantのデバッグ環境を構築する
背景・動機
- デバッグ作業をする際にvar_dumpなどを使って出力していた。
- 場合によっては20個くらいvar_dumpするので、かなり非効率。
- マスターのソースコードに消し忘れたvar_dumpが混入している。
XDebug使えば?と言われて使ってみたらかなり便利で世界が変わったので、共有したいと思い投稿しました。
前提
- Vagrantで起動されたPHP環境がある。
- XDebugはインストール済み。
- VSCodeにPHP Debugプラグインがインストール済み。
手順
- Vagrantのphp.iniファイルでXDebugを有効化
- VSCodeのlaunch.jsonにデバッガの設定を追加
- Chrome拡張をインストール
- ブレークポイントをはる
- デバッグモードにする
- アプリケーションを起動
1. Vagrantのphp.iniファイルでXDebugを有効化
php.iniファイルに以下の設定を追記します。
追記後はservice httpd restart
しましょう。xdebug.remote_enable=1 xdebug.idekey="vscode" xdebug.remote_host=192.168.33.1 xdebug.remote_port=9000 xdebug.remote_autostart=1 xdebug.remote_connect_back=1 xdebug.remote_log=/tmp/xdebug.log2. VSCodeのlaunch.jsonにデバッガの設定を追加
VSCode左メニューのデバッグアイコンをクリックし、左上の歯車アイコンをクリックするとjsonファイルが開くので、以下のように追記します。
{ "version": "0.2.0", "configurations": [ { "name": "Listen for XDebug", "type": "php", "request": "launch", "port": 9000, "serverSourceRoot": "/path/to/app", "localSourceRoot": "${workspaceRoot}" } ] }3. Chrome拡張をインストール
※対象のアプリケーションがWebAPIなどの場合は、GETパラメータに
XDEBUG_SESSION_START=vscode
を追加することで、この手順を省略できます。4. ブレークポイントをはる
- VSCode上で任意の行番号の左をクリック。
5. デバッグモードにする
- 左メニューの虫アイコンをクリックし、デバッグ右の▶︎をクリック。
6. アプリケーションを起動
- アプリケーションを起動すると、ブレークポイントをはった行で実行が止まり、変数の中身が一覧されます。
デバッガ便利なので是非使ってみてください。
- 投稿日:2019-11-29T13:54:14+09:00
【Symfony】フィクチャロード(php app/console doctrine:fixture:load)でエラーが出た時の対処法
はじめに
作業していて発生したエラーの対処法についてまとめた備忘録となります。
開発環境
PHP:7.0
Symfony:2.8
CentOS6系(VirtualBoxで環境を構築)発生したエラー内容
$ php app/console doctrine:fixture:load [Swift_TransportException] Connection could not be established with host email-smtp.us-east-1.amazonaws.com [php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution #0]解決法
VirtualBox再起動
- 投稿日:2019-11-29T13:54:14+09:00
【Symfony】フィクスチャロード(php app/console doctrine:fixture:load)でエラーが出た時の対処法
はじめに
作業していて発生したエラーの対処法についてまとめた備忘録となります。
開発環境
PHP:7.0
Symfony:2.8
CentOS6系(VirtualBoxで環境を構築)発生したエラー内容
$ php app/console doctrine:fixture:load [Swift_TransportException] Connection could not be established with host email-smtp.us-east-1.amazonaws.com [php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution #0]解決法
VirtualBox再起動
- 投稿日:2019-11-29T13:10:39+09:00
PHPで簡易的なフレームワークを作ってみた
簡易的なPHPフレームワークを作ってみた
簡易的なフレームワークを実際手を動かして作ることで多少なりとも、
現在使っているフレームワーク(Laravel・自社フレームワーク)の理解の助けになればよいと思い作ってみました。リクエストの処理をindex.phpに集中させる
.htaccessですべてのリクエストに対してindex.phpが実行されるようにします。
https://sample.jp.localhost/blog/hogehoge
たとえば、上記のようなアクセスがあったら、以下のようなURLにリダイレクトされます。
https://sample.jp.localhost/index.php?url=blog/hogehoge.htaccess<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] </IfModule>index.phpはリクエスト時最初に実行されるPHPプログラムです。
.htaccess
でリダイレクトされることによってどのリクエストもまずはこのプログラムを通ります。/index.phprequire __DIR__ . '/action.php'; $app = new action(); $app->run();index.phpで読み込ませている
action.php
です。
ここでリクエストを判別してmodels
以下にあるclassを呼び出しています。/action.php<?php class action { public function run() { $GET = filter_input(INPUT_GET, 'url'); $params = explode('/', $GET); $fileName = array_shift($params); $filePath = './models/' . $fileName . '.php'; $className = $fileName . '_action'; if ($fileName && file_exists($filePath)) { require './models/' . $fileName . '.php'; $app = new $className(); extract($app->handle($params)); //viewに変数をアサイン require './views/' . $fileName . '.php'; } else if(!$fileName || !file_exists($filePath)){ require './models/index.php'; $app = new index_action(); extract($app->handle($params)); //viewに変数をアサイン require './views/index.php'; } } }models
modelsはGETパラメータが無い時や存在しないURLにリクエストがあったときは
index.php
が読み込まれ、GETパラメータに対応するclassが存在する場合はそれが読み込まれます。
例:https://sample.jp.localhost/blog/
上記のようなリクエストがあった時はblog_actionClass
が読み込まれる。/models/blog.phpclass blog_action{ function handle($params){ return array( 'title'=>'Hello World! (blog)', 'h1'=>'Hello World! (blog)' ); } }/models/index.phpclass index_action { function handle($params) { return array( 'title'=>'Hello World! (index)', 'h1'=>'Hello World! (index)' ); } }views
viewsは
models
の方で設定した配列のキーが変数として使用できます。/views/blog.php<html> <head> <meta charset="utf-8"> <title><?php echo $title; ?></title> </head> <body> <h1><?php echo $h1; ?></h1> </body> </html>まとめ
「PHP フレームワーク 自作」とかで検索して色んなサイト参考にさせて頂きました。
フレームワークが実際どういう原理で動いているのかいまいち理解できていなかったのですが、
手を動かすことによって多少は理解の助けになった気がします。普段の業務ではLaravelや自社製のフレームワークなんかを触る機会が多いですが、
フレームワーク機能を極限まで削るとこんな感じなのかなぁと思います。課題
classを読み込む時はrequireでなくて、autoloaderを使いたかったけどなぜか認識されず要調査。
GitHub
- 投稿日:2019-11-29T12:57:52+09:00
【PHP】PHPのデバッグで滅茶苦茶よく使う関数、”var_dump()”を紹介する記事
皆さんは、PHP開発をするうえで「あの関数、中身どうなってるんだ?」と疑問を呈した事は無いでしょうか?
いちいち中身を確認する為に、実行して結果が思ったものと違って、かといって原因はわからない…
そんな経験もあるものかと思います。そんな状態を解決してくれるのが、この[var_dump]です。これは、いわゆるデバッグに使う関数で、使うとその便利さに驚くはずです。
ここでは便利なvar_dump関数について、使い方&仕様を解説します!var_dump()の動きを解説
まず下記のテストコードを打ち込みます。
var_dump_testCord.php<?php $a = 1; $b = "test"; $c = ""; $d = 1.234567890; $e = ["apple" => 3,"banana" => "sweet","orange" => 7]; var_dump($a); var_dump($b); var_dump($c); var_dump($d); var_dump($e);すると出力は以下の通りになります。
//$a int(1) //$b string(4) "test" //$c string(0) "" //$d float(1.23456789) //$e array(3) { ["apple"]=> int(3) ["banana"]=> string(5) "sweet" ["orange"]=> int(7) }どうでしょう?
var_dump関数を用いる事で、変数の中身がただ出力されるのみならず、なんと内部でのプリミティブ型(0か1の型を示すbool値とか、文字列のstring値のような「変数の種類」を示すもの)付きで、さらに変数の中身が表示されるようになります。int型/float型の場合は、()の中に変数の中身が入ります。
string型の場合、()の中身は"半角/全角スペースを含めた文字列の文字数"が表示され、更にその後に変数の中身が表示されます。
array型の場合、()の中身は何とarrayの中身の数と、中のプリミティブ型とその具体的な仕様まで詳しく出てきます。おまけ:デバッグ向け関数には、print_r()という関数もある。
var_dump関数はデバッグで有用という事をお伝えしましたが、実は似たような用途の関数に。"print_r"関数というものもあります。
こちらは、いうなればvar_dump関数の簡易版ともいえる仕様です。プリミティブ型は表示させず、そのまま関数の中身を表示してくれるという仕様です。
試しに、上記のテストコードで書いたvar_dump()の部分を、全てprint_r()に書き換えてみましょう。
出力は以下の通りです。print_r123test1.23456789Array ( [apple] => 3 [banana] => sweet [orange] => 7 )簡易的ですね。情報量が少なくなっており、更にソースコード自体に工夫をせねば改行もありません。
var_dumpするまでも無いと思えるデバッグをするのなら、使ってみてもいいかもしれません。
- 投稿日:2019-11-29T12:39:18+09:00
入力フォームの実装で結構苦労した話
自己紹介
職業プログラマ歴約半年、PHP歴約半年、Laravelの存在は入社直前に知った、ひよっこプログラマです。Qiitaへの投稿はこれが初めてです。暖かい目で見てただければ幸いです。
背景
会社のサービスで使うフレームワークをLaravelにするということで、とある入力フォームの移植をすることになった。「項目数も少ないしそんなに時間かからないだろう」と思いながらやっていたところ、意外と厄介なことに気づいて、最終的に色々な手法を組み合わせて無理やり仕上げることになった。
この記事はそのダイジェスト版である。(実際のところもっと苦戦したり頓珍漢な実装になりかけたりしている)仕様
- DB
- tableX:ユーザーアカウントと一対一関係にあるが、存在しないことがある
- 入力ページ
- ユーザーアカウントでログインしている時のみアクセスできる = アクセスされる時、アクセス者に対応するユーザーアカウントが必ず存在している
- ユーザーアカウントに紐づくtableXの情報が存在していれば規定値が入力されたフォームを表示する
- ユーザーアカウントに紐づくtableXの情報が存在しなければ空のフォームを表示する
- 確認ページに向かうボタンがある
- 確認ページから戻ってきた際入力値が保持されている
- 確認ページ
- 入力ページのフォームの内容が表示される
- 入力ページに戻るボタン・内容を確定するボタンがある
<input type="hidden">
で内容を保持するのは禁止(改竄対策などの為)どう実装したか
tableXの情報を取得する
まずは以下のような感じに書いた。(ルーティングやミドルウェアについては省略)
App\User.php// ... public function tableX(): HasOne { return $this->hasOne('App\TableX', 'userno') } // ...App\Http\Controllers\SomeformController.phpClass SomeformController extends Controller { public function edit() { $tableX = Auth::user()->tableX(); return view('someform.edit', ['tableX' => $tableX]); } }resources\views\someform\edit.blade.php{{-- ... --}} <form method="..." action="..."> @csrf <div> <h4>項目A</h4> <input name="item_a" value="{{ $tableX->item_a }}" /> </div> {{-- ... --}} <input type="submit" name="confirm" value="確認する" /> </form> {{-- ... --}}このように実装した結果……
ErrorException (E_ERROR) Trying to get property 'item_a' of non-object無事
原因は上にも書いた…が、存在しないこともある
である。次のように書き換えて解消した。resources\views\someform\edit.blade.php{{-- ... --}} <input name="item_a" value="{{ $tableX->item_a ?? '' }}"/> {{-- ... --}}な時
$tableX
はnull
で、通常の箇所で$tableX->item_a
と書くとTrying to get property 'b' of non-object
と出てくるところだが、null合体演算子??
の左辺に書いた場合はisset
の時のように上手く処理してくれるようだった。確認画面に入力値を表示させる
かなり苦戦した覚えがあるが、最終的に次のように書いたところ動作するようになった。
App\Http\Controllers\SomeformController.php// ... public function confirm(Request $request) { $request->flash(); return view('someform.confirm'); } // ...resources\views\someform\confirm.blade.php<div> <div> <h4>項目A</h4> <div>{{ old('item_a') }}</div> </div> {{-- ... --}} <form method="..." action="..."> @csrf <input type="submit" name="submit" value="保存"/> </form> <form method="..." action="..."> <input type="submit" name="back" value="戻る"> </form> </div>
old()
と$request->flash()
の使い方を何となく理解できたようなできていないような……。確認画面から戻った時に入力値がクリアされないようにする
ここまでのコードだと、戻るボタンを押した時に入力値がリセットされてしまう
ここも最適解を見つけるまでに結構な時間が掛かったような覚えがあるが、最終的に次のように書くことで解決できた。resources\views\someform\edit.blade.php{{-- ... --}} <input name="item_a" value="{{ old('item_a', $tableX->item_a ?? '') }}"/> {{-- ... --}}保存処理を実装する
ここまで書いてしまえばあとは保存処理を実装するだけ、楽勝!
App\Http\Controllers\SomeformController.php// ... public function submit() { $tableX = Auth::user()->tableX; $tableX->fill(old())->save(); return redirect('...'); } // ...Symfony \ Component \ Debug \ Exception \ FatalThrowableError (E_ERROR) Call to a member function fill() on null
原因は最初にも出てきた
…が、存在しないこともある
だった。
最終的に次のように書いて解決した。App\Http\Controllers\SomeformController.php// ... public function submit() { $tableX = TableX::firstOrNew(['userno' => Auth::user()->id]); $tableX->fill(old())->save(); return redirect('...'); } // ...「
TableX
テーブルとUser
テーブルは関連付いているからUser
モデルのオブジェクトからTableX
オブジェクトを……」という考えに固執していると無意味に複雑なコードになってしまう。(実際なってしまっていた。)おわりに
本文中にもある通り、完成までにかなり苦戦しました。完成に至るまでに無意味に複雑で長いコードになって、「Laravelは厄介だ」と思うこともありました。しかし記事に起こしていると、
firstOrNew
関数など、煩雑な記述を解消する為のツールがLaravelによって既に用意されていることも分かってきました。そういったツールを使いこなせるよう、これからも勉強していきたい次第です。ここまでお読みくださり、ありがとうございました。
- 投稿日:2019-11-29T11:45:48+09:00
Amazon Linux 2 + nginx + php-fpm + PostgreSQL10
nginx
インストール
# sudo amazon-linux-extras install nginx1.12 -y
# nginx -v
起動と自動起動
# sudo systemctl start nginx.service
# sudo systemctl enable nginx.service
PostgreSQL10
インストール
# amazon-linux-extras install postgresql10
# sudo yum install postgresql-server postgresql-libs postgresql-contrib -y
この順でインストールするとpostgresql-serverもバージョン10が入る初期化
initdb -U postgres --local=ja_JP.UTF-8 -E UTF-8 -D /var/lib/pgsql/data
php-fpm
インストール
# sudo amazon-linux-extras install php7.3 -y
# php-fpm -v
関連パッケージのインストール
# sudo yum install php-mbstring -y
# sudo yum install php-pgsql -y
起動と自動起動
# sudo systemctl start php-fpm.service
# sudo systemctl enable php-fpm.service
config編集
/etc/php-fpm.d/www.confuser = nginx group = nginx
- 投稿日:2019-11-29T11:05:27+09:00
Laravel Mixでwebpackを簡単に利用する」ってどういうこと?
はじめに
業務上活用しているLaravel Mixにてコンパイル関連で色々と模索していおり、
今更ですがLaravel Mixについて整理したことを備忘録としてまとめました。まず、Laravel Mix公式を見てみると
以下公式サイトhttps://readouble.com/laravel/5.5/ja/mix.html より引用
Laravel Mixは多くの一般的なCSSとJavaScriptのプリプロセッサを使用し、Laravelアプリケーションために、構築過程をWebpackでスラスラと定義できるAPIを提供しています。シンプルなメソッドチェーンを使用しているため、アセットパイプラインを流暢に定義できます。例を見てください。
mix.js('resources/assets/js/app.js', 'public/js') .sass('resources/assets/sass/app.scss', 'public/css');Webpackやアセットのコンパイルを始めようとして、混乱と圧倒を感じているならLaravel Mixを気に入ってもらえるでしょう。しかし、アプリケーションの開発時に必要だというわけではありません。どんなアセットパイプラインツールを使用してもかまいませんし、使わなくても良いのです。
なるほど、わからん。カタカナ語が使いたい年頃の人が書いた文書のようにになっていますが、ざっくり言うと「webpackというモジュールバンドラー(モジュールの束)を簡単に使えるようにしたラッパー」ということだと後になって理解しました。
以下でもうちょっとだけ詳しく見ていきます。
概要
①Laravel Mixとは、フロントエンドのアセット(JS,SASS等)をコンパイル、バンドルしてくれるツール
②webpack設定ファイルをより分かりやすく簡単に書けるように設定ファイルをラップしている
③lessやsass、babelなどよく使われるローダーが最初から用意されていて、デフォルトで利用することができる
⑤Laravelを使っていないアプリでも、コンパイル・バインディングのツールとして利用できる使い方
Laravelをインストールした段階で、package.jsonとwebpack.mix.jsが用意されています。
・package.json
⇒Laravel Mix本体やその他必要なパッケージが記述済み。
package.jsonのscriptsにはwebpackを実行するためのスクリプトも記述されている。・webpack.mix.js
⇒webpack設定ファイル(webpack.config.js)のラッパー。
ここにコンパイル対象ファイルやバンドル対象ファイルなどの設定を記述していく。※LaravelではないアプリでLaravel Mixを使う場合でも、
package.jsonとwebpack.mix.jsの2ファイルを用意するだけであとは同じです。
Laravel Mix公式サイトのStand-Alone Projectを参考に。↓
https://laravel-mix.com/docs/5.0/installation設定ファイルの記述
webpack.mix.jsに、設定を記述する。
sassファイルをコンパイルしたい場合
mix.sass('resources/assets/sass/app.scss', 'public/css');cssファイルをバンドルしたい場合
mix.styles([ 'public/css/vendor/normalize.css', 'public/css/vendor/videojs.css' ], 'public/css/all.css');jsをコンパイルしたい場合
mix.js('resources/assets/js/app.js', 'public/js');jsファイルをバンドルしたい場合
mix.scripts([ 'public/js/admin.js', 'public/js/dashboard.js' ], 'public/js/all.js');他にもいろいろ設定できることはある。
Laravel Mixの実行
npm run dev を実行すると、package.jsonに書いてあるスクリプトが実行され、
設定ファイルに記述したコンパイル、バンドルが実行される。npm run production の場合は圧縮されたファイルが出力される。
知っておくと良い機能抜粋
もう少し具体的な機能について、知っておいたほうがいい主な機能を抜粋。
jsのコンパイル
上記の通り、jsファイルをコンパイルするにはmix.js('resources/assets/js/app.js', 'public/js');
と書く。この処理では
・ES2015記法
・モジュール
・.vueファイルのコンパイル
・開発環境向けに圧縮
のコンパイルが実行される。①babel実行
jsファイルをバンドルする設定として、mix.scripts()を紹介したが、
この代わりにmix.babel()を使うことができる。これをすると、バンドルされたファイルはES5記法に変換された状態になる。
IEはES5までしか対応していないため、もしIEでも動作させる必要があるシステムの場合は
このmix.babel()機能を使ってES5記法に変換するほうが良い。②キャッシュバスティング
mix.js('resources/assets/js/app.js', 'public/js') .version();このように記述すると、
ファイル名の末尾に一意のハッシュ値が付与される。これによって、コンパイルのたびにファイル名が変更されるため、CSSやJSなどがブラウザキャッシュに残って変更が反映されないことを防止できる。
このファイルを読み込むbladeファイル側では、
のようにmix()関数を使うことで、ハッシュ値のついたファイル名でも取得することができる。おわりに
Laravel Mixにはオプションの指定で挙動を簡単に変えられるという特徴もあります。
そのあたりのカスタマイズについてのメモがたまってきたのでまた、整理していこうと思います。とりあえず、CSS側のコンパイルが極端に遅い方は
"processCssUrls: false"を試してみましょう。
(画像PASSの書き換えオプションをOFFにする。デフォルトはON。)例)
mix.sass('src/app.scss', 'dist/') .options({ processCssUrls: false });詳細はまた後ほどほどまとめます!
参考
https://readouble.com/laravel/5.5/ja/mix.html
https://laravel-mix.com/docs/5.0/quick-webpack-configuration
- 投稿日:2019-11-29T09:38:15+09:00
PHPUnitを並列実行してできたJUnit XMLファイルとカバレッジファイルをマージする方法
グレンジでサーバー・インフラを担当している村田です。
グレンジ Advent Calendar 2019の1日目の記事です。
グレンジでPHPUnitを使ってテストを行っていますが、
テストが増えるにつれてテストに時間がかかることが問題になりました。テストをテストファイル単位やディレクトリ単位などで分け、PHPUnitを並列実行すればそれだけ早く終わらせることができます。
しかし、その結果ファイルのマージはPHPUnit単体では不可能です。テスト結果のマージする方法をまとめた記事になります。
前提条件
JUnit XMLファイルとカバレッジファイルを.build/junitと.build/phpcovに出力していることを前提としています。
% cd /home/username/src/tests/ % phpunit a_test.php \ --log-junit=/home/username/src/.build/junit/a.xml \ --coverage-php=/home/username/src/.build/phpcov/a.cov % phpunit b_test.php \ --log-junit=/home/username/src/.build/junit/b.xml \ --coverage-php=/home/username/src/.build/phpcov/b.cov% tree /home/username/src/.build /home/username/src/.build ├── junit │ ├── a.xml │ └── b.xml └── phpcov ├── a.cov └── b.covJUnit XMLファイルのマージ
PHPUnit作者のSebastian Bergmannさんがmerge-phpunit-xml.phpという、JUnit XMLファイルをマージするスクリプトを公開しています。
https://gist.github.com/sebastianbergmann/4405658しかし、このコードは
- マージするとテスト結果が重複される
- ライブラリをrequireしていないのでこのままでは実行できない
という優しくない状態です。
なので、結果を重複しないように修正して、ライブラリはcomposerで取得できるように手を加えたGitHubリポジトリがこちら。
https://github.com/muratahiroshi/merge-phpunit-xmlそのDockerイメージも作成。
https://hub.docker.com/r/hiroshimurata/merge-phpunit-xml次のように実行すると.build/junitのJUnit XMLファイルたちがマージされて、junit_merge.xmlが出来上がります。
% docker run -v /home/username/src:/data \ hiroshimurata/merge-phpunit-xml \ /data/.build/junit \ /data/junit_merge.xmlカバレッジファイルをマージしてCloverとHTMLで書き出す
PHPのカバレッジファイルはphpcovでマージできます。これもSebastian氏作。
https://github.com/sebastianbergmann/phpcovPharでも配布されていますが、GCPのCloud Buildでさくっと使えるようにこれもDockerイメージ作成。
https://hub.docker.com/r/hiroshimurata/phpcovただし、Dockerで実行するときは要注意。
phpcovのマージ処理はソースコードを参照するので、Dockerから参照できるようマウントしてあげます。
また、Dockerにマウントするソースコードのパスは、PHPUnit実行時と同じパスにしないといけません。前提条件のパスに合わせて、Dockerのphpcovを実行する例。Clover形式のXMLやHTMLで書き出せたりします。
% docker run -v /home/username/src:/home/username/src hiroshimurata/phpcov:6.0.0 \ merge /home/username/src/.build/phpcov/ \ --clover /home/username/src/.build/clover.xml \ --html /home/username/src/.build/htmlできたindex.htmlに何一つ結果が表示されていないようなら、ソースコードの参照、パスの位置関係に失敗しています。
covファイルの中身を見るとパスを確認できるので、それに合わせると良いです。<?php $coverage = new SebastianBergmann\CodeCoverage\CodeCoverage; $coverage->setData(array ( '/home/username/src/app/library/A.php' => # このパス array (Cloud BuildでPHPUnitの実行からphpcovまでまとめて行うなら、パスの問題は生じないと思います。
まとめ
一時期、2時間近くかかっていた全テストも並列分散させて数十分に抑えることができました。
CIは手早く回せるだけ快適&緊急対応時の変な汗も減らせて快適。おまけ boxで作るPhar
Dockerのmerge-phpunit-xmlはboxを使ってPharを作成しています。
FROM composer:1.8.5 WORKDIR /tmp/merge-phpunit-xml ADD merge-phpunit-xml.php composer.json composer.lock box.json /tmp/merge-phpunit-xml/ RUN /usr/bin/composer install --no-dev ADD https://github.com/humbug/box/releases/download/3.7.0/box.phar /usr/bin/box RUN php /usr/bin/box compile FROM php:7.2.15 COPY --from=0 /tmp/merge-phpunit-xml/merge-phpunit-xml.phar /usr/bin/merge-phpunit-xml ENTRYPOINT ["/usr/bin/merge-phpunit-xml"]box.jsonを書いてコンパイルすれば、1ファイルにまとめられるのでスッキリ。
box.json{ "alias": "merge-phpunit-xml.phar", "files": [ "merge-phpunit-xml.php", "vendor/autoload.php" ], "main": "merge-phpunit-xml.php", "output": "merge-phpunit-xml.phar", "compression": "GZ" }とても重要なおまけ告知
本の執筆に参加しました。
ゲーム開発が変わる!GCPゲームインフラ実践ガイド (NextPublishing)
- 投稿日:2019-11-29T08:59:03+09:00
PHPでも競技プログラミングがしたい!
この記事はチームラボエンジニアリングアドベントカレンダー5日目の記事です。
昨日の記事は、@tsuruken さんによるSeleniumで業務のテストを自動化しようと試みた話でした。
テスト自動化、大切ですよね......!はじめに
今年からwebエンジニアとして働き始め、9月から案件でPHP使うようになりました。
そこでPHPにたくさん触れて慣れたいと思ったので、競技プログラミング(AtCoder)をPHPで始めてみました。
実際にやってみるとC++で書く人が多く、動的型付け言語、さらにPHPとなると圧倒的に少数派なのが現状です。
そこでPHPでも競技プログラミングが始めやすいように入力、例題についてまとめてみました。
PHPを使っている方が競技プログラミングを始めるきっかけになれば幸いです。入力について
いまだに入力に手こずっている雑魚なのでさっくりまとめてみました!これをコピペでいけるはず!
入力 1行のみの場合
入力例
1 2 3 4 5
こちらの入力をintの配列にしていきたいと思います。<?php // 標準入力(STDIN)から1行読み込み(fgets)をしてinputに代入します $input = fgets(STDIN); // $input = "1 2 3 4 5" // 取得した値を半角スペースで分解します(PHPのsplit的な関数) とりあえずこれで十分扱えます! $input_array = explode(" ", $input); // $input_array = [ "1" , "2" , "3" , "4" , "5"] // 厳密な比較をしたい時などキャストしておきたい時などはこちら、型変換はintval()より(int)を使う方が早いらしい $int_array = array_map( function($value) { return (int)$value; }, $input);入力 複数行の場合
入力例(2行だけ)
あいうえお
かきくけこfgets()は読み込むごとに1行ずつ読み込んでくれるので2行の場合はこんな感じに書くだけでうまく読み込んでくれます。
<?php // 標準入力からの入力値を変数に代入します $input1 = fgets(STDIN); // $input1 = "あいうえお" $input2 = fgets(STDIN); // $input2 = "かきくけこ"入力 N行で1行に複数の値を渡される時
https://atcoder.jp/contests/dp/tasks/dp_c
こういう問題ですね。これも扱えるようになればだいたい入力はいけます。入力例
$N $
$a_1 b_1 c_1$
$a_2 b_2 c_2$
...
$a_N b_N c_N$<?php $n=trim(fgets(STDIN)); for($i=0;$i<$n;++$i){ // fscanf()...フォーマットに基づいて入力を処理する fscanf(STDIN,"%d %d %d",$x,$y,$z); $a[$i]=$x; // aのみが入る配列 $b[$i]=$y; // bのみが入る配列 $c[$i]=$z; // cのみが入る配列 }問題を解いてみる
入力ができるようになったらこちらの問題を解いて入出力に慣れましょう!
AtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~これPHPで解いたらネタになるんじゃないかと思ったらすでにありました・・・
AtCoder に登録したら解くべき精選過去問 10 問を PHP で解いてみたいい記事でした、すっきり書かれていて勉強になります。
これをやれれば簡単なアルゴリズムの考え方はわかってくるかと思います。DPを解いてみよう
次は代表的なアルゴリズムの一つである動的計画法を解けるようになりましょう!
超絶初心者なので全然スマートな書き方ではないですが解答例も載せました、参考程度に・・・問題を解いてみる(動的計画法1)
問題はこちら。
https://atcoder.jp/contests/dp/tasks/dp_b
カエルが高さのある足場をジャンプしていき、もっとも疲れないルートを見つけるという問題です。
とても可愛いですね。
難しそうと思ったらこれよりも簡単な問題もあるのでこちらから解いてみるのもおすすめです。
早速PHPでの解答例を載せます。解答例
<?php $first_line = fgets(STDIN); $second_line = fgets(STDIN); $first_line_array = explode(" ", $first_line); // 足場の数の合計 $steps_amount = $first_line_array[0]; // ジャンプで飛べる足場の数 $skip_power = $first_line_array[1]; // 足場の高さの配列 $steps_heights = explode(" ", $second_line); // 足場ごとにたどり着くまでのコストの総和の最小値を入れていく配列 $dps = []; foreach (range(0, $steps_amount - 1) as $index) { // 最初にいた足場に辿り着くまでのコストは0 if ($index === 0) { $dps[0] = 0; continue; } // 一つ前の踏み台につくまでの総コスト $previous_total_cost = $dps[$index - 1]; // 一つ前の踏み台から飛ぶためのコスト $jump_cost = abs($steps_heights[$index] - $steps_heights[$index - 1]); // とりあえず一つ前の踏み台から飛んだ場合の総コストで初期化して、数を比較して最小値を入れていく $dps[$index] = $previous_total_cost + $jump_cost; // 足場を飛ばした場合のことを考える foreach (range(1, $skip_power) as $i) { if ($index >= $i) { $new_total_cost = abs($dps[$index - $i] + abs($steps_heights[$index] - $steps_heights[$index - $i])); // 足場を飛ばした時のコストの方が少ない場合は値を更新する if ($dps[$index] > $new_total_cost) { $dps[$index] = $new_total_cost; } } } } //最後の足場の総コストを出力 echo end($dps);問題を解いてみる(動的計画法2)
お次はこちら
https://atcoder.jp/contests/dp/tasks/dp_d
ナップザック問題と呼ばれる動的計画法の有名問題です。
ちょっととっつきづらいので他の方が書いた素晴らしい解説を載せておきます。
典型的な DP (動的計画法) のパターンを整理 Part 1 ~ ナップサック DP 編 ~PHPでの解答例を載せます。
解答例
<?php $first_line = fgets(STDIN); // 取得した入力値を半角スペースで分解します $first_line_array = explode(" ", $first_line); //複数の変数に値を一括代入する list($item_num, $weight_upper) = $first_line_array; for ($i = 0; $i < $item_num; ++$i) { fscanf(STDIN, "%d %d ", $x, $y); $weight[$i] = $x; $value[$i] = $y; } $dp = []; // 使うアイテムが0個だった場合から一つずつアイテムを増やして検討していく foreach (range(0, $item_num - 1) as $item_index) { $i_value = $value[$item_index]; $i_weight = $weight[$item_index]; // リュックが許容する重さが0だった場合から検討していき増やして検討していく foreach (range(1, $weight_upper) as $weight_index) { // $current_value = isset($dp[$item_index - 1][$weight_index]) ? $dp[$item_index - 1][$weight_index] : 0;と同じ意味です、便利ですね $current_value = $dp[$item_index - 1][$weight_index] ?? 0; if ($weight[$item_index] <= $weight_index) { $new_value = (($dp[$item_index - 1][$weight_index - $weight[$item_index]] ?? 0) + $i_value); $dp[$item_index][$weight_index] = $current_value >= $new_value ? $current_value : $new_value; } else { $dp[$item_index][$weight_index] = $current_value; }; } } echo $dp[(int)$item_num - 1][(int)$weight_upper];PHPで競技プログラミングをやってみて
連想配列のみで純粋な配列がなかったり、if(0 == "-")がtrueになったり、少しゆるいところもありますが、あんまり厳密な書き方をしなくてよくて、分かりやすい言語だと感じました。
モビルスーツの性能の違いが、戦力の決定的差ではないということを・・・教えてやる!
と昔の人も言っていたので、漸進的型付けなど学んで、もっとPHPを使いこなせるようになろうと思いました!
- 投稿日:2019-11-29T04:31:24+09:00
Laravel モデルファイル作成
マイグレーションファイルの作成後に、モデルファイルを作成する手順。
Modelとは
Laravelにおいて、データベースを操作する為の実行ファイル。データベースに関連づけて作成する。
artisanコマンドでモデルを作成
$ php artisan make:model モデル名
Laravelには命名規則があり、テーブル名(複数系)とモデル名(単数系)とする必要がある。
項目 名前 テーブル名 users モデル名 User このルールを守る事で、自動的にデータベースとモデル(実行ファイル)が紐づいてくれる。
実行結果
<?php namespace App; use Illuminate\Database\Eloquent\Model; class モデル名 extends Model { // }モデルが作成される場所
ディレクトリ名/app
appディレクトリ内に作成されるので確認
- 投稿日:2019-11-29T00:29:51+09:00
ComposerでLaravelの開発環境を構築しよう
Laravelとは
Laravelは、MVCのWebアプリケーション開発用の無料・オープンソースのPHPで書かれたWebアプリケーションフレームワークです。webフレームワークを使うことでwebサービスの開発スピードが上がったり、セキュリティの安全性を担保できます。日本では、様々な企業で使われています。
引用: ウィキペディア
公式ページ: http://laravel.jp/Composerとは
composerはPHPのパッケージ管理システムです。
https://getcomposer.org/
環境構築の手順
- Homebrewをインストール
- MySQLをインストール
- Composerをインストール
- Laravelのプロジェクトディレクトリを作成
1. Homebrewをインストール
HomebrewとはmacOS用パッケージマネージャーで、composerとmysqlをインストールをするために必要なのでインストールします。
Homebrew公式ページ: https://brew.sh/index_ja/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"2. MySQLをインストール
laravelはデータベースシステムを使うので、MySQLをインストールします。laravelはMySQL以外にもSQLiteやPostgreSQLなどを使うこともできます。
brewコマンドを使ってmysqlをインストールします。
brew install mysqlmysqlのサーバーを起動させます。
mysql.server start Starting MySQL . SUCCESS!mysqlサーバーに接続します。
mysql -u root3. Composerをインストール
brewコマンドでcomposerでインストールします。
brew install composer4. Laravelのプロジェクトディレクトリを作成
composerコマンドでlaravelのプロジェクトディレクトリを作成します。
composer create-project laravel/laravel your-project-name --prefer-dist作成したプロジェクトディレクトリに移動
cd your-project-namelaravelサーバーを起動
php artisan serve起動ができたら、 http://localhost:8000/ にアクセスしましょう。
参考