- 投稿日:2019-12-01T22:43:19+09:00
Laravelで超簡単に検索機能を作成するPackageを作成した
この記事はLaravel #2 Advent Calendar 2019の2日目になります。
検索機能?
Webアプリを作成していると、よく検索機能作成しますよね?
↓こんな検索画面です。
このくらいシンプルであれば、
ifやEloquentを使って条件を追加する
のも悪くないですが、検索項目が多くなってきたり、複雑になってくると、途端に可読性が悪くなったり、めんどくさくなったりすると思います。その為、Laravelの検索機能の作り方や、検索に特化したPackageなどを、ググったりしたのですが、簡単に検索を実装できるものがなかったので作ってみました。
作ったPackage
https://github.com/fusic/Seaaaaarch/
作るときに気にしたポイント
- 簡単に検索機能を実装できること(検索実装の為に処理をなるべく書きたくない)
- どんな状況にも柔軟に検索できること
- Laravelっぽいこと
Seaaaaarchで出来るようになること
- Searchableクラスを作成することで、検索条件を書く場所を別クラスに分けれる
- 検索条件にcallbackを受け付けることで、柔軟に検索できる
- 簡単な検索は設定を書くだけで検索を実装できる
- Filterクラスを作成することで、検索条件を共通化できる
基本的な使い方
インストール
composer require fusic/Seaaaaarchbladeの準備
index.blade.php等に検索用の画面を作成します。
ここは普通のbladeになります。
※名前と住所で検索することを想定index.blade.php<form action="{{ route('users.search') }}" method="post"> @csrf <div class='row'> <div class='col-md-6'> <div class='form-group'> <input name="name" /> </div> </div> <div class='col-md-6'> <div class='form-group'> <input name="address" /> </div> </div> </div> <div style="text-align: center;"> <button class='btn-default'>検索</button> </div> </form>Searchableの作成
Seaaaaarchをインストールすると、
Searchable
が作成できるようになります。
Searchableは検索の実態を記載するクラスになります。php artisan make:searchable UsersSearchシンプルな検索であれば、下記のような
設定だけで検索を実装
することが可能になります。UsersSearch.php<?php namespace App\Search; use Search\Searchable; class UserSearch extends Searchable { public function __construct() { $this->params = [ // ここに検索条件を記載します // 名前 と 住所を検索できるようにします 'name' => [ // typeにvalueを設定すると、入力値を完全一致で検索します。 'type' => 'value' ], 'address' => [ // typeにvalueを設定すると、入力値を部分一致で検索します。 'type' => 'like' ], ]; } }※検索用にvalue、like、in、callback等準備しています。
※Searchableの詳細な設定項目についてはSearchableの設定を参照してください。コントローラーに検索用アクションを作成
検索用に、上記で作成したformからPOSTを受け取るアクションを作成します。
QueryParser::parse に Searchableクラス のインスタンスを渡すことで、POST値から検索に必要な値を取り出します。
※routesの設定してください。UsersController.phppublic function search() { $query = QueryParser::parse(new UsersSearch()); return redirect()->route('users.index', $query); }検索の実行
SeaaaaachをインストールするとEloquentに
searchメソッド
を追加します。
search
に対してSearchableインスタンスを渡すことで、 Searchableに設定された検索条件で検索を行います。
search
メソッドの前後にEloquentの機能で条件やソートなどを追加することも可能です。UsersController.phppublic function index() { UserModel::search(new UserSearch())->where('example', 'test'); // or UserModel::where('example', 'test')->search(new UserSearch()) }柔軟な検索
Searchableに検索条件を書くことになりますが、完全一致や部分一致ではなく、
JoinやPHPの処理を挟む
等複雑な処理をすることもあると思います。
そういう状況では、Searchableのcallback
を利用します。callbackの引数に渡ってくる
$builder
でEloquentの機能を利用することが出来ます。UsersSearch.php<?php namespace App\Search; use Search\Searchable; class UserSearch extends Searchable { public function __construct() { $this->params = [ 'name' => [ // typeにcallbackを指定すると、methodに検索処理を書くことが出来ます。 'type' => 'callback', 'method' => function (Builder $builder, $key, $value) { // ここで条件を組み立てます $name = mb_convert_kana($value, 'Hc'); $builder->where('name', $name); } ] ]; } }処理の共通化
プロジェクト全体等、共通的に利用したい検索条件を作成する機能も
Seaaaaarch
は準備しています。
Filterクラスを作成することで共通化することが可能です。Filterの作成
Filterを作成することで、検索条件を作成することが可能です。
php artisan make:filter ExampleFilterApp\Search\Filter\ExampleFilter.php<?php namespace App\Search\Filter; use Illuminate\Database\Eloquent\Builder; use Search\Filter\Filter; use Search\Filter\FilterInterface; class ExampleFilter extends Filter implements FilterInterface { protected $defaultOptions = []; public function process(Builder $builder, $field, $value) { // ここに検索条件の処理を記載します // 例:20歳以上を検索条件にする $builder->where('age', '>=', 20); } }Filterの使い方
UsersSearch.php<?php namespace App\Search; use Search\Searchable; class UserSearch extends Searchable { public function __construct() { $this->params = [ 'age' => [ // この指定で、20歳以上の条件がかかるので検索条件の共通化をすることが可能です。 'type' => ExampleFilter::class ] ]; } }その他細かい利用方法
GitHubに記載してありますので確認してください。
https://github.com/fusic/Seaaaaarch/tree/master/docs/ja気に入ったらPull requestやstarよろしくお願いします
- 投稿日:2019-12-01T22:37:22+09:00
PHP の匿名関数に潜む罠
これは 豊田高専コンピュータ部 Advent Calendar 2019 | 3 日目の記事です.
はじめに
つい先日の 11 月 28 日,ついに PHP 7.4 がリリースされました.
プロパティの型ヒンティングが追加されるなど,目玉機能が多いアップデートになりました.
その中の一つに, 匿名関数の新書式 (アロー関数) があります.PHP 7.3 以前では,匿名関数を以下のように書いていました:
function ($args) use ($globals) { return doSomething($args, $globals); }PHP の匿名関数 (Closure) は,記述されたスコープを維持しないので, 外は別の世界 となります.
したがって,外のスコープの変数を使用するときはuse
を用いる必要がありました.PHP 7.4 からは,以下のように書くことができます:
fn($args) => doSomething($args, $globals)この 2 つは等価といえますが,後者では
use
を省略できます.実は,ここに大きな罠があったのです.
(PHP 7.4 で新しくできた罠ではありませんが,より意識しづらくなってしまいました.)なにが起きるのか
以下のようなコードを考えてみましょう.
$i = 0; call_user_func( fn() => echo ++$i . PHP_EOL, ); echo $i . PHP_EOL;匿名関数の中で
$i
をインクリメントしたあと出力し,その後グローバルスコープで$i
の値を出力するコードです.
一見すると,どちらでも1
が出力されるように思えてしまいます.このコードを実行したときの出力は以下のようになります:
1 0匿名関数内でインクリメントした
$i
の 値が維持されていない ことがわかります.どこが罠なのか
先ほどのコードを PHP 7.3 以前の書式に書き直してみましょう:
$i = 0; call_user_func( function () use ($i) { echo ++$i . PHP_EOL; }, ); echo $i . PHP_EOL;この書式にすると
use
で明示的に外のスコープの$i
が使われていると分かります.しかし,この
use
における宣言は単なる宣言ではないのです.
匿名関数に入るとき,外のスコープの変数は,仮引数と同じように 値がコピーされます .
PHP ではオブジェクト (クラスのインスタンス) や配列も既定で値渡しされます.
したがって,匿名関数内でインクリメントしたのはコピーに過ぎないので, 抜けた後には反映されません .どうすればいいのか
旧書式では,明示的に参照渡しをすることで解決できます:
- function () use ($i) { + function () use (&$i) {出力は以下のようになるはずです:
1 1しかし,この方法は 新書式には使えません .
なぜなら,use
を省略するようになったことで参照渡しの&
を明示的に宣言する場所がないからです.
(fn () use (&$i) => ...
とすることも今のところできません)なにが悪いのか
旧書式では
use
で宣言することで 仮引数の一つのような振る舞い をすることが一目瞭然でした.
しかし,新書式では明示的な宣言がないため, 他の言語 で行えるように外のスコープの変数も読み書きできるように思えてしまいます.
use
がないことでその違いに気づけず,罠にはまってしまうのです.まとめ
近年,セミコロンをはじめ,不要なものはできる限り省略することが増えているのではないかと,個人的に思います.
省略はコードを簡潔に保つうえで大切ですが,可読性を犠牲にしたことでこういった罠が増えることのないようにしていきたいですね. (これは余談ですが,セミコロンは付ける派です.)釣りのようなタイトルで長々と書いてしまいましたが,この罠にかかって小一時間費やすといった,私のような人が少しでも減ればいいなと思います.
- 投稿日:2019-12-01T22:33:02+09:00
[CakePHP]パスワードリマインダをつくったよ
はじめに
皆さんはパスワードを忘れたことはありますか?
私はあります。そんな時に便利(?)なのがパスワードを忘れた方はこちらってやつですね。
いわゆるパスワードリマインダとかパスワードリセットとかいうやつです。
今回はCakePHPを使用し、それを作ります。いろいろなパスワードリマインダの方法
管理者向けパスワードリセット機能
利用者は、パスワードを忘れた場合、管理者に問い合わせて対処してもらいます。
しかし、通常パスワードはハッシュ化されていて管理者さえもパスワードを知ることができないので、
通常はパスワードを再設定する方式が取られます。(今のパスワードがそのまま通知方式が採用されているならやばい)パスワード再発行の順序として、
- 問い合わせを受付、利用者の本人確認
- 管理者がパスワードをリセットして、利用者に仮パスワードを伝える
- 利用者は仮パスワードでログインして、直ちにパスワードを変更する
となります。
利用者向けパスワードリセット機能
こちらは利用者自らがパスワードをリセットします。
管理者向けパスワードリセット機能はすべてのアプリケーションが備えるべきですが、
利用者向けパスワードリセット機能はセキュリティ強度を下げる原因となるため、サイトの性質により、実装の是非を検討します。本人確認
通常にアカウントに登録済みのメールアドレスによって本人確認をおこないます。
また、二段階認証を用いることで、本人確認を強化することができます。しかし、メールは多くの場合平文で送信されることから盗聴のリスクはありますし、二段階認証で生成される
数字は6桁程度ですから、一般に強度不足です。パスワードの通知
パスワードの通知方法には、以下の4種類が考えられます。
①現在のパスワードとメールで送信する。
→現在のパスワードを知り得るということは、パスワードはハッシュ化されていないという不安に利用者に与える上、メールが盗聴された際のリスクが最も大きいため、最悪の方法です。
②推測困難なパスワード変更画面のURLで送付する。
→今回実装する方式です。
③仮パスワードを発行して、メールで送信する。
パスワードをメールで送るという点で、一見①の方法と変わりないと思われますが、仮パスワードが変更された段階で利用者にメールで通知することによって、不正利用を利用者が気づくことができます。
④パスワード変更画面に直接遷移する。
→本人確認のメールアドレスが入力後、メールで受信確認のトークンを送付し、アプリケーションでトークンを入力することで、パスワード変更画面へ遷移する方式です。この項目は、以下の書籍を参考にさせていただきました。
体系的に学ぶ 安全なWebアプリケーションの作り方パスワードリマインダの実装
使用するテーブル
Users Table
CREATE TABLE Users ( id int(11) unsigned NOT NULL AUTO_INCREMENT, name varchar(20) NOT NULL, email varchar(255) NOT NULL UNIQUE, password varchar(255) NOT NULL, )ハッシュ化された文字は通常60文字ですが、公式では今後アルゴリズムが変更になる可能性があるので、
255文字が適切だと書いてあります。PasswordReset.php<?php namespace App\Model\Entity; use Cake\ORM\Entity; use Cake\Auth\DefaultPasswordHasher; class PasswordReset extends Entity protected function _setPassword($password) { if (strlen($password) > 0) { return (new DefaultPasswordHasher)->hash($password); } } }CakePHPでパスワードをハッシュ化するときには、Entityクラスでセッター機能を使うことで実現できます。
これは、newEntityやpatchEntityなどで、エンティティにセットタイミングでバリデーションの後に発動します。ちなみに、DefaultPassWordHasherの内部では、passwordHashが呼ばれています。
DefaultPassWordHaser.phpclass DefaultPasswordHasher extends AbstractPasswordHasher { protected $_defaultConfig = [ 'hashType' => PASSWORD_DEFAULT, 'hashOptions' => [] ]; public function hash($password) { return password_hash( $password, $this->_config['hashType'], $this->_config['hashOptions'] ); }password_hashの2つ目の引数は、ハッシュ化に使うアルゴリズムの定数を指定します。
PASSWORD_DEFAULTを指定しておけば、大丈夫です。PASSWORD_DEFAULT - bcrypt アルゴリズムを使います (PHP 5.5.0 の時点でのデフォルトです)。 新しくてより強力なアルゴリズムが PHP に追加されれば、 この定数もそれにあわせて変わっていきます。 そのため、これを指定したときの結果の長さは、変わる可能性があります。 したがって、結果をデータベースに格納するときにはカラム幅を 60 文字以上にできるようなカラムを使うことをお勧めします (255 文字くらいが適切でしょう)。
https://www.php.net/manual/ja/function.password-hash.php3つ目の引数は、指定すると手動でソルトを設定することができます。が、非推奨です。
省略するとパスワードをハッシュするたびにランダムなソルトを自動生成します。UsersTable.phppublic function validationDefault(Validator $validator) { $validator ->notEmpty('password', 'パスワードが入力されていません。') ->minLength('password', 8, 'パスワードは8文字以上で入力してください。') ->alphaNumeric('password', 'パスワードには半角英数字のみ使用できます。') ->add('password', 'numberAndAlpha',[ 'rule' => function($data, $context) { $valid = preg_match('/\A(?=.*?[a-z])(?=.*?\d)[a-z\d]/i', $data); return $valid ? true : 'パスワードは英文字、数字それぞれ1文字以上含める必要があります。'; } ]) ->sameAs('password', 'password_check', '確認用のパスワードと一致しません。'); }バリデーションは、tableクラスで定義します。
エンティティのバリデーションは、patchEntity、newEntity,またはsaveが呼ばれた時に実行されます。
validationの詳しい解説はまた今度にも。PasswordResets Table
CREATE TABLE PasswordReset ( id int(11) unsigned NOT NULL AUTO_INCREMENT, email varchar(255) NOT NULL, selector varchar(255) NOT NULL, token varchar(255) NOT NULL, expire datetime NOT NULL )IDいらなくね?ってなるけどとりあえず規約には従っておきましょう。
PasswordReset.php<?php namespace App\Model\Entity; use Cake\ORM\Entity; use Cake\Auth\DefaultPasswordHasher; class PasswordReset extends Entity protected function _setToken($password) { if (strlen($password) > 0) { return (new DefaultPasswordHasher)->hash($password); } } }PasswordResetsTable.php変更点なし
パスワードを忘れた方へのページ
View
はじめにこれがないと始まらないですね。
viewはざっくりと会員に紐付いているメールを送らせるFormを作ります。forget.ctp<?php echo $this->Form->create(); echo $this->Form->control('email', ['type' => 'email']); echo $this->Form->end();Controller
UserControllerのforgerメソッドで処理をします。
UserController.php<?php namespace App\Controller; use App\Controller\AppController; use Cake\Mailer\Email; use Cake\ORM\TableRegistry; use Cake\Routing\Router; use Cake\Network\Exception\NotFoundException; class UserController extends AppController { public function forget() { if ($this->request->is('post')) { $email = $this->request->getData('email'); $users_table = TableRegistry::get('Users'); $users_table = $users_table->find() ->where(['email ' => $email]) ->first(); if ($user) { $password_resets_table = TableRegistry::get('PassWordResets'); $password_reset = $password_resets_table->find() ->where(['email' => $email]) ->first(); if ($password_reset){ // expireを更新するために、すでにテーブルに登録されていたら削除する $password_resets_table->delete($password_reset); } } else { // 未登録のメールアドレスの場合は、なにもせずに結果画面を表示 return $this->redirect('msg'); } $data['email'] = $email; $data['selector'] = bin2hex(random_bytes(8)); $data['token'] = random_bytes(32); $data['expire'] = date("Y-m-d H:i:s",strtotime("1 day")); $url = Router::url([ 'controller' => 'User', 'action' => 'reset', '?' => ['selector' => $data['selector'], 'token' =>bin2hex($data['token'])], ], true); $password_reset = $password_resets_table->newEntity($data); $password_resets_table->save($password_reset); $email = new Email('default'); $email->from(['me@example.com' => 'My Site']) ->to($email) ->subject('パスワード再発行のお知らせ') $email->emailFormat('text'); $email->template('templete'); $email->viewVars($url); return $this->redirect('msg'); } } public function msg() { } }postデータを受け取ったら、実際に会員データに登録されているメールアドレスか調べます。
$email = $this->request->getData('email'); $users_table = TableRegistry::get('Users'); $users_table = $users_table->find() ->where(['email ' => $email]) ->first();会員のメールアドレスではなかった場合、当然メールは送信しませんが、
必ずメール送信に成功した時と同じ画面を表示しましょう。} else { // 未登録のメールアドレスの場合は、なにもせずに結果画面を表示 return $this->redirect('msg'); }これは、存在しないメールアドレスですなどのエラーメッセージを出してしまうと、
第三者にメールアドレスが登録していないことがわかってしまうからですね。存在するメールアドレスなら、処理を続行します。
まず、すでにPassWordReset Tableに登録しているユーザーであるなら、これを削除します。
基本的に、パスワードの変更が完了した場合には、このテーブルの行は削除されますが、
新たにパスワード再発行依頼をしたときにこれを削除しないと、有効期限が上書きされません。if ($user) { $password_resets_table = TableRegistry::get('PassWordResets'); $password_reset = $password_resets_table->find() ->where(['email' => $email]) ->first(); if ($password_reset){ // expireを更新するために、すでにテーブルに登録されていたら削除する $password_resets_table->delete($password_reset); }次に、トークンと有効期限を発行して、URLを生成します。
トークンにはselectorとtokenの2種類ありますね。これらはどう使われるのでしょうか。$data['email'] = $email; $data['selector'] = bin2hex(random_bytes(8)); $data['token'] = random_bytes(32); $data['expire'] = date("Y-m-d H:i:s",strtotime("1 day")); $url = Router::url([ 'controller' => 'User', 'action' => 'reset', '?' => ['selector' => $data['selector'], 'token' =>bin2hex($data['token'])], ], true); $password_reset = $password_resets_table->newEntity($data); $password_resets_table->save($password_reset);URLを得るのは簡単です。Router::urlを使用します。
これは、controllerとactionを指定すると、自動でルーティングに基づいたURLを生成してくれます。
今回は、getパラメーターも指定したいので、'?'をキーに配列に加えましょう。
第2引数をtrueにすると、フルパスで生成することができます。。tokenは、さきほどEntityクラスで見たように、DefaultPasswordHasherによってハッシュ化されます。
もしも、トークンの情報を盗み見られてしまった場合、トークンがそのまま保存されていたら誰でもそのURLにアクセスできてしまうので、
パスワードを乗っ取られてしまいます。そのためトークンもパスワードと同じように、ハッシュ化して保存してあげる必要があるわけですね。
ハッシュ化した場合、そのトークンで直接データベースからデータをもってこれないので、
データベースから検索するようにselectorというトークンを使用するわけです。あとは生成したURLを添付したメールを送ってあげましょう。
CakePHPのEメールに関しては公式の情報を参照してください。
https://book.cakephp.org/3/ja/core-libraries/email.htmlメールを受け取ったユーザーがパスワード再設定ページへアクセス
ユーザーはこんな感じのメールを受け取るので、メール本文のURLへアクセスします。
パスワード再発行のリクエストを受け付けました。
下記リンクをクリックしていただき、パスワード再設定の登録をお願いいたします。
*リンクの有効期限は、24時間です。
ttps://example.com/reset/?selector=4b148a081b9be8f9&token=e63f46d098187d3c0298cca07450963e32dd90d9d70e3f414e3d9f120be567a6
本メールにもしお心当たりのない場合、
恐れ入りますが破棄して頂けるようお願いいたします。View
よくある、パスワードと確認用のパスワードを入力させるやつです。
reset.ctp<?php echo $this->Form->create(['url' => ['action' => 'reset_ok']]); echo $this->Form->control('password'); echo $this->Form->password('password_check'); echo $this->Form->end();ここはgetメソッドだけで入ってこられるようにしたいので、post先は他の場所を指定します。
Form->controlを使うと、カラム名がpasswordなら、勝手にパスワードフォームになってくれます。
確認用のパスワード入力フォームのnameには、UsersTableのバリデーションのSameAsの第2引数で渡した名前にします。
これですね。->sameAs('password', 'password_check', '確認用のパスワードと一致しません。');バリデーションは、テーブルにないカラム名でも、同じrequestに入っていた時に一緒に来てくれます。
Controller
UsersController.phpfunction reset() { if(!$this->request->isget()){ throw new NotFoundException(); } if (!$this->request->query('selector') || !$this->request->query('token')) { throw new NotFoundException(); } $password_resets_table = TableRegistry::get('PassWordResets'); $password_reset = $password_resets_table->find() ->where([ 'selector' => $this->request->query('selector'), 'expire >=' => date("Y-m-d H:i:s"), ]) ->first(); if (!$password_reset) { throw new NotFoundException(); } if (!password_verify(hex2bin($this->request->query('token')), $password_reset->token)){ throw new NotFoundException(); } $this->session->write('email', $password_reset->email); }まず、getメソッド以外はすべて突き返します。
NotFoundExceptionはその名の通り、404エラーを発生させます。selectorまたはtokenが取得できなかったときも同様です。
requestの取得に成功した場合、トークンをもとにデータベースからどのユーザーのリクエストなのかを特定します。
$password_resets_table = TableRegistry::get('PassWordResets'); $password_reset = $password_resets_table->find() ->where([ 'selector' => $this->request->query('selector'), 'expire >=' => date("Y-m-d H:i:s"), ]) ->first();tokenはハッシュ化しているため、こいつをもとにデータを持ってくることはできません。
(password_hashは、毎回自動生成されたソルトをもとにハッシュ化しているので、同じ文字列をハッシュ化しても同じ二度と同じハッシュは得ることはできません)
そのために、selectorも用意したのでしたよね。同時に有効期限が切れていないかどうかも調べます。
データを取得できなかったときもNotFoundを投げます。
(ところで有効期限切れのURL404でよかったんでしたっけ?よくわからんです。)tokenが正しいかどうかは、password_verifyを使って調べます。
if (!$password_reset) { throw new NotFoundException(); } if (!password_verify(hex2bin($this->request->query('token')), $password_reset->token)){ throw new NotFoundException(); } $this->session->write('email', $password_reset->email);getから送られてくるtokenは16進数化しているので、hex2binでバイナリ文字列にデコードする必要があることに注意しましょう。
password_verifyは第1引数にパスワードを、第2引数にハッシュ値をしていして、一致するとtrueを返します。っていうかpassword_verifyってソルトとかどうやって計算してるの?なんかよくわからんけどすごい。
tokenも一致したらOK。セッションでemailを保持します。
再設定のパスワード受け取り、登録
UsersController.phpfunction reset_ok() { if ($this->request->ispost()) { $email = $this->seeeion->read('email'); $this->session->delete(); if (!$email){ throw new NotFoundException(); } $users_table = TableRegistry::get('Users'); $user = $users_table->find() ->where(['email' => $email]) ->first(); $users_table->patchEntity($user, $this->request->getData()); if ($user->errors()) { return $this->redirect($this->referer()); } $password_resets_table = TableRegistry::get('PassWordResets'); $password_reset = $password_resets_table->find() ->where(['email' => $email]) ->first(); $password_resets_table->delete($password_reset); if ($users_table->save($user)) { $this->Flash->success(__('パスワードを変更しました。')); return $this->redirect(['action' => 'login']); } else { $this->Flash->error(__('パスワードの変更に失敗しました。'); return $this->redirect(['action' => 'login']); } } else { throw new NotFoundException(); } }postからパスワードを受け取って、セッションからemailを取得。
セッション切れなどでemailが取得できなかったら返してやりましょう。そしたらemailからパスワードを変更するユーザーを取得します。
パスワードのバリデーションですが、これはUserstableで定義済みなので、pacthEntityしたら全部やってくれます。
で、errorsメソッドを使ってバリデーションエラーが発生したか判定すればOK。// controllerのバリデーションはこれだけでOK! $users_table->patchEntity($user, $this->request->getData()); if ($user->errors()) { return $this->redirect($this->referer()); }エラーメッセージもForm->controlを使用していれば勝手に出してくれます。超便利。
バリデーションも通ったら、PasswordResetテーブルからもトークンの情報を消し去りましょう。
これで一度パスワードを変更したら同じURLではアクセスできなくなります。$password_resets_table = TableRegistry::get('PassWordResets'); $password_reset = $password_resets_table->find() ->where(['email' => $email]) ->first(); $password_resets_table->delete($password_reset);あとはtableのsaveメソッドでパスワードを変更したら完了です。
loginページに飛ばしてやればいいんじゃないでしょうか。
これで終わりです。お疲れさまでした。最後に
こちらのサイト(Youtube)を参考にさせていただきました。
How To Create A Forgotten Password System In PHP | Password Recovery By Email In PHP | PHP Tutorial
- 投稿日:2019-12-01T21:26:34+09:00
PHP基礎 Part1
概要
PHPの独学で学んだ内容をアウトプットしていく
PHPとは
HTMLを動的に表示させ、サーバー側で処理を実行することに適した言語(見る人や時間に応じてテキストが変化する)
HTMLで記述したWebページは静的なコンテンツとなる = 逐一運用しない限り、いつどこで誰が見ても同じ内容となるPHPの用途
主にWeb系サービスの開発で使用されるプログラミング言語
ブログ、SNS、ECサイト、お問合せフォーム、各種サービスのログイン画面..etcPHPの書き方
HTMLにPHPのコードを埋め込んで書くことができる。
他にも外部ファイルを呼び出す形式があるとのことだが、今回は省略。
尚、ファイル名は「〇〇.php」とするPHPの基礎文法と文字列の出力
PHPの文法の最低限満たす必要のあるルールは以下の2つ
1.開始タグと終了タグ
PHPは開始タグ「<?php」から始まり、終了タグ「?>」」で終える。2.コードの末尾にセミコロン「;」を記述
コードの末尾にセミコロン「;」を書かないとエラーになる
※可読性を考慮してタブ、半角スペースを入れると尚良これらを踏まえたものが以下のコードとなる
index.php<?php echo "Hello World"; //出力結果はHello World ?>echoは文字列を出力する際の命令文である
文字列を出力する場合は、ダブルクォーテーション「" "」かシングルクォーテーション「' '」で文字列を挟むこと計算結果の出力
簡単な計算結果(主に四則演算)についても、以下のように出力可能
index.php<?php echo 2 + 3; //出力結果は5 echo 5 - 2; //出力結果は3 echo 2 * 5; //出力結果は10 echo 6 / 3; //出力結果は2 echo 7 % 3; //出力結果は1(割り算の余り = 商) ?>但し、計算式を文字列で囲うと計算式そのものが文字列として出力される
index.php<?php $money = 100; echo "2 + 3"; //出力結果は"2 + 3" ?>次回
- データ型
- 変数
- その他
- 投稿日:2019-12-01T20:18:35+09:00
2019年phpカンファレンス① ~MVCとはなにか~
2019年phpカンファレンスでの『MVCとはなにか』の講演について、思ったことをまとめてみました。
講演資料
https://speakerdeck.com/tenjuu99/what-mvc-is?slide=2
この講演を聞いたきっかけ
この前プライベートでLaravelを触ってみました。
会社のシステムは違うフレームワークでやっているため、結構新鮮だったとともに「model」の考え方が全然違った。今までmodelは「いろんなところで使っている関数をまとめる場所」というイメージでした。
そのため、DB接続系の処理も書いていたが、Laravelでは書かない。みたいな記述を見つけて、「modelって何書けばいいんだろう、、、」と思っていました。そこで、modelの概念について勉強しようと思い、この講演を聞いてみました。
講演を聞いて
「modelってなに、、、?」
という疑問がより深まりました(笑)
なぜそうなったかは、これからゆっくり書いていきます。
内容(解釈をかなり入れております)
システム作ってよ!
大体のシステム開発はここから始まるかと思います。
「こういう不便なところがある」というニーズから、それを解決するために開発を行っていくのが通常です。
その問題を解決するためにエンジニアは、「処理で問題解決する」という手法と「動きで問題解決する」という二つの方法を取るのが一般的です。それらを解決するために実装する。それが実装されるのが、「controller」や「view」なのかなぁと思います。
受け取ったデータに関して、こういう風に処理して、こういうデータを格納する。これがcontrollerの役割。
ここを押したら、こうなるだろうなぁというユーザビリディを考え、思った通りに動くシステムにする。これがviewの役割。
じゃあ、modelの役割は、、、?
個人ごとの問題
同じシステムを使っていても、持っている課題は別のものになることは多いです。
ユーザAはαという点に課題を感じていて、ユーザBはβという課題を抱える。しかもそれをそれぞれ解決しないといけない。
このような場面では、controllerやviewを使い、個別で解決してあげる必要があります。
うまく表面をチューニングしてあげることができるのもシステム運用では大事なため、その役割を持っています。じゃあ、modelの役割は、、、?
問題の根本を見抜く
αという課題でも、βという課題でも、その根本にある課題は同じであることは多いです。
それを解決するのが、modelの役割かと思います。今まではテーブルⅠしか更新してなかったけど、ユーザの要望でテーブルⅡも一緒に更新するようにする。
こういうシステムの根本に関わることを解決するのが、modelの役割になります。
自分が考えたmodelのあり方とは
modelとは、「システム思想の反映」だと思いました。
このシステムはなぜ作られたのか、何のためにつくられたのか。が反映しているものにすべきではないかと思いました。
modelを見るとそのシステムがなぜ作られたのか、どういう風になっていくべきなのか。が伝わるのではないでしょうか?
ユーザの問題をちゃんとキャッチアップして、その根本を見ていく、そしてそれを反映させていく
という箇所がmodelの役割かなぁと感じました。modelってなに、、、?
はじめに何度も書いたのですが、改めて自分の関わっているシステムを考えた時、そのシステムのmodelのあり方って難しいなぁと思いました。
システムが社会にどういう影響を与えていて、どういう風に発展していくべきか。そういうことを考えながら、組んでいく必要がある部分で、システムそのものが表現されている部分だからこそ、もっともっと深く考えるべきだなぁと思いました。
最後に
https://speakerdeck.com/tenjuu99/what-mvc-is?slide=2
そう思って再度資料を見ていただければ、何となーく感じ方が変わるのかなぁ、、、
今回の講演で言いたかったことを10%も吸収できてない気がするけど、とりあえず思ったことを書いてみました。
これ違うよってことがあれば是非コメントください!
- 投稿日:2019-12-01T19:10:07+09:00
PHP カンファレンス 2019 登壇資料まとめ
PHP カンファレンス 2019
https://phpcon.php.gr.jp/2019/
タイムテーブル
https://fortee.jp/phpcon-2019/timetable
登壇資料
敬称略
Track 1
PHPの今とこれから2019
廣川 類
コミュニティアップデート powered by GMOインターネット
GMOインターネット株式会社
思想と理想の果てに -- クリーンアーキテクチャのWEBフレームワークを作ろう
成瀬 允宣
https://nrslib.com/phpcon-2019-proposal/
PHPUnit: Past, Present and Future
Sebastian Bergmann
オニギリペイのセキュリティ事故に学ぶ安全なサービスの構築法
徳丸浩
https://www.slideshare.net/ockeghem/phpconf2019Track 1 - LT
Laravel + Nuxt.js + FirebaseでDXと開発コストを意識したSPA開発
カンボ@沖縄
https://speakerdeck.com/bumptakayuki/laravel-plus-nuxt-dot-js-plus-firebasetedxtokai-fa-kosutowoyi-shi-sitaspakai-fa
SwooleとWebpで遅いページを高速化する
寺井 諒
PHPでgRPCってどこまでいけるの?
株式会社サイバーエージェント 白井 英
https://speakerdeck.com/sgeengineer/phpdegrpctute-dokomadeikerufalse
PHP-CS-FixerをIDEに取り込ませてPSRを強制する開発スタイル
sogaoh
https://gitpitch.com/sogaoh/myslide?p=20191201-phpcon2019-LT#/
レガシーコードでビジュアルリグレッションテストをやってみた
阿波連 智恵
https://speakerdeck.com/aharenchi/regasikodode-biziyuaruriguretusiyontesuto-woyatutemita
PHP on AWS Lambda!
清家史郎
https://slide.seike460.com/slides/phpcon2019#/
2年目エンジニアがスキルアップのためにPHPで競プロやってみた
瑞
設計文化のないチームに文化を広めたが冴えない一手で混沌を招いた話を聞いてほしい
しろぐちゆうま
https://speakerdeck.com/mashirou1234/she-ji-wen-hua-falsenaitimuniwen-hua-woguang-metagahu-enai-shou-dehun-dun-wozhao-itahua-wowen-itehosii
運用経験ばかりのメンバーと新規開発で初めてしっかりと設計から開発までを経験した話
大重智志
https://speakerdeck.com/ohshige/php-conference-japan-2019-lt
エンジニアがブログや登壇で、アウトプットするとどうなる?
うゐろう
https://www.nyamucoro.com/entry/2019/09/21/004626
余裕を生み出すコードレビュー
高野福晃
https://speakerdeck.com/fortkle/code-review-phpcon-2019
社内最長老のシステムにPHPUnitで立ち向かう方法
やなせ たかし
https://speakerdeck.com/penguin045/she-nei-zui-chang-lao-falsesisutemuniphpunitdeli-tixiang-kaufang-fa
開発合宿のススメ!
Hamee株式会社 田島 裕介Track 2
PHP における並列処理と非同期処理入門
めもり〜
https://speakerdeck.com/memory1994/php-niokerubing-lie-chu-li-tofei-tong-qi-chu-li-ru-men
徳丸先生による徳丸試験例題解説とPHP7初級書籍贈呈キャンペーンと市場動向
吉政忠志、ゲスト解説:徳丸浩先生
PHP 開発環境で使う Kubernetes
新原 雅司
https://speakerdeck.com/shin1x1/php-development-environment-on-kubernetes
20年前のMySQL、今のMySQL
yoku0825
Webサービスのトラブルの現場 ~ Webサービスの今と昔 ~
soudai
https://speakerdeck.com/soudai/web-server-is-dead
REST 6+4の制約
郡山昭仁
https://speakerdeck.com/koriym/rest-6-plus-4falsezhi-yueTrack 3
Chatworkのシステムから学ぶレガシーなPHPの限界とレガシーからの脱却
村上 俊介
https://speakerdeck.com/shmurakami/phpcon2019
PHPからgoへの移行で分かったこと
樋口雅拓
https://speakerdeck.com/mahiguch/phpkaragohefalseyi-xing-defen-katutakoto
「弁護士ドットコム」を作り続ける開発組織について
狩野 秀明
https://speakerdeck.com/bengo4com/about-bengo4com-development-organization
改善失敗から学ぶ、レガシープロダクトに立ち向かうチーム作り。
杉山 祐一
https://speakerdeck.com/oogfranz/gai-shan-shi-bai-sitexue-bu-regasipurodakutonili-tixiang-kautimuzuo-ri
プログラム未経験からたった3ヶ月で圧倒的な開発力を身につける ~ スクラッチ開発の重要性
前田直哉
脆弱性から学ぶWebセキュリティ
バーチー
https://speakerdeck.com/hypermkt/study-web-security-from-vulnerability2
このPHP QAツールがすごい!2019
sasezaki
https://www.slideshare.net/sasezaki/php-qa2019Track 4
MVCにおける「モデル」とはなにか
天重誠二
https://speakerdeck.com/tenjuu99/what-mvc-is
『グランブルーファンタジー』開発エンジニアの考え方-PHP7が『グラブル』にもたらしたブレイクスルーと考察-
小松 美穂
新しい概念のWAFが叶える、クラウドネイティブ時代のセキュリティ
EGセキュアソリューションズ株式会社 代表取締役 徳丸 浩(株式会社グレスアベイル 社外取締役)・株式会社グレスアベイル 取締役 CTO 根岸 寛徳
知見のない技術スタックをプロダクション導入するエンジニアの導入戦略
東口和暉
https://speakerdeck.com/hgsgtk/a-strategy-to-choice-no-knowledge-technology
Laravel × クリーンアーキテクチャ 開発中の現状をお伝えしたい
ブライソン イアン
https://speakerdeck.com/ianbrison/laravel-x-clean-architecture-xin-gui-kai-fa-zhong-falsexian-chang
15年続くWebサービスへのCI/CDとテストコードの導入
9bo9bo
https://speakerdeck.com/9bo9bo/cdtotesutokodofalsedao-ruTrack 5
PHP初心者セッション
柏岡秀男
PHPerのためのテストコード入門
02 / 大津 和槻
https://speakerdeck.com/cocoeyes02/phperfalsetamefalse-tesutokotoru-men
Composerって何?どう動くの?読んでみました!
きんじょうひでき
https://speakerdeck.com/o0h/lets-read-composer
PHPは何を捨て、どんな力を手に入れてきたのか
うさみけんた
Zend VMにおける例外の実装
hnw
https://www.slideshare.net/hnw/zend-vm
PHPを学ぶということ
岸田健一郎
https://speakerdeck.com/sizuhiko/phpcon-2019
Apache から LiteSpeed に乗り換えてみませんか?
高橋邦彦Track 6
Hash, Cryptography, and PHP
Peter
https://slides.com/peter279k/php-con-japan-2019/#/
Putting legacy to REST with middleware
Adam Culp
https://www.slideshare.net/adamculp/putting-legacy-to-rest-with-middleware
5ヶ月でカバレッジを20%から90%にした話
吉田あひる
https://speakerdeck.com/yahiru/5keyue-tekaharetusiwo20-percent-kara90-percent-niaketahua
「CPUとは何か」をPHPで考える
長谷川智希
https://speakerdeck.com/tomzoh/what-is-a-cpu
How to Supercharge your PHP Web API
Aurimas Niekis
- 投稿日:2019-12-01T16:32:33+09:00
Docker+Laravel+OpenAPIGenerator
はじめに
OpenAPIGeneratorを使って生成されたLaravelのソースコードで環境構築してみました。
DockerでLaravel環境の構築~スタブサーバの生成、リクエストの確認まで行います。プロジェクトディレクトリ構成
project/ ├ www/ # Laravel Project Container ├ generator/ # generator Container ├ docker-compose.yml ├ oas.ymlSample OAS
プロジェクト直下に
oas.yml
を用意します。
簡易的なCRUDが行えるAPIを想定しています。oas.ymlopenapi: 3.0.0 info: title: Task API version: 0.0.1 servers: - url: http://localhost description: Laravel Server tags: - name: Task paths: /api/task: get: tags: [ "Task" ] summary: Show Task List. description: Show Task List. operationId: listTask responses: '200': description: Successful response post: tags: [ "Task" ] summary: Create Task One operationId: createTask requestBody: content: application/x-www-form-urlencoded: schema: type: object properties: title: type: string sort: type: integer responses: '200': description: Successful response /api/task/{tid}: get: tags: [ "Task" ] summary: Show Task One. description: Show Task One. operationId: showTask parameters: - name: tid in: path required: true schema: type: string responses: '200': description: Successful response put: tags: [ "Task" ] summary: Update Task One. operationId: updateTask description: Update Task One. parameters: - name: tid in: path required: true schema: type: string requestBody: content: application/x-www-form-urlencoded: schema: type: object properties: title: type: string sort: type: integer responses: '200': description: Successful response delete: tags: [ "Task" ] summary: Delete Task One. operationId: deleteTask description: Delete Task One. parameters: - name: tid in: path required: true schema: type: string responses: '200': description: Successful responseDocker環境を用意する
docker-compose.yml
をproject直下に用意します。docker-compose.ymlversion: '3.4' services: generator: build: ./generator volumes: - .:/app tty: true command: sh www: build: ./www volumes: - ./www:/var/www ports: - 80:80
generator
コンテナはOASからPHPコードを生成するコンテナになります。
www
コンテナはApacheで動作するLaravel環境のコンテナになります。続いて、
generator
コンテナを構築するためのDockerfileをgenerator/
配下に用意します。FROM openjdk:8-jdk-alpine WORKDIR /app RUN apk --update add bash maven git RUN rm -rf /var/cache/apk/*ls RUN git clone https://github.com/openapitools/openapi-generator /generator RUN cd /generator && mvn clean packageOpenAPIGeneratorはJVMでありJava環境が必要なため
openjdk:8-jdk-alpine
をベースにしています。
ライブラリのインストーラとしてmaven
を使用します。続いて、
www
コンテナを構築するためのDockerfileをwww/
配下に用意します。FROM php:7.3-apache RUN apt-get update && apt-get install -y git libzip-dev libxml2-dev RUN docker-php-ext-configure zip --with-libzip RUN docker-php-ext-install pdo_mysql mbstring zip xml RUN curl -sS https://getcomposer.org/installer | php RUN mv composer.phar /usr/local/bin/composer RUN a2enmod rewrite && a2enmod headers RUN sed -ri -e 's!/var/www/html!/var/www/public!g' /etc/apache2/sites-available/*.conf RUN sed -ri -e 's!/var/www/!/var/www/public!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf RUN usermod -u 1000 www-data && groupmod -g 1000 www-data ENV COMPOSER_ALLOW_SUPERUSER 1 ENV COMPOSER_HOME /composer ENV PATH $PATH:/composer/vendor/bin WORKDIR /var/www RUN composer global require "laravel/installer"今回はApache上で動作するPHP(
php:7.3-apache
)をベースにしています。
Laravelを使いたいのでcomposer
をインストールしています。
URLを書き換えやリダイレクトを有効にするため、a2enmod rewrite && a2enmod headers
を実行しています。
sed
でLaravel用のドキュメントルートに変更しています。
Apacheを動かすwww-data
ユーザがLaravelから吐き出されるlogファイルにアクセスできるように権限を付与しています。上記の準備ができたらコンテナを起動します。
各リソースのDLやInstall処理が一気に走るため結構な時間を要するかと思います。$ docker-compose up -d --buildLaravel用のソースコードを自動生成する
OpenAPIGeneratorを使用してソースコードを生成します。
generator
コンテナに入って作業します。$ docker-compose exec generator bash bash-4.4# java -jar /generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate \ -i oas.yml \ -g php-laravel \ -o server-generate bash-4.4# cp -rf server-generate/lib/. www上記を実行すると、
server-generate
に自動生成されたPHPのソースコードが出来上がると思います。
生成されたソースコード一式はwww
コンテナへコピーします。
ちなみに、自動生成できるコードの種類はこちらから確認できます。生成されたソースコードからLaravel環境を立ち上げる
$ docker-compose exec www bash root@5295f267d6a1:/var/www# composer install root@5295f267d6a1:/var/www# cp .env.example .env root@5295f267d6a1:/var/www# php artisan key:generate上記の実行が済めば、http://localhost にアクセスして下記の画面を確認することができると思います。
続いて、生成されたコードが機能しているのか確認してみます。
php artisan route:list
を実行してAPIのエンドポイントを確認してみます。$ docker-compose exec www bash root@5295f267d6a1:/var/www# php artisan route:list +--------+----------+----------------+------+------------------------------------------------+------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+----------------+------+------------------------------------------------+------------+ | | GET|HEAD | / | | App\Http\Controllers\Controller@welcome | web | | | POST | api/task | | App\Http\Controllers\TaskController@createTask | api | | | GET|HEAD | api/task | | App\Http\Controllers\TaskController@listTask | api | | | DELETE | api/task/{tid} | | App\Http\Controllers\TaskController@deleteTask | api | | | GET|HEAD | api/task/{tid} | | App\Http\Controllers\TaskController@showTask | api | | | PUT | api/task/{tid} | | App\Http\Controllers\TaskController@updateTask | api | +--------+----------+----------------+------+------------------------------------------------+------------+エンドポイントの確認が行えたので、続いてswagger-editorからGETリクエストを試したいと思います。
ただ、現時点のままだとCORSに引っかかりリクエストできません。
そのため、リクエストのテストを行う前にCORS対策を行います。
まずはbarryvdh/laravel-cors
をインストールします。$ docker-compose exec www bash root@5295f267d6a1:/var/www# composer require barryvdh/laravel-cors
www\config\app.php
に下記を追加します。'providers' => [ // ... Barryvdh\Cors\ServiceProvider::class, ]
app/Http/Kernel.php
に下記を追加します。'api' => [ // ... \Barryvdh\Cors\HandleCors::class, ],設定ファイルを以下のコマンドで作成します。
root@02eec0e47db9:/var/www# php artisan vendor:publish --provider="Barryvdh\Cors\ServiceProvider"設定が完了したので、swagger-editorでリクエストテストを行ってみます。
無事にGETリクエスト出来たことを確認しました。以上、Laravel用のスタブサーバの生成および構築までの流れでした。
自動生成されたコードはスタブなため、実処理は開発者が実装していくことになります。
何かの参考になれば幸いです。
- 投稿日:2019-12-01T16:26:39+09:00
PHPカンファレンス2019 当日レポ
公式サイト
https://fortee.jp/phpcon-2019
今年はBeyond
がテーマになっています。PHPの今とこれから2019
資料: https://www.slideshare.net/hirokawa/presentations
- 歴代のスライド資料あげました
- PHP7.4の次は8
- 2021年の後半(9月?)にリリースかも
- PHP7.xは80%程度利用中(会場内アンケート)
- データではPHP7.xは4割程度
- PHP5.xは57%(昨年は75%)
- 今年出たバージョンは3年後にはEOL
- 7.1のEOLは今日(2019/12/01)
- CVEが付いている修正はセキュリティの修正なので速やかにバージョンアップしたほうがいい
- PHP7.4は7.3より15%程度ベンチマークが改善
- ※体感はあまり変わらないが
- PHP7.4
- TypeHintingの追加
- Null許容型
- OpCacheのPreロードで高速化
- アロー関数
- ??=構文
- mb_str_split関数
- .演算子の優先順位 +のほうが優先される
- 弱い参照
- 継承した型に対してWarningが出ないように
- PHP8
- JITエンジンを活用 →より速くなる
Chatworkのシステムから学ぶレガシーなPHPの限界とレガシーからの脱却
資料: https://speakerdeck.com/shmurakami/phpcon2019
- レガシーコードの定義
- 修正/拡張/作業が非常に難しいコード
- PHPとScalaを使ってる
- PHPはレガシー 巨大なモノリス
- Scalaは分散システム志向 HBase/DynamoDB/Kafka
- 3年半前にPHPからScalaへのゼロベース移行
- PHPの辛み
- オレオレフレームワーク ※遅くはないし悪くもない
- 静的解析ツールは
PhpMetrics
が動かせた- なぜこうなった?
- スピードを優先した結果
- ビジネスが拡大したタイミングで仕様を厳格化しなかった
- 全体的なScala化に失敗し、部分移行になった
- 何が問題なのか
- データベースの分割 クライアント1万問題
- データベースのスケールに限界がある
- マイクロサービスは辛い
- まずはモジュール分割から
- チーム間のコミュニケーションも辛い
- ホラクラシー組織を検討中!
- レガシーなPHPの限界は、関連ツールの限界がきたとき
- ビジネスのスケールに合わせて適切なタイミングでリファクタリングしよう
PHPからgoへの移行で分かったこと
資料: https://speakerdeck.com/mahiguch/phpkaragohefalseyi-xing-defen-katutakoto
- 移行前は ALB->EC2(fpm)->DBなど
- FuelPHPを使用(メンテナンスが少ない)
- 移行後は NLB経由でコンテナでGo
- PHPとGoのmemcacheのライブラリが異なるためHashが合わない
- memcacheへアクセスするだけのGatewayコンテナを作成
- PHPerしかいなかったけど10時間くらいでGo書けるレベルになった
- 記事推薦システムをPythonからGoへ移行
- 記事とユーザーをベクトル化してパーソナライズする
- PHPはプロセスを分けて並列化したが、Goは簡単にできる
- PHPは飽きるのでGoを書くとモチベーションが上がる!
- GopherはPHPの1/100くらいの感覚
- PHP/Pythonに戻すかも^^
- Chatworkさんみたいに5年も頑張れない、、。
思想と理想の果てに -- クリーンアーキテクチャのWEBフレームワークを作ろう
資料: https://nrslib.com/phpcon-2019-proposal/
- クリーンアーキテクチャはフレームワーク非依存
- この仕様をどこに実装したっけ?をなくそう
- ポートアンドアダプター
- ヘキサゴナルアーキテクチャ
- 空いているポートにアダプターを繋ぐイメージ
- DataAccessModuleに主導権を持たせず、BusinessLogicに持たせる
- クリーンアーキテクチャは内側が外側のことを知らない
- Entity
- ビジネスロジックをドメイン化したモジュール
- アプリケーションレイヤー
- ユースケースを提供する
- InterfaceAdapters
- コントローラ(コントローラ)、画面(プレゼンター)、ゲーム機(インタラクター)
- Framework&Devices
- ビジネスロジックに依存しない
- メリット
- 処理や出力を差し替えられる→画面なのかコンソールなのか変えられる
- モックの差し替え、例外を起こすビジネスロジックの差し替えでバグ検証
- 代償
- レスポンスがView(MVC)の場合にマッチしない
- めんどくさい
- Scafold機能欲しい
- PHP_Parser https://github.com/nikic/PHP-Parser
- まずはMVCを捨てる(!?)
- プレゼンターで、どうにかしてViewを描画する
- 戻り値をなくす Laravelだとミドルウェア機能で。
- プラグイン作りました
- https://github.com/nrslib/clarc-laravel-plugin
- プラグインだけどフレームワーク非依存なので組み込みが可能
知見のない技術スタックをプロダクション導入するエンジニアの導入戦略
資料: https://speakerdeck.com/hgsgtk/a-strategy-to-choice-no-knowledge-technology
- 技術選定の視点とは?
- 用件によって重視すべき点が変わる
- 運用実績がないものの方がマッチするケースがある
- 品質には外部品質と内部品質がある
- ユーザーに見えるか見えないか 可用性があるか
- 外部と内部でアプローチを考える
- 業務でやったことないと分からん
- いかに多く学びすぐに反映するか
- じゃあどうやって学ぶの?
- 学びのサイクルを観察する
- テストコードを設計道具として使おう
- 早期にデプロイして監視しておく
- バグに気づく
脆弱性から学ぶWebセキュリティ
資料: https://speakerdeck.com/hypermkt/study-web-security-from-vulnerability2
- 脆弱性の対策方法
- 根本的解決 完全根絶
- 保険的対策 影響範囲を限定させる
- ディレクトリトラバーサル
- ファイルパスをパラメータで受け付けない
- バリデーションする
- OSコマンドインジェクション
- セミコロンを利用してLinuxコマンドが連続して実行される
- 怪しい関数 -> system exec passthru shell_exec popen
- escapeshellargでエスケープする
- セッション管理の不備
- セッションハイジャック
- セッションIDの固定化
- セッションアダプション セッションIDを有効なセッションIDとして許可してもらうこと
- 認証が成功したら新しいセッションを開始する
オニギリペイのセキュリティ事故に学ぶ安全なサービスの構築法
資料: https://www.slideshare.net/ockeghem/phpconf2019
- キャンペーン21%還元! -> 景品表示法違反で消費庁から呼び出し
- 20%にして解決 -> キャンペーン取り消しで不信感を与えてしまった
- パスワードスプレー攻撃で不正ログイン相次ぐ
- IoT機器からの攻撃 -> 多数のIPアドレスからの分散攻撃
- ユーザーIDを連番にしない、二段階認証をおこなう
- 秘密の質問は禁止
- パスワードの定期的変更の強制を禁止
- 2段階認証の実装不備で不正ログインが止まらない
- バイパスできた -> セッションIDにユーザーIDをセットしていた
- 未ログイン/2段階認証待ち/ログイン済み でセッションIDを作成
- ヘルプデスクにパスワードリセットされてログインできない
- 仮パスワードを登録済みのメールアドレスに送信する
- スマホアプリの端末上にパスワードが平文で保存されていた
- iOS: KeyChainに保存する
- Android: KeyStoreに保存する
- リジェクト警告を無視してアプリがストアから消えた
- 「Android」という単語をiOSアプリ内で使用してしまった
- OSコマンドインジェクションで不正アクセスされた
- 改竄検知で早期に発見された
- エスケープが漏れていた
- PHP7.4以降はproc_open関数で対策 シェル経由にならないため安全
- WAFのリバースプロキシがオープンになってAWSのIAMが漏洩した
- SSRF脆弱性という
- Apacheのmod_securityを使っていた -> オープンリバースプロキシになっていた
- リスクアセスメントの方法
- ベースラインアプローチ 例:徳丸本に習ってやりましょう
- 非形式的アプローチ 例:徳丸さんに来てもらいましょう
- 詳細リスク分析 ベースラインで捉えられないものを考えましょう
- 業務レベルでの脅威分析表を作れるかどうか
- 投稿日:2019-12-01T15:50:28+09:00
WordPressのwpForo Forum(掲示板プラグイン)を解析して旧サイトから移行してみた
1.はじめに
もともと20年ほど旧サイト「らららのプログラマーズラウンジ」を運営していたのですが、XServer,WordPressのサイトへ引っ越ししました。これを機に(いまどき?になりますが)CGIの掲示板からWordPressのwpForo Forum(掲示板)へ移行しました。
(移行先は、こちらです。6万件弱の書き込みが移行されたことが確認できます)
旧サイトは約20年ほどの掲示板書き込みデータがあり、もちろんそのデータを移行する必要があるのですが
標準で移行方法が用意されておらず、自力で「wpForo Forum」を解析して移行しました。
ゆうて掲示板だし「眺めれば、なんとかなるっしょ♪」と軽いノリでやってみた。
と、そういう記事です。ちなみに、Qiitaでちょっと「wpForo」を検索してみたところ1件もヒットしない!
WordPressはあってもwpForoは1件もないかw ということで需要はないのはわかりましたが、
私のように(あまりいない)掲示板移行したい方向け、そして自分用のメモとして記事にしておきます。2.移行元と移行先の掲示板について
掲示板(移行元)
移行元の掲示板は、「とほほのWWW入門」で配布されている「WwwLounge - CGI質問掲示板」です。
約20年ほど前に配布され、今は更新されていないCGI掲示板です。
当時は設置している方もちらほら見られて、知っている人は知っている掲示板でした。
書き込みデータは、ほんとテキストのログです。
データはこんな感じです。Subject: WEB質問ラウンジ設置! ======================================== From: ららら E-Mail: rararahp@cool.ne.jp HomePage: http://rararahp.cool.ne.jp Date: 2002/06/01(土) 02:28:34 こんにちは。らららです。 WEB用ラウンジを設置しました。 みなさん、ご活用くださいませ。m(_ _)m ======================================== From: xxx Date: 2002/06/03(月) 12:36:42 新規ラウンジ解説、おめでとうございます。 ところで、ASPの設置予定はないのでしょうか? ======================================== From: xxx Date: 2002/06/03(月) 17:34:41 こんにちは。ASPはHP作成に関わることなので、 このラウンジでよいんじゃないですかねぇ。掲示板(移行先)
移行先の掲示板は、冒頭で書いたようにWordPressのプラグイン「wpForo Forum」です。
「wpForo」はWordPressの掲示板プラグインの中でも優れている機能を持っていて実は、他の掲示板からの移行ツールが用意されています。
画像
https://wpforo.com/docs/root/migrate-to-wpforo/しかし「phpBB」や「MyBB」等の有名どころからの移行ツールは用意されているのですが
むかーしの日本のローカルでしか使われていなかった「WwwLounge」からの移行ツールはもちろん用意されていません。しゅーりょーである?。。。
いや。待て待て。わいエンジニア!?
ちょっと眺めれば、掲示板データの移行なんて楽々いけるでしょ!
ということでやってみた。3.wpForoの解析(完全に理解した。してない)
では、移行に向けてwpForの解析を始めます。
と言っても眺めるだけです?wpForoにはドキュメント(英語)はあるのですが、
ぱっと見、移行ツール以外に有用な情報はなさげ。フォーラムで書き込まれたテキストはどこにいくか?
WwwLoungeみたいにテキストファイルってことはなくデータベースでしょう。
ということで、MySQL管理ツールのphpmyadminからデータベースを覗くとそれっぽいものがありました。
名前が「wp_wpforo_」から続くテーブルがそうですね。
次にテーブルの定義やデータを眺めます。
(眺めるというのは、テーブル名前、項目名から意味を想定して、実際に掲示板から書き込みをしてみて、どのようなデータが生成されるかを確認する地道な作業。。。?)と、すると移行にはこのあたりのテーブルをなんとかすれば良いことがわかります。
wp_wpforo_forums:各掲示板の情報
wp_wpforo_topics:投稿(ヘッダ)
wp_wpforo_posts:投稿(明細)
wp_wpforo_tags:タグ
wp_users:ユーザー (WordPressと共通)
wp_wpforo_profiles:ユーザーのプロファイルwp_wpforo_topicsとwp_wpforo_postsが書き込みデータで
スレ主がスレッドを立てるとwp_wpforo_topics、wp_wpforo_postsがそれぞれ1件出来て、
レスをつけるとwp_wpforo_postsが1件づつ増えていく関係です。それぞれの項目の意味はこんな感じです。
・wp_wpforo_topicsテーブル
・wp_wpforo_postsテーブル
※必要なテーブルの項目は、すべてどのような意味か、データ値はどんな形式で何が入るかを洗い出します。掲示板で使っていない機能とかがあるとデータに変化が現れずわからない事もあるのですが、大体解析しました。4.移行方針決定
移行方針は、事前にWordPressからサブフォーラムの作成など大まかな掲示板の設定を行い、
移行元掲示板からデータを抽出して、移行先掲示板にインポートしてあげます。これだけで動くはず!移行元掲示板(WwwLounge)からのデータエクスポート
上記にも書きましたが、投稿はテキストファイルなので、このテキストファイルを解析して読み込むプログラムを自作します。
SubjectやDateなど特定キーワードや規則的な順序性があるので、これに基づいて読み込みます。移行元→移行先へデータを変換
移行元からのエクスポートをしたデータをwpForoの形式に変換するプログラムを自作します。
(どのように変換するかは「wpForoの解析」のところで把握した構造になるように変換します)★実際の抽出、変換プログラムはこちら
https://github.com/rararalounge/LoungeTool移行先掲示板(wpForo Forum)へのデータインポート
インポートには、phpmyadminにインポート機能があるのでこれを使います。
以下のようにインポートするファイルのフォーマットを指定できます。
今回は、CSVにしています。
(XMLのほうが後々良いかなと思いましたが、1回インポート出来ればいいしCSVのほうがとりあえずなら簡単かなと。安易な選択)またCSV読み込み時にやっかいな、カラムの区切り文字や囲み記号エスケープ記号などをインポート画面から指定できます。便利!
5.移行実行
ここまでくれば、あとはインポートして「めでたしめでたし!」のはずなんですが、実際インポートすると以下のようにエラーが出たり、インポートが成功しても想定していなかった問題が発生したりとです。
解析があまかったり、データが思うように出来ていなかったりといった事が原因ですね。トライアンドエラーを繰り返すしかねぇ!
いくつか対応したのですが、覚えているのだと以下のような問題が出ました。
(もっと問題が出た気がしますが、忘れてしまいました)問題1
問題1 Q&AのURLがなぜか重複する
wpForoの仕様で、質問をすると以下のようにタイトルがそのままURLに使われます。
(例)
「https://rarara.org/community/programming/コードで作成したコントロールのイベントハンドラー」ただこのURLの長さに最大制限があって途中で切り取られます。普通に使う分には重複すると連番(ハイフンと数字)が付与される仕様なので問題ないのですが、私のように移行プログラムでインポートしようとしたところユニークチェックにひっかかって大量にエラーが出ました。
タイトルに
「コードで作成したコントロールのイベントハンドラー1」
「コードで作成したコントロールのイベントハンドラー2」
「コードで作成したコントロールのイベントハンドラー3」
というように連番をついている場合や
「コードで作成したコントロールのイベントハンドラーでエラー発生」
というようにタイトルが途中までが同じ場合です。。。これは変換プログラムを修正して、重複したらURLに連番をつけるようにして対応しました。
問題2
問題2 検索文字列をURLエンコードしてくれない
これはwpForoのバグなのですが、例えばサイドバーからタグ「C#」と検索しても#がURLエンコードされておらず正しく検索出来ない。
公式サイトへの修正依頼
とりあえずwpForoはPHPで出来ているのでソースコードを眺めて、修正箇所を見つけて対応しました。ただプラグインがバージョンアップした際に自分が行った修正が消えてしまうのが面倒だなぁと思ったので、wpForoの公式にサポートがあるので以下のように修正依頼を行いました。
返答はその日のうちにありました。はやい!
回答としては、「そのバグは知っている。次のバージョンで修正する予定」とのこと。おまけに
移行先の掲示板では、ユーザー登録制(登録しなくても投稿と書き込みは可能)なのですが、ユーザー登録していると投稿数に応じて以下のように貢献度がわかるちょっとだけうれしい仕様です。
昔から書き込んでくれているユーザーと、移行した掲示板ログをひもづけるために以下のように直にSQLでデータをいじって対応したりしています。(ユーザー登録して頂いた方のみですが)
UPDATE `wp_wpforo_topics` SET name = "", email="", userid = ユーザーID WHERE name = "ユーザー名" UPDATE `wp_wpforo_posts` SET name = "", email="", userid = ユーザーID WHERE name = "ユーザー名"(上記のSQLは、過去のユーザー名(文字列)で検索して登録したユーザーへの置き換え)
※一応、解析でテーブル構造を把握しているからこそ、こんな対応もできる。6.さいごに
エンジニアだから、存在しないんだったら(今回は移行ツール)自分で作れば良いですね。
エンジニアとかたいそうなものじゃなくて、今回のようにどのように解決するかの思考とプログラムがちょっと書けるぐらいで大丈夫!あと、移行先の「らららのエンジニアラウンジ」
移行前よりさらにアクセスが減っているような。。。引っ越ししたばかりで検索にヒットしにくいというのはあるかもしれませんが。
いまどき掲示板は難しい(チャットやTwitter等と比べて)のはわかっておりますが、みなさんのノウハウが参照できる形でずっと残るという意味ではとても有用だとと思っています。現に約20年分の技術Q&Aのログが本サイトには残っています。ということで当サイトにアクセスして頂けると喜びます!???
また私としてはQiita初投稿でした。勉強になりました。そしてこのしくみ便利だぁー。
- 投稿日:2019-12-01T15:03:11+09:00
カンファレンスに初めて行ってみた
はじめに
2019年12月1日にあったphpカンファレンスに行った。
https://phpcon.php.gr.jp/2019/
恥ずかしながら、エンジニア4年目にしてまだカンファレンスとか行ったことなかったため、ちょっと緊張しつつも、参加したので、
- 初めて勉強会とかカンファレンスに行く予定の人
- 行ったことないけど、行ってみたい人
向けになればと思い、ちょこっと書く。
カンファレンスとは
要は勉強会。大学の特別講義をイメージしてもらうのがいいかも。
今回のphpカンファレンスの場合、phpに関すること、開発環境について、システム設計に関わることなどphp関連のあれこれをテーマにした講演がある。
また、企業でブースを出しているところもあり、各企業のところに行けば、登壇した人からお話を聞くことも可能(っぽい。今回自分は用事があったため、行かなかった。)
行ってよかったこと
刺激になる
自分より全然できる人たちが、自分に向けて話してくれるので、勉強になる。また、自分の周りにいるエンジニアたちと話していると、どうしてもその世界に凝り固まってしまう。世界にはまだまだエンジニアはたくさんいるし、新しい考えを入れることで、勉強になる。
他の人も同じことで悩んでいて安心する
自分たちが立ち向かってる課題は他の企業でも同じことになってるんだなぁと思える。
その中でこんなことやってきました!と聞けるので、うちでもやってみようとなるんじゃないかなぁ。なんか色々もらえる
各企業がある程度採用目的でブースを出展しているところもあるため、なんか色々もらえる機会が多い。
準備しておいたほうがいいもの
- メモ:どういう風にメモをするかはわからないが、PCでも紙でもいいので、持っているほうが何かと便利。
- 荷物を入れる何か:なんだかんだ色々もらえるので、それをしまえるものがあるといいかも。
- Twitterアカウント:講演した資料とかをTwitterで公開してくれるから、あとから見る為にも、公演中に手元で資料見る為にもあると便利。
おわりに
普段ただプログラミングをやっていると手に入れられないこととかを勉強できるので、機会を見つけて、参加してそれをまた発信するというをやってったほうがよさそうだなぁ
実際に聞いた講演はまた別の記事で。
①MVCとはなにか?
https://qiita.com/chanyone_0505/items/9b3c5cd9c410a1406a16
- 投稿日:2019-12-01T13:20:53+09:00
PHPでREPLを使うにはphp -a
- 投稿日:2019-12-01T12:55:43+09:00
【もう悩まない!】WordPressのexcerptやtitle文字数操作の奥義をお教えしますよ
みなさんwordpressで開発をしていて、よくタイトルや抜粋文の文字数制限や、末尾を...にしたりしますよね。
そしてやり方をググると、例えば抜粋文なら大抵このようなコードを目にするかと思います。
function twpp_change_excerpt_length( $length ) { $length = 50; if ( is_category() || is_tax() ) { $length = 100; } return $length; } add_filter( 'excerpt_length', 'twpp_change_excerpt_length', 999 );これはページごとに抜粋文の長さを変えられるコードですね。
excerpt_lengthフィルターを使っています。function twpp_change_excerpt_more( $more ) { return '......'; } add_filter( 'excerpt_more', 'twpp_change_excerpt_more' );これは、抜粋文の省略記号を...にするよくあるやつです。
excerpt_moreフィルターを使っています。
一部分だけ適用したいんだが...
そう、例えば
ページの人気記事一覧の部分だけ文字数を20文字、そのほかの記事一覧の文字数を50文字にしたい
場合どうでしょうか。そういう風に融通がきくコードを今回紹介します。
まずはタイトルの制御から
タイトル文の制御は抜粋文に比べて楽なので、肩慣らしと行きましょう。
20文時までに制限して、それを超えたら末尾は...にします。
いきなりコードを見ます。
<?php if(mb_strlen($post->post_title)>20) { $title= mb_substr($post->post_title,0,20) ; echo $title . '...'; } else { echo $post->post_title; } ?>mb_srtlenとmb_substr
mb_strlen ( string $str [, string $encoding = mb_internal_encoding() ] ) : intmb_substr ( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]] ) : string肝はこの辺ですね。
上から順に見ていくと、
まず
$post->post_title
でタイトルを投稿から文字列で持ってきてます。そしてそれを
mb_strlen
で文字数調査、今回は20文字制限にしてます。20文字以上ならmb_substrで文字数制限をかけます。
プラグインのWP Multibyte Patchを入れていること
そしたらechoでそのタイトルと、末尾に...を出力します。
ここでif文分岐が起きているのは、if文で文字数調査をしないと、20文字以内のタイトルの末尾にも...がついてしまうためですね。
else以下は当然20文字以下なので、タイトルのみechoします。
抜粋文(excerpt)はやっかい
それでは抜粋文を見ていきましょう。
20文字に制限して、それ以上のときは...を出力します。
コードを見ます。
<?php if (mb_strlen($post->post_content, 'UTF-8') > 20) { $content = str_replace('\n', '', mb_substr(strip_tags($post->post_content), 0, 20, 'UTF-8')); echo $content . '...'; } else { echo str_replace('\n', '', strip_tags($post->post_content)); } ?>『やだいきなり複雑になった怖いやめてください?』
落ち着いて。
calm down
。1つ1つ解説します。str_replaceとstrip_tags
str_replace( $検索文字列 , $置換後文字列 , $検索対象文字列 [, int &$count ] )基本はタイトル文の制御と一緒ですが、post_contentにはhtmlタグが含まれています。
そのためそれらを取り除ければ良さそうです。
まずは
mb_strlen
で文字数調査をします。mb_substrで文字数制限をする前に、
strip_tags
でhtmlタグを取り除きます。最後に
str_replace
で半角スペースを取り除いて純粋な文字列になったものを$contentに代入して終わりです。全体に適用するか、個別に適用するか
ここまで紹介したコードは、どちらかといえば細かに適応させたい場合に有効かなと思います
ページ全体でタイトルや抜粋文を制御したい場合はfunctions.phpの方がいいと思いますし、同じページ内で場所ごとに文字数制御を変えたいなら、今回のコードは使えるかなと思います。
- 投稿日:2019-12-01T11:47:22+09:00
PHPカンファレンス2019基調講演まとめ
qnote アドベントカレンダー第1日目は、ちょうど PHP カンファレンス2019の開催日という事もあり、現地からの採れたてレポートをお送りしたいと思います。
採れたての elePHPant (3,000円) 。経費で落ちる?PHPカンファレンス2019
今年はいつもよりも遅めの12月開催。ちょうど11月29日にPHP7.4がリリースされ、またPHP7.1が EOL を迎えた日でもあります。
毎年おなじみの大田区産業プラザにて19年目20回目の開催。今年は嬉しいことに後輩 (@kambe0331) がスタッフとして参加。きっと刺激をうけて来年は登壇してくれる事を期待しています。
基調講演
乗り換え間違えてギリギリ到着。まずは御目当てのキーノートを拝聴。
1回目から参加している私としては廣川さんのキーノートを毎回楽しみにしています。
年を重ねるごとに廣川さんがティムクックに見えるのは多分私だけではないかと思いますが、なんと今は宇宙開発に携わっているそうです。すごい!キーノートではPHPのこれまでとこれから、また、PHP7.4の仕様変更などをわかりやすく紹介していただきました。30分くらいの時間で内容は濃いのに非常にまとまっていてこの辺りはさすがベテランの魅せる技という感じでした。
PHP7.4 改善/変更のポイント
キーノートにもありましたが簡単にまとめます。
クラスのプロパティ型指定
PHP7から導入されたメソッド引数と返り値のタイプヒンティングのプロパティ版。
Nullable 識別子(オプショナル型みたいなもの?)で null を許容するかどうかも定義可能。class Foo { public int $a; public ?object $b = null; }後方互換のためか、デフォルトでは $a に string を突っ込んでもエラーにはならず、int にキャストするだけというのは PHP らしいですが、
declare(strict_types=1);と書く事で Fatal error を出す事が可能。
他の言語では当たり前ですが、ようやく PHP でもタイプセーフなコードが書けるようになりそうですね。
OpCache のプリロード指定
Webリクエストで実行されたコンパイルのキャッシュはこれまでもありましたが、あくまでも起動後に実行されたコードのキャッシュなので、再起動直後はキャッシュ生成のオーバヘッドがありました。これをオプション指定で起動時にキャッシュしてしまおうというもの。これは php.ini に書けば良いのかな?
opcache.preload=/var/www/preload.incFFI (Foreign Function Interface)
あまり知見が無いですが(←調べろ)、PHPからCのソースを実行するようなやつ?前からエクステンションにあったものをネイティブに移植する事によって、前述のプリロード指定などで高速化し実現に至ったようです。より高速な処理が求められる場合に有効。
$ffi = FFI::cdef(“double atan2(const double, const double);”, “libm.so.6”) var_dump($ffi->atan2(0.1, 0.0);昔あったJavaのブリッジエクステンションみたいなもんかな?
配列スプレッド構文
JavaScript ではお馴染みの構文ですね。これまで array_merge を使用していた部分がより簡潔に書けます。
$a = [3, 4]; $b = [1, 2, ...$a, 5]; // => [1, 2, 3, 4, 5];アロー関数 (ショートクロージャー)
これも JavaScript ぽいですね。個人的には fn はダサいなと思いますが。。
これが、function ($x) use ($y) { return ($x * $x + $y); }こう書けます。
fn($x) => ($x * $x + $y);親スコープの変数が参照できる(つまりuse()が不要)なのでより簡潔になりますね。
??=構文
NULL合体演算子をより実用的にしたもの。変数が設定されていない場合に代替文字列を代入。
// PHP7 $a[‘user’] = $a[‘user’] ?? ‘nobody’; // PHP7.4 $a[‘user’] ??= ‘nobody’;で、これ、何演算子っていうの?
その他
これ以外にも細かな関数の追加や演算子の仕様変更、弱参照のインタフェース追加など、
PHP8のリリースまでの最後のメジャーバージョンという事もあり、新機能盛り沢山でした。早く仕事で使いたい!まとめ
そんなわけで基調講演を堪能し、後輩が働いているところも確認できたので、カフェに待避し iPad で Qiita のこの原稿を書いています。午後もいろいろ楽しみなセッションがあるので、年に一度のお祭りを楽しんできます!
qnote のアドベントカレンダー (https://qiita.com/advent-calendar/2019/qnote) も PHP だけでなくいろんな言語、技術のお話をお届けできるかと思いますのでどうか皆さんよろしくお願いいたします!
- 投稿日:2019-12-01T11:47:22+09:00
【レポート】PHPカンファレンス2019キーノート
qnote アドベントカレンダー第1日目は、ちょうど PHP カンファレンス2019の開催日という事もあり、現地からの採れたてレポートをお送りしたいと思います。
採れたての elePHPant (3,000円) 。経費で落ちる?PHPカンファレンス2019
今年はいつもよりも遅めの12月開催。ちょうど11月29日にPHP7.4がリリースされ、またPHP7.1が EOL を迎えた日でもあります。
毎年おなじみの大田区産業プラザにて19年目20回目の開催。今年は嬉しいことに後輩 (@kambe0331) がスタッフとして参加。きっと刺激をうけて来年は登壇してくれる事を期待しています。
基調講演
乗り換え間違えてギリギリ到着。まずは御目当てのキーノートを拝聴。
1回目から参加している私としては廣川さんのキーノートを毎回楽しみにしています。
年を重ねるごとに廣川さんがティムクックに見えるのは多分私だけではないかと思いますが、なんと今は宇宙開発に携わっているそうです。すごい!キーノートではPHPのこれまでとこれから、また、PHP7.4の仕様変更などをわかりやすく紹介していただきました。30分くらいの時間で内容は濃いのに非常にまとまっていてこの辺りはさすがベテランの魅せる技という感じでした。
PHP7.4 改善/変更のポイント
キーノートにもありましたが簡単にまとめます。
クラスのプロパティ型指定
PHP7から導入されたメソッド引数と返り値のタイプヒンティングのプロパティ版。
Nullable 識別子(オプショナル型みたいなもの?)で null を許容するかどうかも定義可能。class Foo { public int $a; public ?object $b = null; }後方互換のためか、デフォルトでは $a に string を突っ込んでもエラーにはならず、int にキャストするだけというのは PHP らしいですが、
declare(strict_types=1);と書く事で Fatal error を出す事が可能。
他の言語では当たり前ですが、ようやく PHP でもタイプセーフなコードが書けるようになりそうですね。
OpCache のプリロード指定
Webリクエストで実行されたコンパイルのキャッシュはこれまでもありましたが、あくまでも起動後に実行されたコードのキャッシュなので、再起動直後はキャッシュ生成のオーバヘッドがありました。これをオプション指定で起動時にキャッシュしてしまおうというもの。これは php.ini に書けば良いのかな?
opcache.preload=/var/www/preload.incFFI (Foreign Function Interface)
あまり知見が無いですが(←調べろ)、PHPからCのソースを実行するようなやつ?前からエクステンションにあったものをネイティブに移植する事によって、前述のプリロード指定などで高速化し実現に至ったようです。より高速な処理が求められる場合に有効。
$ffi = FFI::cdef(“double atan2(const double, const double);”, “libm.so.6”) var_dump($ffi->atan2(0.1, 0.0);昔あったJavaのブリッジエクステンションみたいなもんかな?
配列スプレッド構文
JavaScript ではお馴染みの構文ですね。これまで array_merge を使用していた部分がより簡潔に書けます。
$a = [3, 4]; $b = [1, 2, ...$a, 5]; // => [1, 2, 3, 4, 5];アロー関数 (ショートクロージャー)
これも JavaScript ぽいですね。個人的には fn はダサいなと思いますが。。
これが、function ($x) use ($y) { return ($x * $x + $y); }こう書けます。
fn($x) => ($x * $x + $y);親スコープの変数が参照できる(つまりuse()が不要)なのでより簡潔になりますね。
??=構文
NULL合体演算子をより実用的にしたもの。変数が設定されていない場合に代替文字列を代入。
// PHP7 $a[‘user’] = $a[‘user’] ?? ‘nobody’; // PHP7.4 $a[‘user’] ??= ‘nobody’;で、これ、何演算子っていうの?
その他
これ以外にも細かな関数の追加や演算子の仕様変更、弱参照のインタフェース追加など、
PHP8のリリースまでの最後のメジャーバージョンという事もあり、新機能盛り沢山でした。早く仕事で使いたい!まとめ
そんなわけで基調講演を堪能し、後輩が働いているところも確認できたので、カフェに待避し iPad で Qiita のこの原稿を書いています。午後もいろいろ楽しみなセッションがあるので、年に一度のお祭りを楽しんできます!
qnote のアドベントカレンダー (https://qiita.com/advent-calendar/2019/qnote) も PHP だけでなくいろんな言語、技術のお話をお届けできるかと思いますのでどうか皆さんよろしくお願いいたします!
- 投稿日:2019-12-01T00:00:08+09:00
PHPで分割代入していくだけの投稿
この記事は PHP Advent Calendar 2019 の1日目の記事です。
分割代入とは
配列の中身を複数の変数に一回で代入できる書き方
list()
と[]
list()
を使うか、PHP7.1以降では[]
を使ってもOKこの投稿では基本的に
[]
の書き方で試していきます。$colors = ['red', 'yellow', 'blue']; [$x, $y, $z] = $colors; var_dump($x, $y, $z); // "red", "yellow", "blue" list($a, $b, $c) = $colors; var_dump($a, $b, $c); // "red", "yellow", "blue"swapした例
$a = 'apple'; $b = 'banana'; [$a, $b] = [$b, $a]; var_dump($a, $b); // "banana" "apple" list($a, $b) = [$b, $a]; var_dump($a, $b); // "apple" "banana" // 関数を用意する例 // 参考:https://www.php.net/manual/en/migration71.new-features.php function swap(&$a, &$b): void { [$a, $b] = [$b, $a]; } swap($a, $b); var_dump($a, $b); // "banana" "apple"keyを指定してできる
※
list()
でキーを指定するやり方はPHP7.1から$animals = ['dog' => 'イヌ', 'cat' => 'ネコ']; ['dog' => $dog1, 'cat' => $cat1] = $animals; var_dump($dog1, $cat1); // "イヌ" "ネコ" list('dog' => $dog2, 'cat' => $cat2) = $animals; var_dump($dog2, $cat2); // "イヌ" "ネコ"注意点
一回はまった部分なのですが、配列をソートなどした場合に、インデックスを振り直さないと思い通りに動いてくれない時がありました
// 下2つの例は同じ意味で代入される [$a, $b] = ['aaa', 'bbb']; var_dump($a, $b); // "aaa", "bbb" [$c, $d] = [0 => 'ccc', 1 => 'ddd']; var_dump($c, $d); // "ccc", "ddd" // 配列をソートしたがインデックスが変わってないなどの場合に、このような配列になったとすると [$z, $y] = [1 => 'zzz', 0 => 'yyy']; // $zに "yyy"、$yに "zzz" が代入された状態になってしまう! var_dump($z, $y); // "yyy", "zzz" // やり方はいろいろあると思いますが、 // array_values などで インデックスが振り直された状態にするとうまくいきました。 [$z, $y] = array_values([1 => 'zzz', 0 => 'yyy']); var_dump($z, $y); // "zzz", "yyy"代入する変数が少ない場合
[$a, $b] = [10, 20, 30, 40, 50]; var_dump($a, $b); // 10 , 20 [, $a, , , $b] = [10, 20, 30, 40, 50]; var_dump($a, $b); // 20 , 50 ['blue' => $blue] = ['red' => '赤', 'blue' => '青']; var_dump($blue); // 青配列のキーが存在しない場合
[$a, $b] = [10]; var_dump($a, $b); // 10, NULL [5 => $c] = [10]; // Notice: Undefined offset: 5 ['a' => $aaa] = ['b' => 'bbb']; // Notice: Undefined index: aネストできる
[[$a, $b], [$c, $d]] = [[1, 2], [3, 4]]; var_dump($a, $b, $c, $d); // 1, 2, 3, 4$config = ['db' => [ 'connection' => 'mysql', 'host' => 'localhost', 'database' => 'sample', 'user' => 'root', 'pass' => 'root', ]]; ['db' => [ 'connection' => $connection, 'host' => $host, 'database' => $database, 'user' => $user, 'pass' => $pass, ]] = $config; var_dump($connection, $host, $database, $user, $pass); // "mysql" "localhost" "sample" "root" "root"foreach
$foods = [ ['apple', 'tomato', 'cherry'], ['banana', 'orange', 'paprika'], ]; foreach($foods as [$food1, $food2, $food3]) { var_dump($food1, $food2, $food3); } $foods = [ [ ['apple', 'cherry'], ['tomato'], ], [ ['banana', 'orange'], ['paprika'], ] ]; foreach($foods as [[$fruit1, $fruit2], [$vegetable1]]) { var_dump($fruit1, $fruit2, $vegetable1); }$todos = [ [ 'id' => 1, 'is_done' => true, 'todo' => '買い物', ], [ 'id' => 2, 'is_done' => false, 'todo' => '映画', ], ]; foreach($todos as ['id' => $id, 'is_done' => $isDone, 'todo' => $todo]) { var_dump($id, $isDone, $todo); } $todos = [ [ 'id' => 1, 'is_done' => true, 'todo' => [ 'shopping' => 'コンビニ', ], ], [ 'id' => 2, 'is_done' => true, 'todo' => [ 'shopping' => '百均', ], ], ]; foreach($todos as ['todo' => ['shopping' => $shopping]]) { var_dump($shopping); }配列の形でも代入できる
$todos = [ [ 'id' => 1, 'is_done' => true, 'todo' => '買い物', ], [ 'id' => 2, 'is_done' => false, 'todo' => '映画', ], ]; $ids = []; foreach($todos as $todo) { ['id' => $ids[]] = $todo; } var_dump($ids); // [1, 2]クラスでも分割代入する
クラスで分割代入しようとすると、このようにエラーになるのですが、
class A { public $hoge = 'hoge'; } ['hoge' => $hoge] = new A; // PHP Fatal error: Uncaught Error: Cannot use object of type A as array var_dump($hoge);ArrayAccessインターフェースを実装することで、クラスでも分割代入することができました。
参考:ArrayAccess,IteratorAggregateインタフェースで配列操作可能なオブジェクトを作成する - qiita
※↓実装の内容はLaravelのCollectionクラスをパクりました。
<?php class A implements ArrayAccess { private $items = []; public function __construct(array $values = []) { $this->items = $values; } public function offsetExists($offset) { return array_key_exists($offset, $this->items); } public function offsetGet($offset) { return $this->items[$offset]; } public function offsetSet($offset, $value) { if (is_null($offset)) { $this->items[] = $value; } else { $this->items[$offset] = $value; } } public function offsetUnset($offset) { unset($this->items[$offset]); } } // ArrayAccessインターフェースを実装すると、クラスを配列のように扱うことができる $A1 = new A; $A1[0] = 'hoge'; var_dump($A1[0]); // "hoge" // 分割代入もできました! $A2 = new A(['a', 'b', 'c']); [$a, $b, $c] = $A2; var_dump($a, $b, $c); // "a" "b" "c" $A3 = new A(['dog' => '犬', 'cat' => '猫', 'mouse' => 'ネズミ']); ['dog' => $dog, 'cat' => $cat, 'mouse' => $mouse] = $A3; var_dump($dog, $cat, $mouse); // "犬" "猫" "ネズミ"
参考
- ここが変わった! PHP7.1で知っておきたい新機能まとめ - WPJ
- PHP: list - Manual
- PHP: Migrating from PHP 7.0.x to PHP 7.1.x - Manual
- The list function & practical uses of array destructuring in PHP — Sebastian De Deyne
- A Re-Introduction To Destructuring Assignment — Smashing Magazine
最後まで見ていただいてありがとうございました。m(_ _)m