- 投稿日:2020-07-12T23:31:22+09:00
単一カラム取得
array_column()
こちらの関数が便利そうなので使ってみる
多重配列用意
emperor = [ [ 'id' => 1, 'name' => 'シャンクス', 'nickname' => '赤髪', 'wanted' => 4048900000 ], [ 'id' => 2, 'name' => 'エドワード・ニューゲート', 'nickname' => '白髭', 'wanted' => 5046000000 ], [ 'id' => 3, 'name' => 'カイドウ', 'nickname' => '百獣', 'wanted' => 4611100000 ], [ 'id' => 4, 'name' => 'シャーロット・リンリン', 'nickname' => 'ビッグマム', 'wanted' => 4388000000 ],
array_collum()をつかってみると
$want = array_column($emperor, 'name', 'nickname'); var_dump($want);実行
Array ( [0] => シャンクス [1] => エドワード・ニューゲート [2] => カイドウ [3] => シャーロット・リンリン )。。。なるほど第2引数に指定されたkeyを入れれば
valueさんが取得できるちなみにechoで出力も可能だった。
引数は複数指定可能
$want = array_column($emperor, 'name', 'nickname');引数は複数指定できる
Array ( [赤髪] => シャンクス [白髭] => エドワード・ニューゲート [百獣] => カイドウ [ビッグマム] => シャーロット・リンリン )ただしechoで出力はできなかった。
そして引数3つ以上指定するとエラーが出た。
- 投稿日:2020-07-12T22:43:09+09:00
CakePHP3.8で登録画面→確認画面→完了画面を実装する
今回のお題
表題の通りです。
久しぶりにCakePHP3を使ってコーディングをする機会がありました。
既にCakePHP4も出ていますが、CakePHP3.8からはそんなに変化がないとのことなので
Cake4にもすんなり入れると良いなあ、などと勝手に考えており。。需要があるかはさておき、備忘録として残しておきます。
環境
PHP: 5.6.40
CakePHP: 3.8
OS: CentOS7.2
DB: MySQL5.7.21全体的にレガシーな環境。。
(なのでCakePHP3を採用したという経緯)
テーブル構成
ありきたりで恐縮ですが、ユーザーテーブルへの登録です。
年齢は実年齢ではなく、10代・20代のようなレンジでの管理となります。CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `age` tinyint(4) NOT NULL, `gender` enum('male','female') NOT NULL, `email` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `last_login_at` datetime DEFAULT NULL, `is_deleted` tinyint(4) NOT NULL DEFAULT '0', `created` datetime NOT NULL, `modified` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CakePHP3実装
Model
Table
validationのコードも載せています。
src/Model/Table/UsersTable.phpuse Cake\ORM\Query; use Cake\ORM\RulesChecker; use Cake\ORM\Table; use Cake\Validation\Validator; class UsersTable extends Table { public function initialize(array $config) { parent::initialize($config); $this->setTable('users'); $this->setDisplayField('name'); $this->setPrimaryKey('id'); $this->addBehavior('Timestamp'); } /** * Default validation rules. * * @param \Cake\Validation\Validator $validator Validator instance. * @return \Cake\Validation\Validator */ public function validationDefault(Validator $validator) { $validator ->integer('id') ->allowEmptyString('id', null, 'create'); $validator ->scalar('name') ->maxLength('name', 10, '名前は10文字以内で設定してください。') ->requirePresence('name', 'create') ->notEmptyString('name'); $validator ->add('name', 'unique', [ 'rule' => 'validateUnique', 'provider' => 'table', 'message' => '名前がすでに登録されています。' ]); $validator ->scalar('age') ->requirePresence('age', true) ->notEmptyString('age'); $validator ->scalar('gender') ->requirePresence('gender', true) ->notEmptyString('gender'); $validator ->email('email', false, 'メールアドレスが正しくありません。') ->requirePresence('email', true) ->notEmptyString('email', 'メールアドレスを入力してください。'); $validator ->add('email', 'unique', [ 'rule' => 'validateUnique', 'provider' => 'table', 'message' => 'メールアドレスがすでに登録されています。' ]); $validator ->scalar('password') ->minLength('password', 8, 'パスワードは半角英数字8文字以上で入力してください。') ->requirePresence('password', 'create', 'パスワードを入力してください。') ->allowEmptyString('password', 'パスワードを入力してください。', 'update'); $validator ->scalar('password_confirm') ->minLength('password_confirm', 8, '確認用パスワードは半角英数字8文字以上で入力してください。') ->sameAs('password', 'password_confirm', '異なるパスワードが入力されています。') ->requirePresence('password_confirm', 'create', '確認用パスワードを入力してください。') ->allowEmptyString('password_confirm', '確認用パスワードを入力してください。', 'update'); return $validator; } /** * Returns a rules checker object that will be used for validating * application integrity. * * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. * @return \Cake\ORM\RulesChecker */ public function buildRules(RulesChecker $rules) { $rules->add($rules->isUnique(['email'])); return $rules; } }
Entity
hashしたパスワードを保存するためにDefaultPasswordHasherを使用しています。
src/Model/Entity/User.phpuse Cake\Auth\DefaultPasswordHasher; use Cake\ORM\Entity; class User extends Entity { protected $_accessible = [ 'name' => true, 'age' => true, 'gender' => true, 'email' => true, 'password' => true, 'last_login_at' => true, 'is_deleted' => true, 'created' => true, 'modified' => true, ]; protected $_hidden = [ 'password', ]; protected function _setPassword($password) { return (new DefaultPasswordHasher)->hash($password); } }
Controller
画面は以下の通りです。
add: 登録画面
confirm: 確認画面
complete: 完了画面年齢と性別はconfigで選択肢を管理しています。
確認画面へ遷移した際にvalidationチェックを行い、通ったらセッションに保存をします。
完了画面は「完了しました」が表示されるだけです。src/Controller/UsersController.phpuse App\Controller\AppController; use Cake\Core\Configure; use Cake\Event\Event; use Cake\I18n\Time; class UsersController extends AppController { public function beforeFilter(Event $event) { parent::beforeFilter($event); $this->Auth->allow([ 'add', 'confirm', 'complete', ]); } public function initialize() { parent::initialize(); } public function add() { $user = $this->Users->newEntity(); $ages = Configure::read('User.age'); $genders = Configure::read('User.gender'); if ($this->request->is('post')) { $user = $this->Users->patchEntity($user, $this->request->data); if ($user->errors()) { $this->Flash->error('入力内容を確認してください。'); } else { $this->request->session()->write('session.user_add', $user); return $this->redirect(['action' => 'confirm']); } } if ($this->request->session()->check('session.user_add')) { $user = $this->request->session()->consume('session.user_add'); } $this->set(compact('user', 'ages', 'genders')); } public function confirm() { if (!$this->request->session()->check('session.user_add')) { return $this->redirect(['action' => 'add']); } $ages = Configure::read('User.age'); $genders = Configure::read('User.gender'); $user = $this->request->session()->read('session.user_add'); $this->set(compact('user', 'ages', 'genders')); } public function complete() { if ($this->request->session()->check('session.user_add')) { $user = $this->request->session()->consume('session.user_add'); $result = $this->Users->save($user); if (!$result) { $this->Flash->error('保存できませんでした。'); $this->request->session()->write('errors', $user); return $this->redirect($this->referer()); } } } }
config (選択肢)
bootstrap.phpにファイルを定義をすると使用することができます。
config/bootstrap.phpConfigure::load('user', 'default', false);定数はこのように定義します。
config/user.php<?php return [ "User"=> [ "age" => [ 1 => '20歳未満', 2 => '20-29歳', 3 => '30-39歳', 4 => '40-49歳', 5 => '50-59歳', 6 => '60-69歳', 7 => '70-79歳' ], "gender" => [ 'male' => '男性', 'female' => '女性' ], ] ];
view
登録画面
ここでは細かいデザインは置いておいて、大まかなフォームの設定を記述します。
src/Template/Users/add.ctp<?= $this->Form->create($user, ['type' => 'post', 'autocomplete' => 'off']) ?> 名前 <?= $this->Form->control('name', ['type' => 'text', 'label' => false, 'required' => false]) ?> 年齢(セレクトボックス) <?= $this->Form->control('age', ['type' => 'select', 'label' => false, 'options' => $ages, 'required' => false, 'empty' => '選択してください']) ?> 性別(ラジオボタン) <?= $this->Form->control('gender', ['type' => 'radio', 'label' => false, 'required' => false, 'options' => $genders]) ?> メールアドレス <?= $this->Form->control('email', ['type' => 'text', 'label' => false, 'required' => false]) ?> パスワード <?= $this->Form->control('password', ['type' => 'password', 'label' => false, 'div' => false, 'required' => false, 'value' => '']) ?> パスワード確認用 <?= $this->Form->control('password_confirm', ['type' => 'password', 'label' => false, 'div' => false, 'required' => false, 'value' => '']) ?> <?= $this->Form->button('確認画面へ', ['type' => 'submit']) ?> <?= $this->Form->end() ?>
確認画面
ここでも設定を書き残しておきます。
セッションから値を取得して表示します。src/Template/Users/confirm.ctp<?= $this->Form->create($user, ['type' => 'post', 'url' => ['controller' => 'Users', 'action' => 'complete']]) ?> 名前 <?= $user['name'] ?> 年齢 <?= $ages[$user['age']] ?> 性別 <?= $genders[$user['gender']] ?> メールアドレス <?= $user['email'] ?> <?= $this->Form->button('登録完了', ['type' => 'submit']) ?> <?= $this->Form->end() ?>
以上です。
完了画面は「完了しました」が表示されるだけなので省略します。同じCakePHP3でもフォームヘルパーの書き方が変わっていました。
CakePHP4でもこのような感じでいけるのではないかと(願望込み)。。
- 投稿日:2020-07-12T22:06:43+09:00
【PHP】フレームワーク「Laravel」での開発環境構築(Mac版)
はじめに
今回、フレームワーク「Laravel」を用いてのアプリ開発を行うことになりました。
前提として、私は「Laravel」を使用することが初めてのため、今回記載する内容より最適な手順は他にもあるかもしれません。
ただ、「まずは何から始めれば、、」という初学者の方には得るものがあるかもしれないので、参考にしていただければ幸いです。
開発環境
・MacOS:10.14.6
目標
PHP7.3、Laravel6をインストール
概要(簡単な流れとコマンド)
①Homebrewのインストール
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"②PHPのインストール
brew install php@7.3③Composerのインストール
$ brew install composer④Laravelのインストール
$ composer create-project "laravel/laravel=6.*" プロジェクト名⑤確認
$ php artisan serve手順
②PHPのインストール
$ brew search php ==> Formulae brew-php-switcher php-code-sniffer php@7.2 phplint phpmyadmin phpunit php php-cs-fixer php@7.3 phpmd phpstan ==> Casks homebrew/cask/eclipse-php$ brew install php@7.3 Updating Homebrew... ~ 省略 ~$ php -v PHP 7.1.23 (cli) (built: Feb 22 2019 22:19:32) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies$ echo 'export PATH="/usr/local/opt/php@7.3/bin:$PATH"' >> ~/.bash_profile $ echo 'export PATH="/usr/local/opt/php@7.3/sbin:$PATH"' >> ~/.bash_profile $ source ~/.bash_profile$ php -v PHP 7.3.20 (cli) (built: Jul 9 2020 23:55:30) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.20, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.3.20, Copyright (c) 1999-2018, by Zend Technologies③Composerのインストール
$ brew search composer ==> Formulae composer$ brew install composer Updating Homebrew... ==> Downloading https://getcomposer.org/download/1.10.8/composer.phar ######################################################################## 100.0% ? /usr/local/Cellar/composer/1.10.8: 3 files, 1.9MB, built in 3 seconds$ composer ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ /_/ Composer version 1.10.8 2020-06-24 21:23:30 ~ 省略 ~④Laravelのインストール
ここではバージョン指定をしたLaravelのインストールと同時に「phpsample」というプロジェクト名でディレクトリを作成します。
$ composer create-project "laravel/laravel=6.*" phpsample Creating a "laravel/laravel=6.*" project at "./phpsample" Installing laravel/laravel (v6.18.8) - Installing laravel/laravel (v6.18.8): Downloading (100%)$ cd phpsample $ php artisan serve⑤確認
参考文献
本記事を作成するにあたり、以下を参照させていただきました。
ありがとうございます。https://qiita.com/1992_momotaro/items/c84ad1701c4623c04f27
https://qiita.com/YutakaYamasaki/items/96ab3f20a71d6c825b5d
https://deha.co.jp/magazine/laravel-php-mvc/
- 投稿日:2020-07-12T21:22:22+09:00
Guzzleでリクエスト、レスポンスを取得する方法
公式のドキュメントより一部抜粋。完全に自分のメモ。
ログを残すときとかに便利。以下のようなありがちなリクエストを想定。
$response = $client->request('PUT', '/put', ['json' => ['foo' => 'bar']]);リクエスト・レスポンスボディを取得する方法
getbody()
を使う。ボディがStringでキャストされる。
このとき、リクエストを取得するには#request->getbody()
してあげる。use GuzzleHttp\Middleware; // Create a middleware that echoes parts of the request. $tapMiddleware = Middleware::tap(function ($request) { echo $request->getHeaderLine('Content-Type'); // application/json echo $request->getBody(); // {"foo":"bar"} });レスポンスを取得するには
$response->getbody()
してあげる。ミドルウェアを使わないといけないっぽい。
Guzzleのミドルウェアについては、この記事が詳しいです。$body = $response->getBody(); // Implicitly cast the body to a string and echo it echo $body; // Explicitly cast the body to a string $stringBody = (string) $body; // Read 10 bytes from the body $tenBytes = $body->read(10); // Read the remaining contents of the body as a string $remainingBytes = $body->getContents();ちなみに
read()
を使うと指定したbyteだけ取得してくれるらしいです。重いデータとか用いるときには指定したい。ステータスコード・フレーズを取得する方法
200 OK
とかそういうのをそれぞれ取得してくれます。$code = $response->getStatusCode(); // 200 $reason = $response->getReasonPhrase(); // OK公式ドキュメント
- 投稿日:2020-07-12T20:05:44+09:00
PHP+imagickでMetashapeからエクスポートしたマスクを画像に重ねる
Agisoft社のフォトグラメトリソフトMetashapeのマスク処理が重いので、マスクををExportして、元画像に重ねた後、Metashapeに戻すことを考えました。
Metashapeに限らず、普通のマスク(切り抜き)処理にも使えるかも。PHP、ImageMagik、imagickのインストール
OSはWindows10 Home(64bit)
インストールというか、ファイルをダウンロードして解凍するだけ。PHP
https://windows.php.net/download/ から
php-7.3.20-nts-Win32-VC15-x64.zip をダウンロードして適当な場所に解凍。Webサーバーで使うには、VC、ts(Thread Safe)/nts(Non Thread Safe)をサーバーに合わせる必要があり、x64ではなくx86を使わなければいけない場合もあるようですが、今回はコマンドラインでしか使わないので、適当に選択しました。
バージョン(7.3.20)は、後述するimagickに対応しているものの中で最新版を選択しました。ImageMagik
https://windows.php.net/downloads/pecl/deps/ から
ImageMagick-7.0.7-11-vc15-x64.zip をダウンロードして適当な場所に解凍。imagick
https://windows.php.net/downloads/pecl/snaps/imagick/3.4.3/ から
php_imagick-3.4.3-7.3-nts-vc15-x64.zip をダウンロードして、中に入っている
php_imagick.dll をphpを解凍したフォルダ中ののextフォルダにコピー。それ以外のファイルは無視。VC、ts/nts、バージョンの後ろの方(7.3)はPHPに合わせました。
php.iniの設定
PHPを解凍したフォルダにある、php.ini-development をコピーして、ファイル名を php.ini に変更。
作成したphp.iniをエディタで開いて
;extension_dir = "ext"
のコメントアウト(先頭の;)を外し、
extension=php_imagick.dll
の行を追加。マスク処理のコード
元画像 aaa.JPG 背景の部屋がぐちゃぐちゃなので、目隠ししていますが(汗)
Metashapeから出力したマスク aaa_mask.png
Metashapeから、元画像のファイル名_mask.png という名前で出力されます。
出力画像 aaa.jpg
Metashapeに戻すことを考慮して、ファイル名は元画像と同じにしました。Metashapeから出力したマスクは透過設定されていないので、そのままではアルファ値による重ね合わせではうまくいきませんでした。imagickで透過処理をしようかとも考えましたが、透過処理しなくても単純に掛け算するだけでうまくいきました。
$image->compositeImage($mask, Imagick::COMPOSITE_MULTIPLY , 0, 0);
元画像側に、マスク画像を掛け合わせるのがミソで、こうすることによって、元画像のExifデータ(カメラのレンズ情報や撮影条件を記録したメタデータ)をそのまま残すことができます。(Metashapeに戻した後で、3Dモデルを作るときには、この情報が結構重要になるので。)
逆にExifデータを消したい場合は、マスク画像と元画像を逆にすると良さそうです。コード全文はこちら。
mask.php<?php ini_set("memory_limit", "-1"); // 一時的にメモリ制限をなくす $input_path = 'C:/(元画像が入っているフォルダ)/*.JPG'; // 入力ファイル $mask_path = 'C:/(MetashapeからマスクをExportしたフォルダ)'; // マスクファイルのフォルダ $output_path = 'C:/(マスク済み画像を出力するフォルダ)'; // 出力先フォルダ // 入力ディレクトリ内の全JPGファイルに対してマスク処理 $i = 0; foreach(glob($input_path) as $file){ if(is_file($file)){ $file_num = preg_replace("/^.+\/([^\/]+)\.JPG/", "$1", $file); // ファイルパスからファイル名取得 $mask_file = $mask_path . '/' . $file_num. '_mask.png'; // マスクファイルのパス $out_file = $output_path . '/' . $file_num. '.jpg'; // 出力ファイルのパス mask_iamge($file, $mask_file, $out_file); $i = $i + 1; } } // 元画像にマスクをかけて新しい画像を保存する関数 function mask_iamge($inputfile, $maskfile, $outputfile) { $image = new Imagick($inputfile); $mask = new Imagick($maskfile); $image->compositeImage($mask, Imagick::COMPOSITE_MULTIPLY , 0, 0); $image->writeImage($outputfile); } ?>マスク処理を実行
Windowsのコマンドプロンプトを起動して、以下の3つのコマンドを実行します。
Windowsの環境変数PATHは、PCに登録したくなかったので、コマンドプロンプト上で一時的に設定しました。c:\php はphp-7.3.20-nts-Win32-VC15-x64.zip を解凍した場所、C:\ImageMagick はImageMagick-7.0.7-11-vc15-x64.zipを解凍した場所です。
cd (mask.phpを保存したフォルダのパス) set PATH=%PATH%;C:\php;C:\ImageMagick\bin php mask.php
- 投稿日:2020-07-12T18:49:57+09:00
HerokuでGitHubの外部モジュール使おうとしたらハマったので備忘録(composer,heroku)
これが人生初めてのqiitaです。よろしくお願いします。
外部モジュールが動かない...
herokuにcomposer.jsonやcomposer.lockも含めたソースファイルをpushしたのですが...
いざ、アクセスして動かしてみると、
Uncaught Error: Class............とでて、動きませんでした。どうやら外部モジュールクラスを探せないといった感じです...
ローカルでは動いてた
windowsにxampを入れて動かしていたのですが、そのローカル環境では動いていました。
もちろん、windowsにcomposerも導入し、composer.lockも作成していました。確認
composer.jsonの記述はどうなってた?
こうでした
composer.json{ "require": { "php": "^7.3.14", "<使いたいモジュールのpath>": "dev-master", } }原因
composer updateの際にcomposer.lockと一緒に生成されていたvenderをフォルダごと消して、pushしろとのことでした。本当にただこれだけ...
どうやらherokuさんは向こうでpush時にcomposer.lockを参考にcomposer updateを実行するので、venderは邪魔だったようです。っていうか、「しっかりエラーログに目を通す」って言うことをしてたら簡単にわかったことでした。
- 投稿日:2020-07-12T17:42:37+09:00
短縮URLサービスがどんどん終了しているので、自分でOSSライブラリ「URL Shorter」開発してみた
はじめに
昨今、「短縮URL」サービスがどんどん終了しています。
私が一番使っていたサービスはGoogleの短縮URLだったのですが、いつまにか終了してしまっていました。短縮URLサービスはめちゃくちゃ便利なんですが、いかんせん不正や詐欺に使われてしまうというデメリットがあります。
上記で書いた残り1つについても、いつ終わってしまうか分かりません。そこで、「それならば自分用の短縮URLサービスを作ろう!」と思い立ち、開発し始めました。
かつ、どうせ作るなら、自分だけのものではなく、OSSライブラリとして開発することに決めました。
Laravel製なので、いま開発されているLaravelソリューションがあれば、そのパッケージに組み込むことで、短縮URL生成をかんたんに利用することが可能です。
GitHubにて、MITライセンスで公開済みです。
https://github.com/hirossyi73/url-shorterせっかくの機会ですし、Qiitaでもご紹介することにしました。
短縮URL一例
Amazon ポケモン剣盾
https://s.hirossyi.com/g/5rv2prGoogle(実際にはURL長くなっちゃってるけど)
https://s.hirossyi.com/g/ktuu7g機能
短縮URL生成機能
URLを入力すると、短縮URLを生成してくれます。
文字列の長さと、使用する文字は、設定ファイルで変更することが出来ます。
文字列が長ければ長いほど、重複は発生しにくいですが、文字列は短い方が短縮URLとしてはカッコいいです。
その辺はお好みで。短縮URL生成ページの認証機能
Googleをはじめとした、各種短縮URLサービスが終了した原因の99.9999999%は、間違いなく、詐欺サイトや犯罪などに使われる「不正利用」だと思っています。
なので、今回開発したライブラリでは、短縮URL生成ページにパスワード認証を付けることができます。
パスワードを知らない人は短縮URLを生成出来ないので、不正利用に使われる可能性はグッと減るはずです。プレビュー機能
短縮URLをブラウザで入力した際に、直接リダイレクトせず、プレビュー画面を表示することができます。
「本当にこのサイトにアクセスしますか?」的な機能ですね。APIから短縮URL作成、URL復元
このライブラリでは、短縮URL作成とURL復元を、APIから呼び出すことができます。
なので、他のシステムから、「短縮URLを生成する」的なこともしやすいです。ちなみに、API機能やパスワード認証機能は、設定によりオンオフが可能です。
用途によって、必要な場合のみオンにする・・・ということができるので、柔軟性は高いです。Laravelから短縮URL作成、復元
もちろん、プログラムから直接短縮URLを生成することも可能です。
いま開発されているLaravelソリューションがあれば、そのパッケージに組み込むことで、短縮URL生成をかんたんに利用することが可能です。use Hirossyi73\UrlShorter\Model\Shorter; // 短縮URL生成 $shorter = Shorter::create([ 'url' => $url ]); return $shorter->generate_url; // 短縮URLからURL復元 $shorter = Shorter::findByGenerateUrl($generate_url); return $shorter->url;ライブラリ・ライセンス情報
Laravelフレームワークとして開発しています。PHP7.0.0以上、Laravel5.5以上。
ちなみに、最新(2020/07/12現在)のLaravel7での動作も確認しています。ライセンスはMITです。
https://github.com/hirossyi73/url-shorterその他
このライブラリですが、短縮URL生成ページを公開する予定はありません。
前述してますが、先行サービスが相次いで終了したのは、間違いなく不正利用です。公開するとなると、それらの不正利用の対処も必要になってきます。なのでこのライブラリは、
・自分だけが短縮URL生成を行えるような設定で利用する
・社内で短縮URL生成など、限られた環境で利用する
・短縮URL生成のエンドポイントは公開せず、短縮URL生成はプログラムのみに絞る
このような用途が良いんじゃないか、と思っています。ちなみにこのライブラリは、だいたい10時間ぐらいで開発・公開しました。
もともと開発しているOSSのWebデータベース Exment のソースコードを流用しているものもありますが、それにしても超速で開発できるのは、Laravelは本当に素晴らしいですね。良ければ、URL Shorter、ご利用ください!
- 投稿日:2020-07-12T17:16:00+09:00
Laravelでキューを使う
キュー
実行したい処理(ジョブ)をjobsテーブル(キュー)に入れて置き、後で実行するものです
前提条件
eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っていますLaravelでデータベースを扱う準備をする
Laravelでテーブル作成
Laravelで初期データ投入
本記事は上記の内容を理解している前提で書かれています
本記事は上記ので作成したデータベースとレコードを使用しますjobsテーブル生成
コマンドラインで
sample
php artisan queue:table
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
eclipseプロジェクトを右クリック→リフレッシュ
/sample/database/migrations/xxxx_xx_xx_xxxxxx_create_jobs_table.phpが現れますマイグレーション実行
コマンドラインで
sample
php artisan migrate
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押しますjobsテーブルができました
データベースを使うように設定
/sample/.env修正
‥‥ QUEUE_CONNECTION=database ‥‥QUEUE_CONNECTIONをdatabaseにします
この値は
/sample/config/queue.phpで使われますジョブ生成
(1) コマンドラインで
sample
php artisan make:job SampleJob
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
eclipseプロジェクトを右クリック→リフレッシュ
/sample/app/Jobs/SampleJob.phpが現れます(2) SampleJob.php修正
SampleJob.php<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class SampleJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private $sample; public function __construct($sample) { $this->sample = $sample; } public function handle() { error_log("SampleJob " . $this->sample . PHP_EOL, 3, __DIR__ . DIRECTORY_SEPARATOR . "SampleJob.log"); } }コンストラクタとメソッドを修正しました
handleメソッドはジョブが処理されるときに呼びだされます
error_log関数でSampleJob.phpと同階層のSampleJob.logというファイルに文字列を出力するようにしていますControllerにメソッド追加
(1) /sample/app/Http/Controllers/SampleController.phpにuse文を追記
use App\Jobs\SampleJob;
(2) /sample/app/Http/Controllers/SampleController.phpにjobメソッドを追記
SampleController.phppublic function job() { SampleJob::dispatch("afterResponse")->afterResponse(); SampleJob::dispatch("delay 3 Seconds")->delay(now()->addSeconds(3)); return view('sample.job'); }SampleJob::dispatchの引数に渡しているものはSampleJobクラスのコンストラクタの引数になります。
afterResponseはブラウザにレスポンスを送り終えたらジョブを実行するものです
delayは指定した引数時間後にジョブを実行します。delayの引数に渡しているnow()はLaravelが提供しているヘルパ関数でCarbonクラス(https://carbon.nesbot.com/)を返します。今回はaddSeconds(3)で3秒後にジョブが実行されるようにしています。addSecondsの他にもいろいろ関数があります(carbon Docs Addition and Subtraction)
SampleJob::dispatch("delay 3 Seconds")->delay(now()->addSeconds(3));
で先ほど作成したjobsテーブルにジョブがinsertされます(3) /sample/routes/web.phpに下記を追記
Route::get('sample/job', 'SampleController@job');
viewの作成
/sample/resources/views/sample/job.blade.phpファイル作成
job.blade.php<html> <head> <title>sample</title> </head> <body> job </body> </html>キューワーカーの起動
キューワーカーとは、キュー(jobsテーブル)に投入されたジョブを処理してくれるものです
sample
php artisan queue:work
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
これは常時実行させておくものです。本番ではSupervisor等で常時動くように設定してください(http://supervisord.org/)(Laravel 7.x キュー Supervisor設定)
また、ジョブを修正した場合はキューワーカーを再起動してください
キューワーカーの再起動コマンドです
php artisan queue:restart
(Laravel 7.x キュー キューワーカーのデプロイ)動作確認
http://localhost/laravelSample/sample/job
MySQLでlaravel_sampleデータベースを確認してみましょう
select * from jobs;
3秒後に実行されるジョブが入っています
ジョブが実行されるとレコードはdeleteされます
/sample/app/Jobs/SampleJob.logが作成され、afterResponseもdelay 3 Secondsも出力されています
- 投稿日:2020-07-12T16:17:16+09:00
【Laravel】バリデーションの作成
バリデーションを簡単に安全にかけるための方法メモ。
フォームリクエスト
バリデーション専用のフォルダを作って処理をさせる。
書きコマンドを打ってrequestフォルダとファイルを作成。php artisan make:request StoreContactFormファイル名は任意。
Controller
controller内にフォルダとファイルが出来てるはずなので確認。
Controller/Requests/StoreContactForm.phppublic function authorize() { return true; //falseからtrueに変更 } . . . public function rules() { return [ //ここにバリデーションのルールを書く 'name' => 'required|string|max:20', 'title' => 'required|string|max:50', 'email' => 'required|email|unique:users|max:255', 'gender' => 'required', 'contact' => 'required|accepted', ]; }ルールは公式参照。
【https://readouble.com/laravel/7.x/ja/validation.html#rule-url】作成したデータを送る際に表示させたいので、storeメソッドに適応してあげる。
ContactFormController.php//バリデーションのファイルを呼び出し use App\Service\CheckFormData; //第一引数をRequestからバリデーションクラス名にする public function store(StoreContactForm $request) { $contact = new ContactForm; $contact->name = $request->input('name'); $contact->email = $request->input('email'); $contact->title = $request->input('title'); $contact->gender = $request->input('gender'); $contact->contact = $request->input('contact'); $contact->save(); return redirect('contacts/index'); }View
エラーメッセージを表示します。
create.blade.php@if($errors->any()) <ul> @foreach($errors->all() as $error) <li class="list-group-item list-group-item-danger">{{$error}}</li> @endforeach </ul> @endif$errorsはViewファイルに与えられてる変数らしいです。
こいつを回してあげて該当するもののみ表示します。まとめ
バリデーションは最初ややこしい印象があるのですが、大事な部分でもあるので徐々に慣れていきたいです。
- 投稿日:2020-07-12T16:12:12+09:00
あるあるなWebアプリの開発環境(PHP、Apache、MySQL)をDockerで構築した話
自社サービスのメンテナをしています。
悩み
- PHP、Apache、MySQLのバージョン上げなアカン
- でも、旧バージョンもメンテし続けなアカン
- ローカルPCでの開発環境がゴチャついてきた
- 顧客ごとにカスタマイズされてて、顧客環境ごとにソース自体が微妙に違ってる・・・
- できたらリモートサーバなしで、ローカルPCだけで完結させたい
Webサーバのディレクトリ構造はこんな感じ
/var/apps/ |-- abc # 株式会社abcさま用アプリ | |-- abc.php | |-- abc.png | `-- ・・・ |-- def # 株式会社defさま用アプリ | |-- def.php | |-- def.png | `-- ・・・ `-- ghi # 株式会社ghiさま用アプリ |-- ghi.php |-- ghi.png `-- ・・・
Dockerでなんとかしたいのですが・・・
- アプリのバージョンで、PHPやMySQLのバージョンが異なる
- アプリのバージョンごとにそれぞれの環境が必要
- 顧客ごとにDBのインスタンスが異なる
- MySQLの公式Dockerイメージって、初回起動のときにしかDB初期化スクリプト走らない
- なので、初回起動以降は、いちいちDBコンテナに入ってDB作ったりテーブル作ったりラジバンダリ
- 開発環境なので、デバッグを容易にできるようにしたい
Docker Composeでなんとかする
- アプリが依存するPHP、Apache、MySQLのバージョンを切り替えられるようにする
- 各アプリが使用するMySQLのDBインスタンスの生成を手軽に行いたい
- Xdebugでデバッグしたい
できあがったものがこちらです
くわしくはREADMEをご参照くださいmm
- 投稿日:2020-07-12T11:14:20+09:00
MAMPでDB接続にハマったときの話
最近PHPの学習始めました。
学習言語の雑食感が半端ないですが、必要に駆られてやっていることで、挫折したとかではありません。動画でPHPを学習していたときのMAMP環境でDB接続するときにMySQLに接続できなかったときの話です。
教材では<?php try{ $db=new PDO('mysql:host=127.0.0.1;dbname=mydb;charset=utf8','root',''); }catch(PDOException $e){ echo 'DB接続エラー: ',$e->getMessage(); } ?>とするように説明されていたのですが、MySQL SQLSTATE[HY000] [1049] Unknown database 'mydb'
とのこと。いや、あるし。で、色々ググってたらhostにポート番号いれて...というのを見つけたので、
host=127.0.0.1:3306ん?そもそもMAMP環境でMySQLのポートは3306でいいのか?と思いながら指定するも、案の定変わらず。
とりあえず再起動するか、と思ってやってみると、立ち上がってきたMAMPの初期画面に
書いてあるやん!!!
しかもパスワード間違えてるし。<?php try{ $db=new PDO('mysql:host=127.0.0.1:8889;dbname=mydb;charset=utf8','root','root'); }catch(PDOException $e){ echo 'DB接続エラー: ',$e->getMessage(); } ?>これで無事接続できました。
- 投稿日:2020-07-12T07:19:49+09:00
PHPのdate()やmktime()をjavascriptでも使いたい
同様の処理を行うライブラリとしてはdayjsやMoment.jsがメジャーかと思いますが、個人的にはあれほど多機能なものは要らなかったので作成してみました。
toLocaleDateString()
やtoLocaleTimeString()
等でも色々な表現はできますが、個人的にはやっぱり使い慣れたフォーマット文字列での指定が楽です。基本的にPHPの
date()
、mktime()
とほぼ同様に扱えるかと思いますが、以下の既知の相違があります。mktime()
第7引数 is_dst はありません。
第6引数 year にマイナスの値を渡した場合の結果がPHPのmktime()
とは異なります。date()
フォーマット文字は個人的に必須のもの(YmdHis)といくつかの簡単そうなもののみの実装で、PHPで使えるものすべてには対応していません。
PHPのdate()
ではフォーマット文字を\
でエスケープすることで置換せずそのまま表示させることができますが、本スクリプトでは未対応です。参考
PHP mktime マニュアル
PHP date マニュアルスクリプト
mktime_date.js/** * 日付をUNIXタイムスタンプに変換 */ function mktime(...args) { const d = new Date(), // 引数受け取り hour = args[0] ?? d.getHours(), minute = args[1] ?? d.getMinutes(), second = args[2] ?? d.getSeconds(), month = args[3] ?? d.getMonth() + 1, day = args[4] ?? d.getDate(), year = args[5] ?? d.getFullYear(), // 年指定補正 _year = year < 70 ? 2000 + year : year < 100 ? 1900 + year : year; try { // 引数に数値でないものが含まれる if( isNaN(hour) || isNaN(minute) || isNaN(second) || isNaN(month) || isNaN(day) || isNaN(year) ) throw new Error('A non-numeric value was specified for the argument'); } catch(e) { console.error(e.message); } // 日時設定 d.setFullYear(_year, month -1, day); d.setHours(hour, minute, second); // 戻り値 return Math.floor(d.getTime() / 1000); } /** * 日付/時刻を書式化 */ function date(format, timeStamp) { // timeStamp未指定の場合は現在日時のタイムスタンプを設定 if(timeStamp === undefined) timeStamp = Math.floor(Date.now() / 1000); try { // format未指定 if(format === undefined) throw new Error('Argument `format` not specified'); // timeStampが数値ではない if(isNaN(timeStamp)) throw new Error('A non-numeric value was specified for the argument'); } catch(e) { console.error(e.message); } // 日時取得 const d = new Date(timeStamp * 1000), df = new Date(timeStamp * 1000), year = d.getFullYear(), month = d.getMonth() + 1, day = d.getDate(), hour = d.getHours(), minute = d.getMinutes(), second = d.getSeconds(), w = d.getDay(), mDays = monthLastDays(year); // 1月1日からの日数 df.setMonth(0); df.setDate(1); const days = (d - df) / 864e5; // 戻り値 // 不要なreplaceメソッドチェーンがあれば // 適宜コメントアウトや削除をしてください return String(format) // 年 4桁 .replace(/Y/g, String(year).padStart(4, '0')) // 月 2桁 .replace(/m/g, String(month).padStart(2, '0')) // 日 2桁 .replace(/d/g, String(day).padStart(2, '0')) // 時 24時間単位 2桁 .replace(/H/g, String(hour).padStart(2, '0')) // 分 2桁 .replace(/i/g, String(minute).padStart(2, '0')) // 秒 2桁 .replace(/s/g, String(second).padStart(2, '0')) // 年 2桁 .replace(/y/g, ('0' + year).slice(-2)) // 月 桁揃えなし .replace(/n/g, month) // 日 桁揃えなし .replace(/j/g, day) // 時 24時間単位 桁揃えなし .replace(/G/g, hour) // 時 12時間単位 2桁 .replace(/h/g, String((hour + 11) % 12 + 1).padStart(2, '0')) // 時 12時間単位 桁揃えなし .replace(/g/g, (hour + 11) % 12 + 1) // 閏年か 0:閏年ではない 1:閏年 .replace(/L/g, mDays[1] == 29 ? 1 : 0) // 曜日 0:日~6:土 .replace(/w/g, w) // 曜日 1:月~7:日 .replace(/N/g, (w + 6) % 7 + 1) // 年間通算日 0~365 .replace(/z/g, days) // 月の日数 .replace(/t/g, mDays[month - 1]) // 文字誤置換防止 下処理 .replace(/(\w)/g, ":$1:") // 曜日 3文字 .replace(/:D:/g, ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][w]) // 月 3文字 .replace(/:M:/g, ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][month - 1]) // 曜日 フルスペル .replace(/:l:/g, ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][w]) // 月 フルスペル .replace(/:F:/g, ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][month - 1]) // am/pm .replace(/:a:/g, hour < 12 ? 'am' : 'pm') // AM/PM .replace(/:A:/g, hour < 12 ? 'AM' : 'PM') // 誤置換防止処理 後始末 .replace(/:(\w):/g , "$1") } /** * 該当年の各月末日を返す */ function monthLastDays(year) { return [31, (!(year % 4) && (year % 100) || !(year % 400)) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; } /** * mktimeへ年月日時分秒の順に引数を渡すラッパー */ function dateToTime(...args) { return mktime( args[3], args[4], args[5], args[1], args[2], args[0]); }実行サンプル
// example // Date.now()でタイムスタンプ取得 time = Math.floor(Date.now() / 1000); console.log( time ); // 1594489402 // mktime()へ引数なしでのタイムスタンプ取得 time = mktime(); console.log( time ); // 1594489402 // 実装した全フォーマット文字 formatStr = 'Y-m-d H:i:s y w z t D M l F a A G h g L'; console.log( date(formatStr, time) ); // 2020-07-12 02:43:22 20 0 193 31 Sun Jul Sunday July am AM 2 02 2 1 console.log( date(formatStr) ); // 2020-07-12 02:43:22 20 0 193 31 Sun Jul Sunday July am AM 2 02 2 1 console.log( date('Y-m-d H:i:s D', time) ); // 2020-07-12 02:43:22 Sun time = mktime(0, 0, 0, 1, 1, 2000); console.log( date('Y-m-d H:i:s D', time) ); // 2000-01-01 00:00:00 Sat time = mktime(4, 5, 6, 12, 3, 2000); console.log( date('Y-m-d H:i:s D', time) ); // 2000-12-03 04:05:06 Sun time = mktime(0, 0, 0, 2, 29, 2020); console.log( date('Y-m-d H:i:s D', time) ); // 2020-02-29 00:00:00 Sat time = mktime(0, 0, 0, 2, 29, 2019); console.log( date('Y-m-d H:i:s D', time) ); // 2019-03-01 00:00:00 Fri time = mktime(0, 0, 0, 1, 0, 2000); console.log( date('Y-m-d H:i:s D', time) ); // 1999-12-31 00:00:00 Fri time = mktime(0, 0, -1, 1, 1, 2000); console.log( date('Y-m-d H:i:s D', time) ); // 1999-12-31 23:59:59 Fri time = mktime(24, 0, 0, 1, 1, 2000); console.log( date('Y-m-d H:i:s D', time) ); // 2000-01-02 00:00:00 Sun console.log( date('Y-m-d H:i:s D', 0) ); // 1970-01-01 09:00:00 Thu console.log(date('Y-m-d H:i:s', mktime(123, 456, 789, -123, -4567)) ); // 1997-03-05 10:49:09 // dateToTime()はmktime()へ年,月,日,時,分,秒の順で引数を渡すラッパーです console.log(date('Y-m-d H:i:s', dateToTime(2020, 1, 2, 12, 34, 56))); // 2020-01-02 12:34:56
- 投稿日:2020-07-12T01:42:40+09:00
【Laravel】保存済みデータを編集/保存/削除する
ほぼほぼ自分用メモです。
編集
編集の画面と処理。
Route
web.phpRoute::group( ['prefix' => 'contacts', 'middleware' => 'auth'], function () { Route::get('index', 'ContactFormController@index')->name('contacts.index'); Route::get('create', 'ContactFormController@create')->name('contacts.create'); Route::post('store', 'ContactFormController@store')->name('contacts.store'); Route::get('show/{id}', 'ContactFormController@show')->name('contacts.show'); Route::get('edit/{id}', 'ContactFormController@edit')->name('contacts.edit'); //追加 } );Controller
ContactFormController.php//モデルの呼び出し use App\Models\ContactForm; //ファサードの呼び出し use Illuminate\Support\Facades\DB; public function edit($id) { $contact = ContactForm::find($id); return view('contacts/edit', compact('contact')); }View
show.blade.php{{-- createで入れた情報の表示 --}} <div>{{$contact->name}}</div> <div>{{$gender}}</div> <div>{{$contact->contact}}</div> {{-- editページに移動 --}} <a href="{{route('contacts.edit',[$contact->id])}}" class="btn btn-primary" type="submit">編集する</a>edit.blade.php<form method="POST" action="{{route('contacts.edit',['id'=>$contact->id])}}"> @csrf <input class="input-group" type="text" name="name" value="{{$contact->name}}"> <input class="input-group" type="text" name="email" value="{{$contact->email}}"> 男性:<input type="radio" name="gender" value="0" @if($contact->gender===0)checked @endif> 女性:<input type="radio" name="gender" value="1" @if($contact->gender===1)checked @endif> <input class="input-group" type="text" name="title" value="{{$contact->title}}"> <textarea class="input-group" name="contact">{{$contact->contact}}</textarea> <input class="btn btn-primary" type="submit" value="更新する"> </form>ラジオボタンはif文で現在のデータを表示する。
保存
編集ができるようになったので、編集したデータをアプデートして表示させます。
Route
web.phpRoute::group( ['prefix' => 'contacts', 'middleware' => 'auth'], function () { Route::get('index', 'ContactFormController@index')->name('contacts.index'); Route::get('create', 'ContactFormController@create')->name('contacts.create'); Route::post('store', 'ContactFormController@store')->name('contacts.store'); Route::get('show/{id}', 'ContactFormController@show')->name('contacts.show'); Route::get('edit/{id}', 'ContactFormController@edit')->name('contacts.edit'); Route::post('update/{id}', 'ContactFormController@update')->name('contacts.update'); //追加 } );Controller
ContactFormController.phppublic function update(Request $request, $id) { $contact = ContactForm::find($id); $contact->name = $request->input('name'); $contact->email = $request->input('email'); $contact->title = $request->input('title'); $contact->gender = $request->input('gender'); $contact->contact = $request->input('contact'); $contact->save(); return redirect('contacts/show/' . $id); }redirectでcontacts/showにリダイレクトさせる。
「.」でidをつなげてるので該当idのページがリダイレクトさせる。ちゃんと変わってたらおk。
削除
作成したデータを削除します。
Route
web.phpRoute::group( ['prefix' => 'contacts', 'middleware' => 'auth'], function () { Route::get('index', 'ContactFormController@index')->name('contacts.index'); Route::get('create', 'ContactFormController@create')->name('contacts.create'); Route::post('store', 'ContactFormController@store')->name('contacts.store'); Route::get('show/{id}', 'ContactFormController@show')->name('contacts.show'); Route::get('edit/{id}', 'ContactFormController@edit')->name('contacts.edit'); Route::post('update/{id}', 'ContactFormController@update')->name('contacts.update'); Route::post('destroy/{id}', 'ContactFormController@destroy')->name('contacts.destroy'); //追加 } );Controller
ContactFormController.phppublic function destroy($id) { $contact = ContactForm::find($id); $contact->delete(); return redirect('contacts/index'); }View
destroy.show.php<form method="POST" action="{{route('contacts.destroy',[$contact->id])}}"> @csrf <button type="submit" class="btn btn-danger" data-id="{{$contact->id}}" onclick="deletePost(this);">削除する</button> </form> <script> function deletePost(e) { if (confirm('本当に削除してもいいですか?')) { document.getElementById(('delete_' + e.dataset.id).submit()); } } </script>javascriptで削除前に警告を表示しています。
まとめ
メモ程度ですが、参考になれば。
- 投稿日:2020-07-12T01:42:40+09:00
【Laravel】保存済みデータを編集/保存する
既に保存されているデータの編集をします。
編集画面
編集画面を作成します。
Route
web.phpRoute::group( ['prefix' => 'contacts', 'middleware' => 'auth'], function () { Route::get('index', 'ContactFormController@index')->name('contacts.index'); Route::get('create', 'ContactFormController@create')->name('contacts.create'); Route::post('store', 'ContactFormController@store')->name('contacts.store'); Route::get('show/{id}', 'ContactFormController@show')->name('contacts.show'); Route::get('edit/{id}', 'ContactFormController@edit')->name('contacts.edit'); //追加 } );Controller
ContactFormController.php//モデルの呼び出し use App\Models\ContactForm; //ファサードの呼び出し use Illuminate\Support\Facades\DB; public function edit($id) { $contact = ContactForm::find($id); return view('contacts/edit', compact('contact')); }View
show.blade.php{{-- createで入れた情報の表示 --}} <div>{{$contact->name}}</div> <div>{{$gender}}</div> <div>{{$contact->contact}}</div> {{-- editページに移動 --}} <a href="{{route('contacts.edit',[$contact->id])}}" class="btn btn-primary" type="submit">編集する</a>edit.blade.php<form method="POST" action="{{route('contacts.edit',['id'=>$contact->id])}}"> @csrf <input class="input-group" type="text" name="name" value="{{$contact->name}}"> <input class="input-group" type="text" name="email" value="{{$contact->email}}"> 男性:<input type="radio" name="gender" value="0" @if($contact->gender===0)checked @endif> 女性:<input type="radio" name="gender" value="1" @if($contact->gender===1)checked @endif> <input class="input-group" type="text" name="title" value="{{$contact->title}}"> <textarea class="input-group" name="contact">{{$contact->contact}}</textarea> <input class="btn btn-primary" type="submit" value="更新する"> </form>ラジオボタンはif文で現在のデータを表示する。
保存画面
編集ができるようになったので、編集したデータをアプデートして表示させます。
Route
web.phpRoute::group( ['prefix' => 'contacts', 'middleware' => 'auth'], function () { Route::get('index', 'ContactFormController@index')->name('contacts.index'); Route::get('create', 'ContactFormController@create')->name('contacts.create'); Route::post('store', 'ContactFormController@store')->name('contacts.store'); Route::get('show/{id}', 'ContactFormController@show')->name('contacts.show'); Route::get('edit/{id}', 'ContactFormController@edit')->name('contacts.edit'); Route::post('update/{id}', 'ContactFormController@update')->name('contacts.update'); //追加 } );Controller
ContactFormController.phppublic function update(Request $request, $id) { $contact = ContactForm::find($id); $contact->name = $request->input('name'); $contact->email = $request->input('email'); $contact->title = $request->input('title'); $contact->gender = $request->input('gender'); $contact->contact = $request->input('contact'); $contact->save(); return redirect('contacts/show/' . $id); }redirectでcontacts/showにリダイレクトさせる。
「.」でidをつなげてるので該当idのページがリダイレクトさせる。ちゃんと変わってたらめでたし。
まとめ
メモ程度ですが、参考になれば。