- 投稿日:2020-12-08T23:49:29+09:00
Laravel Duskエラー 「Facebook\WebDriver\Exception\UnknownErrorException: unknown error: net::ERR_CONNECTION_REFUSED」
発生したタイミング
テストコード作成後、下記コマンド実行時にタイトル記載のエラーが表示
php artisan dusk原因
調べるとどうやら.envに記載したAPP_URLパラメータの値が間違っている模様
原因解決
.envのAPP_URLパラメータの値をブラウザからアクセスするアプリケーションで使用するURLと一致させる。
現在デプロイをしておらずローカル環境だった為下記のように変更する。APP_URL=http://localhost:8000/修正後コマンド実行
これで解決したと思い再度php artisan duskを実行すると......。
同じエラーが。
.......原因はサーバーを立ち上げていなかったから。
下記コマンドでサーバーを立ち上げ。。。。php artisan serve新規タブで
php artisan duskこれで無事テストが実行されました!!!
以上
- 投稿日:2020-12-08T23:31:28+09:00
Laravel Duskの導入
導入背景
現在Laravelで作成したポートフォリオのテストをデフォルトで搭載されているテストディレクトリを使用して実行していたが、利用が簡単なブラウザの自動操作/テストAPIであるDuckの存在を知ったので使用してみた
導入方法
プロジェクトに移動し、パッケージを取得
cd (プロジェクト名) composer require --dev laravel/dusk php artisan dusk:install上記後、プロジェクトのtestディレクトリ内にBrowserディレクトリが作成される
.envの変更
.envファイル内のAPP_URL環境変数を変更する。
値はブラウザからアクセスするアプリケーションで使用するURLと一致させます。
※ローカル環境の場合//デフォルト) APP_URL=http://localhost ↓ //変更後) APP_URL=http://localhost:8000参考文献
- 投稿日:2020-12-08T23:01:31+09:00
CakePHPでテキストを切り詰めてサフィックスを付ける
はじめに
テキストを特定の文字数で切り詰めて末尾に
...
などを付けたいときありますよね。
PHPでの組み込み関数を使えば簡単に実現できるのですが、CakePHPでは更に簡単に使えるものがありました。
最新のCakePHP4のものを紹介します。(2.x、3.xでもありました)https://book.cakephp.org/4/ja/core-libraries/text.html#Cake\Utility\Text::truncate
使い方
Cookbookに載っている通りになるのですが、
Cake\Utility\Text::truncate(string $text, int $length = 100, array $options)
メソッドを利用します。
$text
が$length
より長い場合、このメソッドはそれを$length
の長さに切り詰め、'ellipsis'
が定義されているなら末尾にその文字列を追加します。ちなみに
ellipsis
は日本語で省略記号
という意味です。(truncate
は切り捨てる
)use Cake\Utility\Text; $text = 'テキストを切り詰めます' echo Text::truncate( $text, 5, [ 'ellipsis' => '...', ] );出力:
テキストを...※$textがnullの場合でもサフィックスだけ表示されちゃうみたいなことは起きません。
おわりに
FWを利用する場合、汎用的なメソッドであれば便利な形で用意されてることもあるので、一度FW標準のものがないか確認した上で、なければ組み込み関数を使うといった手順を踏んだ方がよい、と本日先輩に教わりましたので備忘録として書き記しました。
- 投稿日:2020-12-08T22:55:05+09:00
Laravel/Socialiteが何をしているか調べてみた ~①リダイレクト編~
目標
Laravelで「Googleでログイン」を実装する際に導入する"Socialite"がどんなことをやっているのか調査してみた。
①リダイレクト編の目標は、Controllerに記述することになる下記コードが最終的なアウトプットは何か、どのように実装されているのかを明確にする。以下Controllerに記述することになるコード
SocialOAuthController.php/** * Googleアカウントの認証ページへユーザーをリダイレクト * ①リダイレクト編で解説するコード */ public function redirectToGoogle() { return Socialite::driver('google')->redirect(); } /** * Googleアカウントからユーザー情報を取得 * ②コールバック編で解説予定 */ public function handleGoogleCallback() { $user = Socialite::driver('google')->stateless()->user(); }先に結論(最終的なアウトプット)
認可URLにリダイレクトするRedirectResponseインスタンスを生成します。
リダイレクト先のURLは下記の通りです。RequestURL = https://accounts.google.com/o/oauth2/auth? client_id=$client_id &redirect_url=$redirect_url &scope=$scope1+$scope2+$scope3 &response_type=code &state=$state<環境>
Laravel Framework 8.17.2 laravel/socialite v5.1.2<解説>
Socialite
SocialiteのファサードはSocialiteServiceProviderで下記のように定義されています。
SocialiteServiceProvider.phpnamespace Laravel\Socialite; class SocialiteServiceProvider extends ServiceProvider { /** * Register the service provider. * * @return void */ public function register() { $this->app->singleton(Factory::class, function ($app) { return new SocialiteManager($app); }); } }Socialite::driver('google')
driverメソッドに文字列でドライバを指定しています。
Socialiteクラスの基底クラスであるManagerクラスにdriverメソッドで引数に渡した文字列に該当するドライバが可変関数として呼び出されます。Managernamespace Illuminate\Support; abstract class Manager { /** * Get a driver instance. * * @param string|null $driver * @return mixed * * @throws \InvalidArgumentException */ public function driver($driver = null) { $driver = $driver ?: $this->getDefaultDriver(); if (is_null($driver)) { throw new InvalidArgumentException(sprintf( 'Unable to resolve NULL driver for [%s].', static::class )); } // ドライバのインスタンスはキャッシュして再利用される。 if (! isset($this->drivers[$driver])) { $this->drivers[$driver] = $this->createDriver($driver); } return $this->drivers[$driver]; } /** * Create a new driver instance. * * @param string $driver * @return mixed * * @throws \InvalidArgumentException */ protected function createDriver($driver) { if (isset($this->customCreators[$driver])) { return $this->callCustomCreator($driver); } else { $method = 'create'.Str::studly($driver).'Driver'; if (method_exists($this, $method)) { // ここでcreateGoogleDriver()が呼び出される return $this->$method(); } } throw new InvalidArgumentException("Driver [$driver] not supported."); } }createGoogleDriver()
createGoogleDriverメソッドではconfigファイル(services.google)に設定した情報を取得し、GoogleProviderクラスのインスタンスを生成しています。
SocialiteManager.phpnamespace Laravel\Socialite; class SocialiteManager extends Manager implements Contracts\Factory { /** * Create an instance of the specified driver. * * @return \Laravel\Socialite\Two\AbstractProvider */ protected function createGoogleDriver() { $config = $this->config->get('services.google'); return $this->buildProvider( GoogleProvider::class, $config ); } }AbstractProviderを継承するGoogleProviderインスタンスを生成するまでがSocialite::driver('google')の動きです。
Socialite::driver('google')->redirect()
redirectメソッドでは生成されたリダイレクトURLにリダイレクトするようにRedirectResponseクラスのインスタンスを生成している。
AbstractProvider.phpnamespace Laravel\Socialite\Two; abstract class AbstractProvider implements ProviderContract { /** * Redirect the user of the application to the provider's authentication screen. * * @return \Illuminate\Http\RedirectResponse */ public function redirect() { $state = null; if ($this->usesState()) { $this->request->session()->put('state', $state = $this->getState()); } // ここでRedirectResponseクラスのインスタンスを生成 return new RedirectResponse($this->getAuthUrl($state)); } }getAuthUrl()
getAuthUrlメソッドはGoogleProviderで定義されている
GoogleProvider.phpnamespace Laravel\Socialite\Two; use Illuminate\Support\Arr; class GoogleProvider extends AbstractProvider implements ProviderInterface { /** * {@inheritdoc} */ protected function getAuthUrl($state) { return $this->buildAuthUrlFromBase('https://accounts.google.com/o/oauth2/auth', $state); } }AbstractProviderに戻ってURLがどのように生成されるか見ていく。
GoogleのベースURLにクエリパラメータとして認可に必要な各項目をセットしている。AbstractProvider.php/** * Build the authentication URL for the provider from the given base URL. * * @param string $url * @param string $state * @return string */ protected function buildAuthUrlFromBase($url, $state) { // $this->encodingTypeはPHP_QUERY_RFC1738が定義されている return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType); } /** * Get the GET parameters for the code request. * * @param string|null $state * @return array */ protected function getCodeFields($state = null) { $fields = [ 'client_id' => $this->clientId, 'redirect_uri' => $this->redirectUrl, 'scope' => $this->formatScopes($this->getScopes(), $this->scopeSeparator), 'response_type' => 'code', ]; if ($this->usesState()) { $fields['state'] = $state; } return array_merge($fields, $this->parameters); } /** * Format the given scopes. * * @param array $scopes * @param string $scopeSeparator * @return string */ protected function formatScopes(array $scopes, $scopeSeparator) { return implode($scopeSeparator, $scopes); } }<おまけ>認可リクエストのカスタマイズ
Laravel公式にもあるようにパラメータや、スコープの追加をすることができる。
パラメータの追加
パラメータを追加するにはwithメソッドを使用します。
$parametersの規定値は空の配列[]です。AbstractProvider.php/** * Set the custom parameters of the request. * * @param array $parameters * @return $this */ public function with(array $parameters) { $this->parameters = $parameters; return $this; }scopeの追加
scopeに規定値は['openid','profile','email']が定義されている。
追加するには下記のようにscopesメソッドを呼び出すか、setScopeメソッドで上書きするとよい。SocialOAuthController.php/** * Googleアカウントの認証ページへユーザーをリダイレクト */ public function redirectToGoogle() { return Socialite::driver('google')->scopes(['read:user', 'public_repo'])->redirect(); }メソッドの中身は下記のように配列がマージされる。
array_uniqueメソッドでは、重複した要素が存在する場合、はじめの要素のキーと値が保持されます。
https://www.php.net/manual/ja/function.array-unique.phpAbstractProvider.php/** * Merge the scopes of the requested access. * * @param array|string $scopes * @return $this */ public function scopes($scopes) { // $this->scopes = array_unique(array_merge($this->scopes, (array) $scopes)); return $this; } /** * Set the scopes of the requested access. * * @param array|string $scopes * @return $this */ public function setScopes($scopes) { $this->scopes = array_unique((array) $scopes); return $this; }リダイレクトURLの変更
リダイレクトURLが複数存在し、configファイルではなく動的に値を変更したい場合はredirectURLメソッドを使用します。
SocialOAuthController.php/** * Googleアカウントの認証ページへユーザーをリダイレクト */ public function redirectToGoogle() { return Socialite::driver('google')->redirectURL($redirectUrl)->redirect(); }AbstractProvider.php/** * Set the redirect URL. * * @param string $url * @return $this */ public function redirectUrl($url) { $this->redirectUrl = $url; return $this; }
- 投稿日:2020-12-08T21:36:47+09:00
PHPの入門書1周するために、サクッとDockerでApache + MySQL + PHP + phpmyadminの環境つくる
PHP入門書を1周したいので、LAMP環境がほしい
PHP力を上げたいので、入門書からやろうと思い立ちました。
そのためにサクッと用意できて、使いまわせるローカル環境が欲しかったので、そのメモです。今回はdocker-composeを使って環境構築します。
現在の環境
- OS:MacOS Big Sur
- Docker:19.03.13
- DockerCompose:1.27.4
ディレクトリ構成&ファイル
ディレクトリ構成
. ├── docker-compose.yml ├── html │ └── index.php ├── mysql ├── php │ ├── dockerfile │ └── php.ini └── phpmyadmindocker-comopse.yml
version: '3' services: php: build: context: ./php dockerfile: dockerfile volumes: - ./php/php.ini:/usr/local/etc/php/php.ini - ./html:/var/www/html ports: - 8080:80 mysql: image: mysql:5.7 volumes: - ./mysql:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=test - MYSQL_USER=root - MYSQL_PASSWORD=root phpmyadmin: image: phpmyadmin/phpmyadmin environment: - PMA_ARBITRARY=1 - PMA_HOST=mysql # - PMA_USER=test # - PMA_PASSWORD=test links: - mysql ports: - 4040:80 volumes: - ./phpmyadmin/sessions:/sessionsdockerfile
FROM php:7.2-apache RUN apt-get update && \ docker-php-ext-install pdo_mysql mysqli mbstringindex.php
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>php7.2-apache</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <?php phpinfo(); ?> </body> </html>php.ini
[Date] date.timezone = "Asia/Tokyo" [mbstring] mbstring.internal_encoding = "UTF-8" mbstring.language = "Japanese"エラーすべて表示させたいときは、以下を追加する
error_reporting = E_ALL実行コマンド
docker-compose.yml
があるディレクトリに移動して、以下を実行$ docker-compose up -dもし、dockerfileを変更した場合は
$ docker-compose up -d --buildphp.iniなどの変更を反映させたい時は
$ docker-compose restart動作確認
phpinfoを表示
http://localhost:8080/
phpmyadminを表示
http://localhost:4040/
ログイン画面でdocker-compose.yml
のmysqlコンテナの情報を記載してログインできればOK
つまづいたところ
- 当初は
docker-compose.yml
にimage: php:7.2-apache
と記載していたが、MySQLのドライバががなくてMySQL動かせなかったので、dockerflieを作成したdocker-compose.yml
のphpmyadminコンテナのPMA_USER
とPMA_PASSWORD
を記載すると、phpmyadminにうまくログインできず、データベースが作成できなかったので、コメントアウト今回やった、やる予定のPHP入門書
PHP入門 確認画面付きのお問い合わせフォームをつくりながらPHPを学ぶ(第2版)
https://amzn.to/2Irz7Fr
※上記環境で動作確認済みよくわかるPHPの教科書 【PHP7対応版】
https://amzn.to/2LhQ4mW
※まだ未着手参考記事
- 投稿日:2020-12-08T21:36:23+09:00
PHPでクラスを作成する
PHPでクラスを作成
以下の通り、PHPのクラスのコードを書いていく
<?php class Human{⇦クラス名を定義する。クラス名はPHPでは慣例的に大文字で記載する。
public $name; public $gender;⇦プロパティ(クラス内で定義された変数)を記載。
public function __construct($name,$gender){ $this->name = $name; $this->gender = $gender; } }コンストラクタはクラスからオブジェクトがnewによって作成される時に自動的に呼び出されるメソッド。
←メソッドはクラス内で定義された関数のこと。$human = new Human('suzuki','female'); echo $human->name; echo $human->gender; ?>$humanをsuzuki,femaleの名前でオブジェクトを作る(インスタンス化)
echoで名前と性別を出力。出力結果
xxxxxx@xxxxxx test-php % php class.php suzukifemale
- 投稿日:2020-12-08T21:16:14+09:00
Laravelですでにあるマイグレーションファイルを書き換えてテーブル定義を変更する
「テーブル作成のマイグレーションファイルに外部キー制約書き忘れた!」みたいなときの対処法です。
マイグレーションファイルが増えるのが気にならない場合や、複数人で管理している場合は、新しくマイグレーションを追加してテーブル定義を変更したほうがいいと思います。1. テーブルを出力する
対象のデータベースのテーブルを全て出力します。
$ mysqldump --no-create-info --ignore-table=database_name.migrations -u user_name -ppassword > ~/dumps/database_name_20210101.dumpポイント
--no-create-info
オプション : マイグレーションでテーブルは作成するので、CREATE TABLE ~
を出力しないオプションです。INSERT INTO ~
のみが出力されます。--ignore-table=database_name.migrations
: マイグレーションデータが保存されているテーブルを出力しません。2. マイグレートする
ファイルを書き換えたあと、マイグレーションを実行します。
php artisan migrate:fresh注意
migrate:fresh
はデータベース内の全てのテーブルをDropするので、データベースをLaravelアプリケーション以外と共有している場合はmigrate:refresh
等を検討してください。3. 挿入する
出力したデータを再度データベースに追加します。変更したテーブル定義が、変更前と互換性がないと、挿入できないので注意してください。
mysql -u user_name -ppassword -D database_name < ~/dumps/database_name_20210101.dumpエラーがでなければ、大丈夫です。
- 投稿日:2020-12-08T20:12:53+09:00
CentOS 8にPHP 7.4, PHP-FPM 7.4をインストール(AppStream)
はじめに
Application Stream(AppStream)を利用してCentOS8にPHP7.4をインストール
親記事:PHP, PHP-FPMの各種インストール方法とEOLまとめ
参考:RHEL8のパッケージ構成 - BaseOSとApplication Stream - 赤帽エンジニアブログ
第4章 新機能 Red Hat Enterprise Linux 8 | Red Hat Customer Portalサポート
本手法で導入した場合、Red Hat Enterprise Linux 8 Application Streams Life Cycle - Red Hat Customer Portalより、2029-05がEOLだと思われる。
それ以降に報告された脆弱性や不具合への対応は実施されない可能性がある。LOG
インストール
# cat /etc/redhat-release CentOS Linux release 8.3.2011 # yum module install php:7.4 ... 略各種確認
# which php /usr/bin/php # php -v PHP 7.4.6 (cli) (built: May 12 2020 08:09:15) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies # php -i | grep php.ini Configuration File (php.ini) Path => /etc Loaded Configuration File => /etc/php.ini # which php-fpm /usr/sbin/php-fpm # /usr/sbin/php-fpm -v PHP 7.4.6 (fpm-fcgi) (built: May 12 2020 08:09:15) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies
- 投稿日:2020-12-08T20:02:35+09:00
ローカル開発でSymfony CLIがすごい
Symfony Advent Calendar 2020 12日目の記事です。
Symfony CLIはSymfonyローカルマシンでの開発を支援するコマンドラインツールです。Laravel Installer(laravelコマンド)のようにSymfonyプロジェクトの作成にもできますが、それ以外にも優れた機能を備えています。その機能のうち、ぼくがよく使っている便利な機能をいくつかご紹介します。
Symfonyプロジェクトの作成
Symfonyのプロジェクトを作成するにはlavael Installerと同じ感じです。
symfony new [プロジェクト名] cd [プロジェクト名]プロジェクト名でディレクトリを作成し、ディレクトリ内にSymfonyをインストールします。以降のコマンドはプロジェクトのディレクトリに移動して、実行します。
余談ですが、Symfony CLIはコマンド実行時に、必ずアップデートを確認・ダウンロードし、常に最新のものが利用できるようになっています。PHPやSymfony ConsoleなどPHPコマンドを実行する
Symfony CLI経由でPHPなどのコマンドを実行します。これは、後述するバージョン指定、DockerCompose連携で大化けします。
# PHP実行 symfony php -v PHP 7.4.11 (cli) (built: Oct 1 2020 23:30:54) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Xdebug v2.9.6, Copyright (c) 2002-2020, by Derick Rethans with Zend OPcache v7.4.11, Copyright (c), by Zend Technologies # Symfony Console実行(symfony bin/consoleではないので注意) symfony console -V Symfony 5.2.0 (env: dev, debug: true) # composer実行 symfony composer require twigその他、pecl, pear, php-fpm, php-cgi, php-config, phpdbg, phpizeが実行できます。
ローカルWebサーバ起動
今まではSymfony Console
bin/console
でWebサーバを起動していましたが、それに代わってSymfony CLIで起動するようになりました。# Webサーバ起動 symfony server:start # バックグラウンドで実行する場合 symfony server:start -d # Webサーバ終了(バックグラウンド実行時) symfony server:stop # ドキュメントルートを変える場合 symfony server:start --document-root=webroot # ブラウザを開く symfony open:local # ログを確認する symfony server:logここでポイントはドキュメントルートを指定できる点。当然ながら、LaravelやCakePHPのプロジェクトでも実行できます。
また、PHP-FPMが有効な場合は、自動でPHP-FPMを利用します。ローカルをHTTPSにする
php -S
でビルトインサーバを起動した場合、HTTPでの通信になります。上記のSymfony CLIでのローカルWebサーバ起動も同じくHTTPですが、HTTPSに変えることができます。symfony server:ca:installこのコマンドで、ローカルに証明書をインストールします。以降、サーバを起動するとこのローカル証明書を利用したHTTPS通信になります。インストール後にHTTP通信したい場合は、ローカルWebサーバ起動時に、
--allow-http
オプションを指定すると、HTTPとなります。PHPのバージョンを指定する
プロジェクトによってPHPのバージョンが異なる場合、phpenvでバージョンを切り替えたりしていましたが、プロジェクトディレクトリのルートに、
.php-version
という名前のファイルを作成しファイル内にPHPのバージョンを記載すると、Homebrewなどでインストールしたローカルの該当バージョンPHPを、切り替えなしで利用できます。
※該当のバージョンのPHPがインストールされている必要があります.php-version8.0このファイルがある状態でSymfony CLIを実行すると、指定されたバージョンで実行されます。
# ローカルのデフォルトPHPを実行 php -v PHP 7.4.11 (cli) (built: Oct 1 2020 23:30:54) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Xdebug v2.9.6, Copyright (c) 2002-2020, by Derick Rethans with Zend OPcache v7.4.11, Copyright (c), by Zend Technologies # Symfony CLI経由で、.php-versionに8.0を指定して実行 symfony php -v PHP 8.0.0-dev (cli) (built: Oct 5 2020 01:14:40) ( NTS ) Copyright (c) The PHP Group Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies with Zend OPcache v8.0.0-dev, Copyright (c), by Zend Technologiesまた、composerも指定したPHPのバージョンで実行することができます。
# Symfony CLI経由で、バージョンを指定してcomposer実行 symfony composer require twigローカルWebサーバも、もちろん変わります。ただし、ローカルWebサーバ起動中にバージョンを変えても反映されないので、サーバの再起動が必要です。
symfony server:start -d echo 8.0 > .php-version symfony server:stop symfony server:start -d↓
インストールされているPHPのバージョンを確認する
ローカルにどのPHPのバージョンがインストールされているか
symfony local:php:list
で確認することができます。symfony local:php:list ┌─────────┬──────────────────────────────────┬─────────┬────────────────────────────────┬───────────────────────────────┬─────────┬─────────┐ │ Version │ Directory │ PHP CLI │ PHP FPM │ PHP CGI │ Server │ System? │ ├─────────┼──────────────────────────────────┼─────────┼────────────────────────────────┼───────────────────────────────┼─────────┼─────────┤ │ 7.2.33 │ /usr/local/Cellar/php@7.2/7.2.33 │ bin/php │ sbin/php-fpm │ bin/php-cgi │ PHP FPM │ │ │ 7.3.11 │ /usr │ bin/php │ sbin/php-fpm │ │ PHP FPM │ │ │ 7.4.11 │ /usr/local │ bin/php │ Cellar/php/7.4.11/sbin/php-fpm │ Cellar/php/7.4.11/bin/php-cgi │ PHP FPM │ * │ │ 8.0.0 │ /usr/local/Cellar/php@8.0/8.0.0 │ bin/php │ sbin/php-fpm │ bin/php-cgi │ PHP FPM │ │ └─────────┴──────────────────────────────────┴─────────┴────────────────────────────────┴───────────────────────────────┴─────────┴─────────┘ The current PHP version is selected from default version in $PATHDocker Composeと連携する
プロジェクトルートに
docker-compose.yml
があり、コンテナを起動している場合は、Docker Composeと連携します。前述の状態でSymfony CLIを実行すると、利用しているイメージに合わせて自動的に環境変数が設定されます。
例えば、以下のようなdocker-compose.ymlがある場合、docker-compose.ymlversion: '3' services: database: image: mariadb ports: [3306] environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=database - MYSQL_USER=user - MYSQL_PASSWORD=password mailer: image: mailhog/mailhog ports: [8025, 1025]Dockerを起動して、Symfony CLIでコマンド実行を行うと、
docker-compose.yml
内の設定(サービス名、イメージ、ポート)を利用して、自動で環境変数を生成します。docker-compose up -d symfony server:start -d symfony var:export --multiline export DATABASE_DATABASE=database export DATABASE_DRIVER=mysql export DATABASE_HOST=127.0.0.1 export DATABASE_NAME=database export DATABASE_PASSWORD=password export DATABASE_PORT=49195 export DATABASE_SERVER=mysql://127.0.0.1:49195 export DATABASE_URL=mysql://user:password@127.0.0.1:49195/database?sslmode=disable&charset=utf8mb4 export DATABASE_USER=user export DATABASE_USERNAME=user export MAILER_AUTH_MODE= export MAILER_CATCHER=1 export MAILER_DRIVER=smtp export MAILER_DSN=smtp://127.0.0.1:49197 export MAILER_HOST=127.0.0.1 export MAILER_PASSWORD= export MAILER_PORT=49197 export MAILER_URL=smtp://127.0.0.1:49197 export MAILER_USERNAME= export MAILER_WEB_HOST=127.0.0.1 export MAILER_WEB_IP=127.0.0.1 export MAILER_WEB_PORT=49196 export MAILER_WEB_SCHEME=http export MAILER_WEB_SERVER=http://127.0.0.1:49196 export MAILER_WEB_URL=http://127.0.0.1:49196 [以下省略]イメージにあったポートを指定していれば、上記が自動で生成され、
.env
に記述がなくても上記の環境変数で動作します。また、.env
に該当の変数が定義されていても、こちらが優先されます。
docker-compose.yml
のサービス名は、環境変数のプレフィクスになります。サービス名がdatabase
であれば、DATABASE_
に、db
であればDB_
となります。現在サポートされているサービスは以下の通りです。
サービス ポート Symfonyで使う場合のデフォルトのプレフィクス MySQL 3306 DATABASE_ PostgreSQL 5432 DATABASE_ Redis 6379 REDIS_ Memcached 11211 MEMCACHED_ RabbitMQ 5672 RABBITMQ_ Elasticsearch 9200 ELASTICSEARCH_ MongoDB 27017 MONGODB_ Kafka 9092 KAFKA_ MailCatcher 1025/1080 or 25/80 MAILER_ MailHog 1025/1080 or 25/80 MAILER_ Blackfire 8707 BLACKFIRE_ ※公式にMailHogは記載されてないけど、動く。
Docker Composeと連携している場合は、追加コマンドが利用できるようになります。
# MailHog, MailCatcherを設定している場合は、メールボックスをブラウザで開く symfony open:local:webmail # RabbitMQを設定している場合は、マネージャーをブラウザで開く symfony open:local:rabbitmq1点注意が必要ですが、Docker ComposeでWebサーバやPHPを定義している場合に、Symfony CLIコマンドをローカルで使ってもコンテナ上のWebサーバやPHPは利用しません。 Symfony CLIは、あくまでローカルでPHPを実行するすることを想定していて、コンテナ上で実行することを想定していません。
"Why would you want to use the web server in a Docker container?"
by Fabien Potencier※ファビアンも、なんでコンテナなん?って言ってる。
まとめ
他にも機能はありますが、利用する機会が多いのは上記に挙げたものかなと思います。前述の通り、Symfonyだけではなく、LaravelやCakePHP、頑張ればWordPressなどのCMSも動くので、ぜひ一度試してもらえたらと思います。公式ドキュメントはこちら
- 投稿日:2020-12-08T19:15:57+09:00
PHP製デプロイツール Deployer v7beta を試す init編
はじめに
- PHP製のデプロイツールである Deployer が7系になって書き方が結構変わりそうなので触っておく
- Deployまでやろうと思ったが調査等に時間かけすぎたので一旦アウトプット
- 本当にさわりだけ、host周りの移行が出来たらまた書く
環境
- PHP 8.0
- Deployer 7.0.0-beta.11
初期化
- dep initで生成されるファイルがphpかyamlか選べるようになっている
- deploy.yamlとdeploy.php両方ある場合はphpのみ読み込まれる
dep initの実行
$ dep init ╔╦╗┌─┐┌─┐┬ ┌─┐┬ ┬┌─┐┬─┐ ║║├┤ ├─┘│ │ │└┬┘├┤ ├┬┘ ═╩╝└─┘┴ ┴─┘└─┘ ┴ └─┘┴└─ ███████████████████████████████████████████████ Welcome to the Deployer config generator. Press ^C at any time to quit. Select recipe language [php]: [0] php [1] yaml > 1 Select project template [common]: [0 ] cakephp [1 ] codeigniter [2 ] common [3 ] composer [4 ] drupal7 [5 ] drupal8 [6 ] flow_framework [7 ] fuelphp [8 ] joomla [9 ] laravel [10] magento [11] magento2 [12] prestashop [13] provision [14] shopware [15] silverstripe [16] sulu [17] symfony [18] typo3 [19] wordpress [20] yii [21] zend_framework > 2 Repository []: > Project name [src]: > DeployerSample Hosts (comma separated) []: > example Successfully created deploy.yaml生成されたyaml
deploy.yamlimport: - recipe/common.php - recipe/provision.php config: application: 'DeployerSample' repository: '' shared_files: - .env shared_dirs: - uploads writable_dirs: - uploads hosts: example: deploy_path: '~/{{application}}' tasks: build: script: - 'cd {{release_path}} && npm run build' after: deploy:failed: deploy:unlock生成されたphp
最初の選択をphpにして実行
deploy.php<?php namespace Deployer; require 'recipe/common.php'; require 'recipe/provision.php'; // Config set('application', 'DeployerSample'); set('deploy_path', '~/{{application}}'); set('repository', ''); add('shared_files', []); add('shared_dirs', []); add('writable_dirs', []); // Hosts host('example'); // Tasks task('build', function () { cd('{{release_path}}'); run('npm run build'); }); after('deploy:failed', 'deploy:unlock');外部ファイルのimport
ファイルツリー
/path/to/project ├─deployer │ ├─hoge.php │ └─hoge.yaml └─deploy.yaml or deploy.phpdeployer/hoge.php<?php namespace Deployer; task('hoge:php', fn() => writeln('PHP'));deployer/hoge.yamltasks: hoge:yaml: script: "touch ./touch-hoge-yaml"yamlの場合
- 今回はテスト用に諸々省いたdeploy.yamlを使う
- 普通にimportの中にファイルを指定するだけで外部ファイルのタスクを取り扱えるようになる
- 取り敢えずローカルで実行
deploy.yamlimport: - deployer/hoge.php - deployer/hoge.yaml hosts: example: local: truedeployer/hoge.phpのタスクを実行
$ dep hoge:php example task hoge:php [example] PHPdeployer/hoge.yamlのタスクを実行
- 現状アウトプットを表示しない?ようなのでtouchして確認
- やり方あればコメントで教えて下さいm(_ _)m
- scriptを実行すると sh
COMMAND
が実行される- Importer#tasksのrunの戻り値を取って funcions.phpのwriteln等に渡せば出力出来る
$ dep hoge:yaml example task hoge:yaml $ ls touch-hoge-yaml touch-hoge-yamlphpの場合
- yaml同様簡易化したファイル
- 結果はyamlと変わらないので割愛
deploy.php<?php namespace Deployer; import('deployer/hoge.php'); import('deployer/hoge.yaml'); localhost('example');memo
- yamlで定義されたtasks配下はImporter#tasks内で組み立てられる
- yamlのタスクに紐づくキーがscript, upload, download以外の場合、結局 src/functions.phpのtask内でエラーになる。
- 投稿日:2020-12-08T19:07:41+09:00
Laravelのフォームリクエストで他のパラメータを参照する方法
やりたいこと
いろいろあって、バリデーション時に他の入力値を使ってバリデーションしたい。
(今回は、ユーザー情報を暗号化していたため、他の入力値を元にユーザーを探して、一致するかどうかを確認したかった)方法
$this->request->get()
を使用する。laravel/app/Http/Requests/HogeRequest.php<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use App\Rules\BirthdayMatched; class HogeRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { // getメソッドで入力値を取得する。 $email = $this->request->get('email'); return [ 'email' => ['required', 'string', 'email'], 'birthday' => ['required', 'string', new BirthdayMatched($email)], ]; }あとはカスタムバリデーションルールのプロパティに代入して使用する。
laravel/app/Rules/BirthdayMatched.php<?php namespace App\Rules; use Illuminate\Contracts\Validation\Rule; use App\User; class BirthdayMatched implements Rule { private $email; /** * Create a new rule instance. * * @return void */ public function __construct(string $email) { $this->email = $email; } /** * Determine if the validation rule passes. * * @param string $attribute * @param mixed $value * @return bool */ public function passes($attribute, $value) { //ここで$this->emailを使ったバリデーション処理をする。 } /** * Get the validation error message. * * @return string */ public function message() { return '誕生日が一致しません'; } }
- 投稿日:2020-12-08T15:18:36+09:00
PHP8 で Call to undefined function each() になる場合の対処法
PHP8 で each 関数が削除されたため、
Call to undefined function each()
というエラーになります。例えば、以下のような処理がある場合は、
current()
とnext()
を使用した処理に書き換えます。// PHP7 までは each() 関数が使える while (list($k, $v) = each($array)) { echo $k; echo $v; }// PHP8 以降は each() 関数は使えない while ($current = current($array)) { $k = key($array); $v = $current; echo $k; echo $v; next($array); }Composer のパッケージで each() が使われている場合
Composer のパッケージで
each()
関数が使われている場合は、直接書き換えるわけにはいきませんので、解決するためのパッケージを作成しました。以下のコマンドで PHP8 でも
each()
関数を使えるようになります。composer require nanasess/php8-compat本来は、こういった互換プログラムを使用するのはあまりよろしくないので、
1. 対応されるまで待つ
2.each()
関数を使わないよう修正し、 Pull Request する
などの対応をするのが良いと思います。
- 投稿日:2020-12-08T12:38:12+09:00
WordPressで新規投稿時のURLをデフォルトで連番化
ワードプレスのテーマtwentytwentyを基盤に、新規投稿を作成した際に記事のURLの末尾をデフォルトで連番にする実装を行ったので、備忘録的にこの記事を残す。
環境情報
PHP:version 7.3.12
WordPress:version 5.5.3
WPテーマ:twentytwenty作業
投稿ページや固定ページのパーマリンクは、デフォルトだとタイトルに入力した文字がそのまま入力されます。
URLに日本語が含まれるとエンコードされてごちゃごちゃしてしまいます。
記事のパーマリンクをデフォルトで連番にしてすっきりさせましょう。まずは、パーマリンク設定で「カスタム構造」を選択してください。記入欄は %postname% のままで大丈夫です。
次に、functions.php に、下記コードを任意の場所に追記します。functions.php// スラッグ連番付番 function incliment_slug( $slug ) { global $post; // 投稿ページのみ if ( ( $slug ) || ( $post->post_type == 'page' ) ) { return $slug; } global $wpdb; $slug = $wpdb->get_var( "select max( cast( post_name as unsigned ) ) from { $wpdb->posts } where post_type = 'post' and post_status not in ( 'object', 'attachment', 'inherit' ) and post_name regexp '^[0-9]+$'" ); $slug ++; return $slug; } // フィルターフック add_filter( 'editable_slug', 'incliment_slug' );
- 投稿日:2020-12-08T12:34:53+09:00
【PHP】本番環境と開発環境が同じドメイン内で構築されている場合のファイルパス分岐処理
はじめに
フロント実装者でもときどきPHPを用いた実装を担当することがあると思います。
今回は実際にあった例を用いて、対策を共有できればと思います。今回のケース
・別の制作会社さんがPHPで作成・運営していたサイトを引き継いだ
・同じドメイン内に本番環境(ドメイン直下)と開発環境(例:ドメイン/develop)がある
・構成を完璧には引き継いでいない(もはや分かる人がいないそうな)ので、できるだけ改修せずにローカロリーで対応する問題点
前述のとおり本番・開発でディレクトリが違うのだが、PHPでパスルート分岐などをしていないので、作業ファイルをまるっとコピペができない。
よって、開発環境で更新作業を行い、OKが出たら差分箇所を漏れなく本番環境用ファイルに反映させなければならない。大変な二度手間だ。やりたくない。ムキー対応策
結論から書くと、パスルートを下記の記述に統一させることで対応しました。
<?php // ドキュメントルートとディレクトリ構成を取得 $doc_root = $_SERVER['DOCUMENT_ROOT']; $dir = explode('/',$_SERVER["REQUEST_URI"]); // 開発環境(develop)配下のファイルにはパスルートに /develop を付与 if( $dir[1] == 'develop' ) { $current_dir = $doc_root . '/develop'; } else { $current_dir = $doc_root; }これをconfig.php とかにして読み込ませつつ、
<?php require($_SERVER["DOCUMENT_ROOT"] . '/develop/inc/header.php'); ?>上記のように書かれていたインクルード部分を
<?php require($current_dir.'/inc/header.php'); ?>と書き換えました。
これにより、記述の差異が無くなるので本番・開発用ファイルを1つに統合でき、二度手間は解消されました。まとめ
どういう経緯でこのような構成になっていたのかは分かりませんが、設計の段階で運用の面倒さ、リスクはある程度予測できたと思うので、もうひと工夫しておいてくれればゴニョゴニョと思いました。
でもPHPは昔すこしだけ勉強していたが役に立った気がして、それはそれで良かったと思いましたまる変なところや、こういうやり方あるよーっていうのがあればご教示いただけると大変ありがたいです。
参考にさせていただいたURL
https://www.php.net/manual/ja/function.set-include-path.php
https://webnetamemo.com/coding/php/201803016775
- 投稿日:2020-12-08T12:34:40+09:00
WordPressでカテゴリーで記事を絞り込む(管理画面)
ワードプレスのテーマtwentytwentyを基盤に、管理画面の投稿一覧ページにおいて、投稿をカテゴリーで絞り込めるようにする機能の実装を行ったので、備忘録的にこの記事を残す。
環境情報
PHP:version 7.3.12
WordPress:version 5.5.3
WPテーマ:twentytwenty作業
今回は投稿一覧ページにカテゴリーを表示させて、絞り込みできるようにしようと思います。
下記のfunctions.phpの任意の場所に追記します。functions.phpfunction my_add_filter() { global $post_type; if ( 'news' == $post_type ) { ?> <select name="tax_news"> <option value="">タクソノミー指定なし</option> <?php $terms = get_terms( 'tax_news' ); foreach ( $terms as $term ) { ?> <option value="<?php echo $term->slug; ?>" <?php if ( $_GET['tax_news'] == $term->slug ) { print 'selected'; } ?>><?php echo $term->name; ?></option> <?php } ?> </select> <?php } } add_action( 'restrict_manage_posts', 'my_add_filter' );
こんな感じにカテゴリーが表示され、絞り込みができるようになりました。
newsやtax_newsなどは、カスタム投稿やタクソノミーを作成したときのものを入力してください。
- 投稿日:2020-12-08T12:31:45+09:00
WordPressで投稿編集画面をビジュアルエディタにする
ワードプレスのテーマtwentytwentyを基盤に、WordPressの投稿編集画面をビジュアルエディタで編集できるようにする実装を行ったので、備忘録的にこの記事を残す。
環境情報
PHP:version 7.3.12
WordPress:version 5.5.3
WPテーマ:twentytwenty作業
htmlの知識があまりない人なら、意味不明なコードが書いてあるテキストエディタよりビジュアルエディタの方を使いたいですよね。
ビジュアルエディタで編集すると、WordPressの仕様でpタグやspanタグが自動挿入されますが、静的コーディングの段階でその想定をしていないと大幅に崩れてしまうことがあります。以下の点に注意してコーディングしましょう。
・記事コンテンツエリア内の要素にはクラスなしでデザインがあたるようにする。
・要素がどんな順番で置かれても余白が不自然にならないようにする。また、ビジュアルエディタで記事を作成・編集するときに実際のウェブ上のデザインが当たっているようにするととても分かりやすいのでおすすめです。
ビジュアルエディタを実際のサイト上のデザインに近づけるための方法をご紹介いたします。
editor_style.css(エディタ用CSS)に、サイト用のCSSをインポートする方法です。
WP構築時に、サイトのスタイルを記述しているCSSファイルに、ビジュアルエディタ上に適用させるためのクラス「.wp-editor」も追加します。style.css.news-block article h2,//実際のサイト用のクラス .wp-editor h2 {//管理画面用のクラス border-bottom: 4px solid #eaaeb1; position: relative; padding-bottom: 10px; font-size: 24px; margin: 60px 0 20px; } .news-block article h2,//実際のサイト用のクラス .wp-editor h2:after {//管理画面用のクラス position: absolute; content: ""; display: block; border-bottom: 4px solid #ca353b; bottom: -4px; width: 33%; }このように管理画面にもCSSが当たるようにクラスを追記したら、そのCSSファイル(以下の例の場合style.css)をeditor_style.cssでインポートします。
editor_style.css//editor_style.cssに記述 @import url("style.css");最後にそのeditor_style.cssをfunctions.phpで読み込みます。
functions.php// editor_style.css読み込み function add_editor_style_cb() { add_editor_style(); } add_action('admin_init', 'add_editor_style_cb');これでビジュアルエディタにも共通のスタイルが適用されるようになります。
プレビューを見なくてもビジュアルエディタ上でデザインが確認できるので便利ですね。
- 投稿日:2020-12-08T12:28:24+09:00
WordPressでバージョンアップの警告バナーを消す
ワードプレスのテーマtwentytwentyを基盤に、管理者画面に表示されるWordPressのバージョンアップを促すバナーを非表示にする実装を行ったので、備忘録的にこの記事を残す。
環境情報
PHP:version 7.3.12
WordPress:version 5.5.3
WPテーマ:twentytwenty作業
WordPressの新しいバージョンが公開されると、ダッシュボードにこのような通知が出てきます。
「今すぐ更新してください。」と書いてあると、クライアントは「更新しないと何かまずいのかな?」と感じてアップデートしてしまうかもしれません。
そのようなことがないように、ダッシュボードの更新通知は管理者のみに表示させるようにして、他の権限グループでは非表示にしておきましょう。
ユーザー権限の分け方については後述の「ユーザーの権限を分ける」にて紹介しています。管理者権限以外で更新通知を非表示にするには、以下のコードをfunctions.phpの任意の場所に追記してください。
//更新通知を管理者権限のみに表示 function update_nag_admin_only() { if ( ! current_user_can( 'administrator' ) ) { remove_action( 'admin_notices', 'update_nag', 3 ); } } add_action( 'admin_init', 'update_nag_admin_only' );
これで管理者権限以外のユーザーには更新通知が届かなくなりました。詳しくは下記の参考サイトをご覧ください。
管理画面に表示される更新通知を非表示にするまた、WordPressのデフォルトの設定では、メジャーバージョン・マイナーバージョンは自動で更新されないようになっていますが、ビルドバージョンは自動更新となっています。
ビルドバージョンの更新内容は細かな不具合の修正で、バグやセキュリティホールが見つかった場合に修正をあててくれます。ビルドバージョンの自動更新は切らない方がよいでしょう。
- 投稿日:2020-12-08T11:16:51+09:00
WordPressで子テーマを作るための準備
ワードプレスのテーマ twentytwenty を基盤に、子テーマを作る実装を行ったので、備忘録的にこの記事を残す。
環境情報
PHP:version 7.3.12
WordPress:version 5.5.3
WPテーマ:twentytwenty作業
WordPress の公式テーマなどで配布されているオリジナルテーマに独自に機能を追加したり別のテンプレートを追加したい場合には、「子テーマ」という仕組みを利用します。
もし、オリジナルのテーマを直接変更した場合、テーマのアップデートの際にアップデートの仕組み上最新バージョンのテーマのファイルに上書きされてしまい、自分が行った変更が消えてしまいます。
その状況を防ぐため、親テーマを継承した子テーマを作成してカスタマイズを行う手法をとります。
今回は、例として Twenty Twenty のテーマを継承して作成していきます。「twentytwenty_child」という子テーマを作成します。ディレクトリとファイルの作成
wp_content/themes 以下に twentytwenty_child というディレクトリを作成し、その中に index.php、style.css、functions.php の3つのファイルを作成する。
style.css の編集
子テーマの style.css に以下を記述する。
style.css/* Theme Name: Twenty Twenty Child <--- 子テーマの名前 Author: the WordPress team <--- 子テーマの作成者 Template: twentytwenty <--- 親テーマのディレクトリ名 Description: Twenty Twentyの子テーマです。 <--- テーマの説明 Version: 1.0 <--- 子テーマのバージョン */
index.php の編集
子テーマの index.php は親テーマの index.php のコピペでよいが、必要に応じて変更箇所を編集することができる。
functions.php の編集
親テーマのスタイルシートを読み込むようにするために、wp_enqueue_scripts をフックに、wp_enqueue_style で style.css を読み込むようにします。
以下を functions.php に記述します。functions.phpadd_action( 'wp_enqueue_scripts', 'theme_enqueue_styles' ); function theme_enqueue_styles() { wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' ); wp_enqueue_style( 'child-style', get_stylesheet_directory_uri() . '/style.css', array( 'parent-style' ) ); }管理画面から有効化
WordPressの管理画面から「外観」>「テーマ」>「Twenty Twenty Child」が表示されるので、「有効化」をクリックします。
- 投稿日:2020-12-08T10:39:39+09:00
WordPressの独自リソースを一元管理する
ワードプレスのテーマtwentytwentyを基盤に、wp_head( )内の独自のスクリプト、スタイルシートをfunctions.php内で一元管理する実装を行ったので、備忘録的にこの記事を残す。
環境情報
PHP:version 7.3.12
WordPress:version 5.5.3
WPテーマ:twentytwenty作業
functions.php内に以下のコードを記述する。
functions.phpfunction my_enqueue_scripts() { wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'bundle_js', get_template_directory_uri() . '/assets/js/bundle.js', array() ); wp_enqueue_style( 'my_styles', get_template_directory_uri() . '/assets/css/styles.css', array() ); } add_action( 'wp_enqueue_scripts', 'my_enqueue_scripts' );その後、header.php内の以下の3行を削除する。
header.php<link rel="stylesheet" type="text/css" href="<?php echo get_template_directory_uri(); ?>/assets/css/styles.css" /> <script type="text/javascript" src="<?php echo get_template_directory_uri(); ?>/assets/js/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="<?php echo get_template_directory_uri(); ?>/assets/js/bundle.js"></script>
- 投稿日:2020-12-08T10:27:26+09:00
WordPressテーマ開発で最低限必要なファイル
下記の方向け
・WordPressテーマ開発したい
・取りあえす表示させたい
・php初心者WordPressテーマ開発で最低限必要なファイル
では、WordPressでテーマをアップロードしたとき最低限必要なファイル(構成)についてです。
今回、紹介するのは
WordPressの設定でテーマとして認識されるための構成です。次のファイルを同じ階層にまとめて、アップロードすれば
とりあえず動くようになります。index.php
中身は何でも可能です。
ひとまず、「Hello World」的な文言でも書いておきます。HTMLのベタ書きでもOKです。
style.css
style.css/* Theme Name: サイトorブログ名等々 */
他に、記載しとくと良いものも合わせると
下のようになります。style.css/* Theme Name: サイトorブログ名等々 Description: テーマの説明 Version: 1.0.0 Author: テーマの作成者 */
まとめ
以上です。
あとは上の2つのファイルを1つのファイルにまとめてWordPressにアップロードして確認してみて下さい。
- 投稿日:2020-12-08T10:25:04+09:00
Laravel8で完成されたModelFactoryの使い方
Laravel Advent Calendar 2020 - Qiita の 8日目 の記事です。
昨日は @okdyy75 さんのLaravelを触って1年経ったのでTIPSの記事でした!
明日は @kaino5454 さんの記事です!概要
Laravel8からModelFactoryの機能が刷新されました。
新しい書き方についてまとめたいと思います。こちらの記事をリスペクトしてタイトル名に使わせていただきました???
予備知識
Laravelのデータベース関連の用語として、Migration、Seeder、ModelFactory、Fakerの4つあります。
Migration(マイグレーション)
https://readouble.com/laravel/8.x/ja/migrations.html
マイグレーションはデータベースのテーブルの定義を書きます。
英語で「移動・移行」という意味になります。テーブルの変更のソースコードで管理することができます。
そのため本番環境へリリースしたり、新しいメンバーが参画した際にも簡単に同じテーブルの状態を再現できます。artisan コマンド使用例
$ php artisan make:migration create_posts_table # マイグレーションのテンプレート生成 $ php artisan migrate # マイグレーション実行 $ php artisan migrate:fresh # 全テーブル削除、全マイグレーション再実行 $ php artisan migrate:fresh --seed # シーディングも行う同じ名前のマイグレーションファイルはエラーになるので、注意して命名していく必要があります。
1ファイルで複数テーブルのマイグレーション定義は書かないほうが良いです。Seeder(シーダー)
https://readouble.com/laravel/8.x/ja/seeding.html
シーダーはデータベースにダミーデータを一斉に挿入できる機能です。
英語で「種をまく人」という意味になります。(Seeding/シーディングとも表記されます)artisan コマンド使用例
$ php artisan make:seeder PostsSeeder # シーダーのテンプレート生成 $ php artisan db:seed # DatabaseSeeder を実行 $ php artisan db:seed --class=PostsSeeder # クラス指定して実行テーブル単位やデータのまとまり毎に書くと良いでしょう。
ModelFactory(モデルファクトリ)
https://readouble.com/laravel/8.x/ja/database-testing.html#creating-factories
モデルファクトリはEloquentモデルの各フィールドに入る値を定義します。
モデルの工場といったところです。主にシーダーやテストコードからモデルファクトリーは呼び出されます。
artisan コマンド使用例
$ php artisan make:factory PostFactory # モデルファクトリのテンプレート生成Facker(フェイカー)
フェイカーはダミーデータ生成用のライブラリです。
主にモデルファクトリを定義する際に使用します。名前や住所、メールアドレスといったよく使われるテストデータをランダムに生成してくれる便利な機能です。
補足: fzaninotto/Fakerのアーカイブ
最近の話(2020年10月21日)ですが、 fzaninotto/Faker の本家のリポジトリがアーカイブされています。
詳細については、下記のリンクをご参照ください。
- https://github.com/fzaninotto/Faker
- https://marmelab.com/blog/2020/10/21/sunsetting-faker.html
- https://twitter.com/taylorotwell/status/1321091021342650377
今後はフォーク先のリポジトリでメンテナンスが続けられるそうです。
また、こちらのプルリクエスト で既にライブラリの変更が行われてます。
- v8.12.0 (2020-10-29)
- v7.29.0 (2020-10-29)
- v6.20.0 (2020-10-28)
上記のバージョン以降のLaravelをご利用であればフォーク先のリポジトリが使用されます。
tinkerでお試し実行方法
ModelFactory, Fakerはtinker上で簡単にお試しできます。
どんなテストデータが生成されるのか気になる場合は、tinkerで試してみましょう!ModelFactoryのお試し実行方法
$ php artisan tinker # make() だとインスタンスのみ返す >>> App\Models\User::factory()->make() => App\Models\User {#3385 name: "Miss Dolores Mueller III", email: "marcus19@example.org", email_verified_at: "2020-12-06 14:58:34", } # create() だとデータベースに挿入してインスタンスを返す >>> App\Models\User::factory()->create() => App\Models\User {#3387 name: "Annabel Upton III", email: "kenton.klocko@example.org", email_verified_at: "2020-12-06 14:58:39", updated_at: "2020-12-06 14:58:39", created_at: "2020-12-06 14:58:39", id: 1, }Fakerのお試し実行方法
詳しい使い方はこちら https://fakerphp.github.io
$ php artisan tinker >>> $faker = app()->make(Faker\Generator::class) >>> $faker->name() => "中津川 和也" # () なしでメソッドではなくプロパティへのアクセスでも実行可能 >>> $faker->name => "佐々木 学" >>> $faker->safeEmail() => "kondo.yui@example.com" >>> $faker->emoji() => "?" >>> $faker->imageUrl() => "https://via.placeholder.com/640x480.png/0066ee?text=aut" >>> $faker->dateTimeBetween('-2weeks', '9 days')->format('Y-m-d H:i:s') => "2020-11-28 05:24:51"設定
Faker 日本語化
config/app.php
のfaker_locale
の値を変更してください。config/app.php/* |-------------------------------------------------------------------------- | Faker Locale |-------------------------------------------------------------------------- | | This locale will be used by the Faker PHP library when generating fake | data for your database seeds. For example, this will be used to get | localized telephone numbers, street address information and more. | */ // 'faker_locale' => 'en_US', 'faker_locale' => 'ja_JP',実行すると日本語名のデータが入るようになります。
>>> App\Models\User::factory()->make()->name => "田中 桃子"
本題
横道に逸れてしまいましたが、ここからが本題のModelFactoryです。
シーダーからテストデータを挿入する
シーダーからテストデータをデータベースに挿入したい場合、こんな感じで生成することができます。
database/seeders/DatabaseSeeder.php<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; class DatabaseSeeder extends Seeder { public function run(): void { DB::table('users')->insert([ 'name' => Str::random(10), 'email' => Str::random(10).'@gmail.com', 'password' => Hash::make('password'), ]); } }数件程度であれば良いですが、開発が進むにつれて複雑なテストデータを作ろうとしていくとメンテナンスがとても大変になってしまいます...。
デフォルトで用意されているモデル
app/Models/User.php<?php namespace App\Models; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { use HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'email_verified_at' => 'datetime', ]; }
Illuminate\Database\Eloquent\Factories\HasFactory
trait を use する必要があります。デフォルトで用意されているモデルファクトリ
database/factories/UserFactory.php<?php namespace Database\Factories; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; class UserFactory extends Factory { /** * The name of the factory's corresponding model. * * @var string */ protected $model = User::class; /** * Define the model's default state. * * @return array */ public function definition() { return [ 'name' => $this->faker->name, 'email' => $this->faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; } }tinker からモデルファクトリーを呼び出して使う
$ php artisan tinker App\Models\User::factory()->create() => App\Models\User {#3380 name: "近藤 結衣", email: "uno.naoki@example.net", email_verified_at: "2020-12-06 15:43:28", updated_at: "2020-12-06 15:43:28", created_at: "2020-12-06 15:43:28", id: 3, } // name 属性を上書きする App\Models\User::factory()->create(['name' => 'ucan']) => App\Models\User {#4264 name: "ucan", email: "sato.yuki@example.com", email_verified_at: "2020-12-06 15:43:46", updated_at: "2020-12-06 15:43:46", created_at: "2020-12-06 15:43:46", id: 4, }
- モデルファクトリを使うと複数代入の保護を自動的に無効化します。
Laravel7との違い
database/factories/UserFactory.php<?php /** @var \Illuminate\Database\Eloquent\Factory $factory */ use App\User; use Faker\Generator as Faker; use Illuminate\Support\Str; /* |-------------------------------------------------------------------------- | Model Factories |-------------------------------------------------------------------------- | | This directory should contain each of the model factory definitions for | your application. Factories provide a convenient way to generate new | model instances for testing / seeding your application's database. | */ $factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; });呼び出し方
factory(App\User::class)->create();以前は
$factory->define()
関数を使って登録、factory()
関数を使って呼び出ししていく形でしたが、Laravel 8からクラスベースとなりました。基本の流れ
posts テーブル
テーブルがないとテストデータを入れれないので、下記のようにマイグレーションファイルを作っておきます。
$ php artisan make:migration create_posts_table
database/migrations/YYYY_MM_DD_XXXXXX_create_posts_table.php
といったマイグレーションファイルが生成されるので、下記のように追記しておきます。database/migrations/2020_12_08_000000_create_posts_table.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreatePostsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->foreignId('user_id'); $table->string('title'); $table->text('content'); $table->string('published_at')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('posts'); } }$ php artisan migrateモデル、モデルファクトリークラスの生成
新しく自分でファイルを作成しても良いですが、テンプレートを生成する便利コマンドがあるので使いましょう。
# モデルとファクトリー個別に生成する $ php artisan make:model Post $ php artisan make:factory PostFactory --model=Post or # モデルとファクトリーを生成する $ php artisan make:model Post --factory $ php artisan make:model Post -f # 省略オプション or # モデルとファクトリー、マイグレーション、シーダー、コントローラを生成する $ php artisan make:model Post --all $ php artisan make:model Post -a # 省略オプション生成されるファイル
app/Models/Post.php
database/factories/PostFactory.php
app/Models/Post.php<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Post extends Model { use HasFactory; }database/factories/PostFactory.php<?php namespace Database\Factories; use App\Models\Post; use Illuminate\Database\Eloquent\Factories\Factory; class PostFactory extends Factory { /** * The name of the factory's corresponding model. * * @var string */ protected $model = Post::class; /** * Define the model's default state. * * @return array */ public function definition() { return [ // ]; } }
database/factories/PostFactory.php
のdefinition()
を書き換えてみます。database/factories/PostFactory.phppublic function definition() { return [ 'user_id' => \App\Models\User::factory(), 'title' => $this->faker->title, 'content' => $this->faker->paragraph, ]; }シーダーへ追記します。
database/seeders/DatabaseSeeder.phppublic function run(): void { \App\Models\Post::factory()->count(3)->create(); }$ php artisan migrate:fresh --seed $ php artisan tinker >>> App\Models\User::count(); => 3 >>> App\Models\Post::count(); => 3 >>> App\Models\User::first(); => App\Models\User {#4044 id: 1, name: "斉藤 里佳", email: "yamamoto.asuka@example.org", email_verified_at: "2020-12-06 16:43:25", created_at: "2020-12-06 16:43:25", updated_at: "2020-12-06 16:43:25", } >>> App\Models\Post::first(); => App\Models\Post {#3639 id: 1, user_id: 1, title: "Dr.", content: "Aspernatur maiores qui neque iure cupiditate. Facilis voluptas consequatur mollitia placeat ea. Aperiam neque qui quae placeat debitis nobis minima.", published_at: null, created_at: "2020-12-06 16:43:25", updated_at: "2020-12-06 16:43:25", }Laravelではこんな流れでテストデータを作成していきます。
詳細な使い方
https://readouble.com/laravel/8.x/ja/database-testing.html
公式ドキュメントに詳しい使い方が記載されているので詳細はこちらを参考すると良いです。
補足
公式ドキュメントに書かれてないけど、知ってると便利そうなことをいくつかご紹介します。
Fakerに独自のメソッドを追加する
FakerServiceProvider
を作成します。$ php artisan make:provider FakerServiceProvider
FakerServiceProvider
を登録します。config/app.php'providers' => [ App\Providers\FakerServiceProvider::class, ],
FakerServiceProvider
を登録します。app/Providers/FakerServiceProvider.php<?php declare(strict_types=1); namespace App\Providers; use App\Faker\Planet; use Faker\Generator; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Support\ServiceProvider; class FakerServiceProvider extends ServiceProvider { /** * Bootstrap services. * * @return void * @throws BindingResolutionException */ public function boot(): void { $faker = $this->app->make(Generator::class); $faker->addProvider(new Planet($faker)); } }app/Faker/Planet.php<?php declare(strict_types=1); namespace App\Faker; use Faker\Provider\Base; /** * 惑星をランダムに返す */ class Planet extends Base { protected array $planet = [ '水星', '金星', '地球', '火星', '木星', '土星', '天王星', '海王星', ]; /** * @return string */ public function planet(): string { return $this->generator->randomElement($this->planet); } }$ php artisan tinker >>> $faker = app()->make(Faker\Generator::class) >>> $faker->planet() => "海王星"モデルファクトリーのリレーション定義
ファクトリークラスにリレーションの定義も行えます。
ただ私個人としては、ファクトリークラスはなるべくシンプルに保ち、シーダークラスに生成ルールを書いた方が読みやすいかなと思ってます。連番を生成する
連番のテストデータを作りたい時は、staticなプロパティを作ると良いです。
class UserFactory extends Factory { private static $sequence = 1; public function definition() { return [ 'name' => sprintf('No. %d %s', self::$sequence++, $this->faker->name), ]; }実行するとこんな感じです。
>>> App\Models\User::factory()->make()->name => "No. 1 青山 明美" >>> App\Models\User::factory()->make()->name => "No. 2 工藤 七夏" >>> App\Models\User::factory()->make()->name => "No. 3 渡辺 稔"$faker->email は使うな! $faker->safeEmail を使え!
$ php artisan tinker >>> $faker = app()->make(Faker\Generator::class) >>> $faker->email => "kyosuke.sasada@gmail.com" >>> $faker->safeEmail => "kondo.yui@example.com"実行結果を見るとわかりますが、
@gmail.com
など実在するドメインのテストデータが生成されます。
開発環境でメールの誤送信を防止するため$faker->safeEmail
を使うようにしましょう。Faker nullデータをランダムで入れたい
->optional()
を使うとランダムでnull値が入ります!public function definition() { return [ // ランダムでnullを入れたい時 'completed_at' => $this->faker->boolean ? $this->faker->dateTime : null, // Fakerのoptional()を使うとランダムでnullを入れてくれます(オススメ) 'completed_at' => $this->faker->optional()->dateTime, // 80%の確率で日付データが入り、20%の確率でnullが入ります 'completed_at' => $this->faker->optional(0.8)->dateTime, ]; }状態に応じたテストデータを作成する
公式サイトに詳しく記述されているので、ここで書く必要ないですがとても便利な機能なのでご紹介します。
※お試しする場合はマイグレーションファイルに下記を追記して、
php artisan migrate:fresh
してください。database/migrations/2014_10_12_000000_create_users_table.php$table->string('account_status');database/factories/UserFactory.php<?php namespace Database\Factories; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; class UserFactory extends Factory { /** * The name of the factory's corresponding model. * * @var string */ protected $model = User::class; /** * Define the model's default state. * * @return array */ public function definition() { return [ 'name' => $this->faker->name, 'account_status' => 'init', 'email' => $this->faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; } /** * そのユーザーが有効化(activated)されていることを表す * * @return Factory */ public function activated(): Factory { return $this->state(function () { return [ 'account_status' => 'activated', ]; }); } /** * そのユーザーが資格保留(suspended)されていることを表す * * @return Factory */ public function suspended(): Factory { return $this->state(function () { return [ 'account_status' => 'suspended', ]; }); } }$ php artisan tinker # 仮登録(init)状態のユーザーを作成 >>> App\Models\User::factory()->create() => App\Models\User {#3346 name: "中津川 あすか", account_status: "init", email: "tanaka.momoko@example.org", email_verified_at: "2020-12-06 18:30:21", updated_at: "2020-12-06 18:30:21", created_at: "2020-12-06 18:30:21", id: 7, } # 有効化(activated)状態のユーザーを作成 >>> App\Models\User::factory()->activated()->create() => App\Models\User {#3343 name: "石田 京助", account_status: "activated", email: "harada.yuta@example.com", email_verified_at: "2020-12-06 18:30:23", updated_at: "2020-12-06 18:30:23", created_at: "2020-12-06 18:30:23", id: 8, } # 資格保留(activated)状態のユーザーを作成 >>> App\Models\User::factory()->suspended()->create() => App\Models\User {#4090 name: "伊藤 稔", account_status: "suspended", email: "taichi.sugiyama@example.com", email_verified_at: "2020-12-06 18:30:24", updated_at: "2020-12-06 18:30:24", created_at: "2020-12-06 18:30:24", id: 9, }モデルの状態によって必要な埋めたいデータは異なるので、状態に応じてモデルファクトリを定義できるのはとても便利です!
db:seed でテストデータをクリアする
先週書いた記事ですが、こちらもやっておくと便利です。
Laravel 8 のリリース記事
Laravel 8の新しい機能を知りたい方はこちらの記事をご覧ください。
- 投稿日:2020-12-08T10:11:26+09:00
※学習用メモ デザインパターン:Mediator編[PHP]
Mediatorパターンとはどういうものか
Mediatorとは仲介者という意味です。
プログラムを組んでいく中で、様々なクラスがお互いにやり取りする状態が多くなり、複雑な構造となることがしばしばあります。
そこでオブジェクト間のやり取りを仲介するmediatorを配置することで、オブジェクト同士で直接通信を行うことなくお互いの依存関係を削減することができます。登場するクラス
Mediator:オブジェクト間のやり取りを仲介するクラス
Colleague:Mediatorによってやり取りされる側のクラス[参考サイト]
https://ja.wikipedia.org/wiki/Mediator_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
https://bmf-tech.com/posts/PHP%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%20-%20Mediator%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
https://qiita.com/i-tanaka730/items/9f96d28d32ab3c9099d9#4-2-loginframe%E3%82%AF%E3%83%A9%E3%82%B9
https://www.ritolab.com/entry/137Mediatorパターンを用いるメリット
・オブジェクト間のやり取りがmediatorによって行われるため、処理がカプセル化される。
・オブジェクト同士の複雑な依存関係を集約できる。サンプルコード
仕様
・出社しているアルバイトに仕事を割り振る
・料理ができる人は優先的にキッチンへ
・シフトがいっぱいになったら出社できないようにアルバイトのクラス(Colleague)
クラス内でのちに登場するMediatorクラスを保持しています。
arriveAtWork(出社)メソッドを呼ぶことで、Mediator側にWokerクラスの状態判断を委ねます。
つまり、このクラス内で他のオブジェクトとの関係性を気にする必要がなくなるという事です。Worker.class.phpclass Worker { private $name = ""; private $goodCookFlg = 0; private $workerMediator = null; public function __construct($name, $goodCookFlg) { $this->name = $name; $this->goodCookFlg = $goodCookFlg; } public function setWorkerMediator($workerMediator) { $this->workerMediator = $workerMediator; } public function getRole() { return $this->workerMediator->getRole($this->name); } public function getName() { return $this->name; } public function getGoodCookFlg() { return $this->goodCookFlg; } }Mediatorクラス
このMediatorクラスで各Wokerクラスの状態を判断し結果を指定しています。
(コンパクトさに欠ける形となってしまいましたが、、^^;)
先にも記述したように、Mediator側で各クラスの状態に応じた判断を行うため、
Wokerクラス内で他のオブジェクトとの関係性を気にせず、すべての判断をこのMediatorクラス内に集約できます。WorkerMediator.class.phpclass WorkerMediator { const KITCHEN = 1; const FLOOR = 2; const WORKER_CAPACITY = 4; private $workersInfo = array(); public function arriveAtWork($worker) { $worker->setWorkerMediator($this); $this->divisionOfRoles($worker->getName(), $worker->getGoodCookFlg()); } // 他オブジェクトの状態に応じて役割分担 private function divisionOfRoles($name, $goodCookFlg) { if (count($this->workersInfo) >= self::WORKER_CAPACITY) { return 0; } if (count($this->workersInfo) == 0) { if ($goodCookFlg) { $this->workersInfo[$name] = self::KITCHEN; } else { $this->workersInfo[$name] = self::FLOOR; } } else { $counts = array_count_values($this->workersInfo); if ($goodCookFlg) { if ($counts[self::KITCHEN] >= self::WORKER_CAPACITY - 1) { $kitchenArray = array_keys($this->workersInfo, self::KITCHEN); $this->workersInfo[$kitchenArray[0]] = self::FLOOR; $this->workersInfo[$name] = self::KITCHEN; } else { $this->workersInfo[$name] = self::KITCHEN; } } else { if (!in_array(self::KITCHEN, $this->workersInfo)) { $this->workersInfo[$name] = self::KITCHEN; } elseif (!in_array(self::FLOOR, $this->workersInfo)) { $this->workersInfo[$name] = self::FLOOR; } elseif ($counts[self::KITCHEN] > $counts[self::FLOOR]) { $this->workersInfo[$name] = self::FLOOR; } else { $this->workersInfo[$name] = self::KITCHEN; } } } } public function getRole($name) { if (!array_key_exists($name, $this->workersInfo)) { return $name ."さんはシフトがいっぱいなので出社できません。\n"; } elseif ($this->workersInfo[$name] == self::FLOOR) { return $name . "さんはフロアです。\n"; } else { return $name . "さんはキッチンです。\n"; } } }使ってみる
5人のアルバイトが順番に出社
料理できる人にはフラグが立っている$workerMediator = new WorkerMediator(); $tanaka = new Worker('田中', 1); $sato = new Worker('佐藤', 1); $kato = new Worker('加藤', 0); $tsuji = new Worker('辻', 1); $kusunoki = new Worker('楠木', 1); // 出社 $workerMediator->arriveAtWork($tanaka); $workerMediator->arriveAtWork($sato); $workerMediator->arriveAtWork($kato); $workerMediator->arriveAtWork($tsuji); $workerMediator->arriveAtWork($kusunoki); echo $tanaka->getRole(); echo $sato->getRole(); echo $kato->getRole(); echo $tsuji->getRole(); echo $kusunoki->getRole(); /*結果 田中さんはキッチンです。 佐藤さんはキッチンです。 加藤さんはフロアです。 辻さんはキッチンです。 楠木さんはシフトがいっぱいなので出社できません。 */まとめ
・Mediatorパターンは、各Colleagueクラス間での相互関係などによるやり取りをすべて1点に集約することで全体の構造をシンプルに、かつオブジェクト同士の依存関係を削減できる。
・サンプルコードにて、出社したのに入れないのは仕様として問題ありですね。。楠木さんごめんなさい。。
- 投稿日:2020-12-08T09:52:02+09:00
Composerパッケージを作ろう その② - Laravelパッケージ編
はじめに
今回はLaravelのComposr自作パッケージの公開手順を書いていきます。
基本的な手順は前回の記事と同様なので、まだご覧になっていない人はこちらの記事から先に読むことをお勧めします。下準備
ディレクトリやファイルの構成や準備などは前回の記事を参考にしてください。
Laravelでパッケージを自作する際はLaravelというFWのコアとなる
サービスプロバイダ
をパッケージ側にも実装します。Laravel5.5以降からは
Package Auto Discovery
という仕組みが導入されており、作成するパッケージのcomposer.json
に以下の様に追記しておくことでサービスプロバイダを自動的に読み込んでくれます。"extra": { "laravel": { "providers": [ "Vendor\\PackageName\\PackageServiceProvider" ] } }
サービスプロバイダ
内でルートを生成したり、作成したルートにミドルウェアを挟んだりすれば、デバッグツールの様なものを作成することも可能です。ちなみに自分は以下の様なパッケージを公開しています。
temori/artisan-browserブラウザ上でターミナル風の画面を表示してルートの確認や
artisan
コマンドを実行するための開発ツールになります。このパッケージでもサービスプロバイダでルートを生やして、ミドルウェア内でhtmlの要素やjs,cssを表示しているviewに追加する形でツールの画面を表示させています。
基本的な構成は前回の記事と同様ですが、Laravel用にディレクトリを若干追加しています。
以下が作成したパッケージのディレクトリになります。
├── artisan-browser │ ├── LICENSE │ ├── README.md │ ├── composer.json │ ├── config │ │ └── artisanbrowser.php │ └── src │ ├── ArtisanBrowser.php #このパッケージの基幹クラス │ ├── ArtisanBrowserServiceProvider.php #サービスプロバイダー │ ├── Controllers #ルートを生成したので、そこで呼び出すコントローラー │ │ ├── AjaxController.php │ │ └── AssetController.php │ ├── Facades │ │ └── ArtisanBrowser.php │ ├── Logs │ │ └── HistoryLogger.php │ ├── Middleware #パッケージのhtml要素を全ページにレンダリングするためのミドルウェア │ │ └── Inject.php │ ├── Renderer │ │ ├── AssetRenderer.php │ │ └── HtmlRenderer.php │ ├── Resources │ │ ├── artisanBrowser.css │ │ ├── artisanBrowser.js │ │ ├── image │ │ │ └── logo.png │ │ ├── vendor │ │ │ ├── draggabilly.pkgd.min.js │ │ │ ├── jquery-1.12.4.min.js │ │ │ └── suggest.js │ │ └── views │ │ └── index.blade.php │ └── helpers.php作成するパッケージによってディレクトリを適宜作成してください。
パッケージの処理を実装する
ここも前回の記事と一緒ですが、もし設定ファイル等で挙動を制御する様な場合、
パッケージ使用者に設定ファイルを編集してもらいたい場面が出てくるかと思います。その様な場合はLaravelにはアプリケーション側にassetを配置する仕組みが存在しているので、それを活用しましょう。
サービスプロバイダの
boot
メソッド内に$this->publishes([ __DIR__ . '/../config/artisanbrowser.php' => config_path('artisanbrowser.php', 'config'), ]);の様な記述をしておくと
# forceオプションはすでにファイルが存在していても上書きでファイルを配置する php artisan vendor:publish --tag=config [--force]で設定ファイルがアプリケーション側へ配置されます。
もしくはアプリケーション側とvendor下のパッケージの設定ファイルをマージすることもできます。
register
メソッド内に以下の様に定義すればパッケージ側の設定をデフォルトとして使用することができ、ユーザーは変更したい値のみオーバーライドすればパッケージ側とアプリケーション側の設定値をマージしてくれます。$this->mergeConfigFrom( __DIR__ . '/../config/artisanbrowser.php', 'artisanbrowser' );詳細は公式のパッケージ開発に詳しく書かれています。
パッケージの実装が完了したらその後の手順は前回の記事と同様です。
最後に
2回に分けて
Composer
自作パッケージの流れを書いていきましたがいかがでしたでしょうか?
ざっくりとしか流れしか書いていませんが、そこまでハードルの高いものではないと少しでも感じてもらえたら嬉しいです手始めに自分が使う用にパッケージを作ってみるのもありかも知れません。
- 投稿日:2020-12-08T09:50:54+09:00
Composerパッケージを作ろう その①
はじめに
PHPを使っていてComposerのお世話になっていない人はいないと思いますが、公開されているパッケージを使用するだけではなく、自作のパッケージを公開してやろうという人向けの記事になります。
OSS活動をしていきたいけど具体的な手順はどうなっているんだーと思っている人の一助になれば幸いです
(※自分もこれまでパッケージ公開などはやってきていなかったので、アドバイス等あれば喜びます)temori/distancexport
上記パッケージはDB間でデータを移行する必要があるやテーブル名やカラム名が違うデータの移行の際にスプレッドシート形式で移行先のカラムと移行元のカラムを編集できたら便利だなーと思って作成しました。最終目標
packagistに公開して
composer require [パッケージ名]
でパッケージがインストールできるまでを目指します。環境構築
開発環境はいつも
docker
で準備しています。
お好きな環境で開発すれば良いかと思います。ちなみに自分は以下のリポジトリの環境で開発しています。
temori1919/docker-lampプロジェクト作成
まずパッケージ用のディレクトリをします。
最低限以下の様なディレクトリ構成でいいかと思います。自分は
package
ディレクトリの下にvendor名
、パッケージ名
でディレクトリを作成していますが、ルートディレクトリがパッケージ名でも問題ないかと思います。package └── vendor名 └── パッケージ名 ├── LICENSE # githubのリポジトリ内で作成することが可能です ├── README.md # パッケージの概要を記述するので完成してから作成しても構いません ├── composer.json # 後述するcomposerコマンドで生成されるファイルです ├── .gitignore # gitから除外したいファイルがあれば作成します ├── src # パッケージのソースを格納するディレクトリです └── tests # テストコードを格納するディレクトリです(なくてもOK)ディレクトリを作成したら、
composer init
コマンドでcomposer.json
を作成します。外部パッケージを定義しているいつものあのファイルです。
$ cd path/to/package/vendor名/パッケージ名 # コマンドを実行すると対話形式でファイル作成が始まります $ composer init Welcome to the Composer config generator This command will guide you through creating your composer.json config. # ベンダー名/パッケージ名を入力してEnter(ここで入力したものが外部公開した時のベンダー名/パッケージ名になります) Package name (<vendor>/<name>) [some/mypackage]: # パッケージの概要を記述しますが空白でも構いません。(後述するpackagistの概要に表示される箇所になります) Description []: # 製作者、nでスキップしても問題ありません Author [some <some@address.com>, n to skip]: # 最小安定性、そのままEnterでも問題ありません # 作成するパッケージの安定性を記述する箇所になります # デフォルトは「stable」で使用できる値は「dev」、「alpha」、「beta」、「RC」、「stable」のみとなります Minimum Stability []: # パッケージのタイプ、そのままEnterでOK # デフォルトが「library」(vendorの下にインストールされるタイプ)でそのほかに「project」、「metapackage」、「composer-plugin」があります Package Type (e.g. library, project, metapackage, composer-plugin) []: # ライセンスのタイプ(用途にあうライセンスを指定してください) License []: # 使用する予定のあるパッケージを検索します # とりあえず使用する予定はないのでそのままEnter Would you like to define your dependencies (require) interactively [yes]? Search for a package: # こちらは上記のdevパッケージになります # 使用予定のものがあればここで検索してもいいですが、後から追加でもOKなのでとりあえずEnter Would you like to define your dev dependencies (require-dev) interactively [yes]? Search for a package: # コマンドに記述した内容の確認になります # 問題なければEnter(あとでファイルを直接修正することもできます) { "name": "some/mypackage", "license": "MIT", "require": {} } Do you confirm generation [yes]?パッケージディレクトリの直下に
composer.json
ができていると思います。mypackage/composer.jsonこれでディレクトリの準備ができました。
パッケージの処理を実装する
ディレクトリやダミープロジェクトの作成
src
ディレクトリ配下にクラスやメソッドを作成していきます。パッケージを開発する際はローカルのディレクトリにあるパッケージを
composer require
して開発を進めていきます。まず開発するパッケージをインストールするダミーのプロジェクトを作成します。
先ほど作成したパッケージ用
ディレクトリと同階層になります。├── package └── dummy-project※FWを使用してのプロジェクト作成でも問題ありません。
作成したプロジェクトに移動して
composer.json
を作成し以下を追記します。
※FWを使用してプロジェクトを作成した場合は、すでに配置されているcomposer.json
に追記します。
sh
cd dummy-project
composer.json"repositories": [ { "type": "path", "url": "../package/vendor名/パッケージ名" } ]これで
composer init
の際に指定したベンダー名/パッケージ名
でcomposer require ベンダー名/パッケージ名コマンドを打てばローカルのディレクトリがパッケージとしてインストールされます。
composer.json
のtype
を変更することにより、インストール先を変更することができます。repositories
の指定がないと、デフォはpackagistからパッケージを検索します。
path
でローカルディレクトリからの読み込み、vcs
でgithub等のリポジトリホスティングサービスから直接インストールなどインストール先を変更することが可能です。詳しくは以下を参照してください。
type
をpath
にしてインストールするとインストールしたプロジェクト内のパッケージの格納されているvendor
ディレクトリにローカルパッケージディレクトリのシンボリックリンクが生成されます。パッケージのコーディングを行う際はプロジェクト内の
vendor
以下のパッケージを変更すれば、プロジェクトと同階層に作成したパッケージディレクトリのソースに反映されるので、IDEを使用している時はコードジャンプ等ができて便利です。クラスやメソッドの作成
ちなみに自分はパッケージ名でメインとなるクラスを作成しています。
今回のパッケージだと以下の様な命名や構成になります。
├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── resources │ ├── assets │ │ ├── css │ │ │ ├── jexcel.css │ │ │ └── jsuites.css │ │ └── js │ │ ├── jexcel.js │ │ └── jsuites.js │ └── views │ └── index.php ├── src │ ├── DataBases │ │ ├── BaseDriver.php │ │ ├── Connect.php │ │ └── Drivers │ │ ├── Mysql.php │ │ └── Pgsql.php │ ├── Distancexport.php │ ├── Provider.php │ └── Renders │ └── AssetResponse.php └── tests └── DistancexportTest.php
Distancexport.php
がパッケージの起点となるクラスファイルです。また
resources
ディレクトリは今回のパッケージにはviewを描画する必要があったのでディレクトリを作成しています。クラスを作成したらパッケージの
composer.json
にautoloadの設定を追記します。{ "name": "some/mypackage", "license": "MIT", "require": {}, "autoload": { "psr-4": { "Some\\Mypackage\\": "src/", "Some\\Tests\\": "tests/" } } }これで
src
配下がSome\Mypackage
のnamespaceでオートロードされます。
tests
に関してはディレクトリ配下にテストコードが格納される前提なので、Some\Tests
のnamespaceでオートロードされます。またクラスを作成する際に命名するnamespaceですがオートロードで設定したnamespaceでないとファイルが読み込めません。
パッケージに実装するnamespaceを記述するファイルには
composer.json
に記述した通りのnamespaceをつける必要があります。
(※上記例でいうとSome\Mypackage\ClassName
)また外部のパッケージが必要な場合は
require
に記述すればプロジェクト側でこのパッケージをcomposer require
もしくはinstall
した際にインストールしてくれます。README.mdを作る
READMEを作成します。
自分は主に以下の様な項目を記載する様にしています。
- パッケージ名
- バージョンやライセンス等を表示するバッチ
視覚的にわかりやすいし何よりカッコイイ![]()
- 概要
パッケージの概要を記述します- パッケージにUIがあればgifアニメや画像
UIがあるものなら画像などがあった方がわかりやすいと思います。また、作成した画像でもいいかもしれません。- 目次
各見出しのリンク付きの目次を配置します。- 特徴 パッケージの特徴です。
- 要件 対応するバージョンなどを記載する箇所になります。
- インストール方法 パッケージのインストール方法です。 コピペできる様にコマンドで記述します。
- 使い方
- ライセンス
今回公開したパッケージは以下の様な項目を記載しています。
https://github.com/temori1919/distancexport/blob/master/README.mdバッチに関しては以下のサービスを使用しました。
packegistに公開してあるパッケージなら簡単にバッチを作成することができます。
もちろん公開していなくても作成できるバッチもあります。
上記はpackagistのversionになりますが、公開されているベンダー名とパッケージ名を入力するとmdやhtmlでURLを生成してくれます。
バージョンに関してはpackagistに公開後でないと生成できないので、パッケージを公開し、後述するバージョンをタグ付けしたのちに作成することになると思います。
また、バッチのスタイルや文言を変更することが可能です。
GitHubにリポジトリを作成してpushする
リポジトリにソースをpushしましょう。
pushした後にLICENSE
ファイルを作成します。
Create new file
を押下し新しいファイルをGitHub上で作成します。
ファイル名にLICENSE
と入力するとライセンスファイルのテンプレートが選べる様になります。
後は用途にあったライセンスを選択してファイルを生成します。
タグでバージョンを指定する
tags
->Create a new release
で以下の様なタグ作成画面に遷移すると思うので、タグを生成していきます。
Tag version
を指定します。自分は
v1.0.0
からスタートする様にしています。
v
があってもなくても問題ありません。composerは
v
付きでもバージョンとして認識します。
Tag version
の横にTarget
ブランチが選択されていますが、master
ブランチの現時点での最新コミットが今回のタグ付けしたバージョンになります。タイトル、詳細内容等あれば記述しますがなくても問題はありません。
packagistに公開する
最後にpackagistにGitHubのリポジトリを公開します。
アカウントがなければ作成してください。
GitHubアカウントでもログインすることが可能です。ヘッダーにある
Submit
ボタンからパッケージを送信するページを表示します。リポジトリのURLを
Repository URL
に記載してCheckボタンを押してください。これでGitHubのリポジトリとpackagistが紐付来ました。
GitHubで追加したタグ情報もpackagistのパッケージバージョンとしてリンクする様になります。リポジトリに追加してある
README.md
もpackagistのパッケージTOPページに反映されます。GitHubのWebhookにpackagistのapiトークンを登録する
GitHubのWebhookにpackagistのapiのURLを登録します。
Webhookを活用することでGitHubのリポジトリを更新した際に自動でpackagist側に変更を反映させることができる様になります。
Show API Token
を押してトークンを表示してください。
その下のthe docs
のリンクを押すとGitHubのWebhookの登録方法が表示されます。上記のドキュメントに記載されているURLを表示したトークンを使ってGitHubにWebHookを登録します。
ドキュメントに記載されている通りに入力してください。
これで公開に伴う全ての作業が完了しました。
最後に
手順を追ってみると、パッケージの公開はそこまでハードルの高いものではないと思います。
次回はLaravelのパッケージを公開する手順に続きます。
- 投稿日:2020-12-08T09:29:32+09:00
PHPの文字列連結の中で最速を考えてみる
はじめに
こんにちは、普段は業務で求人系サービスの開発を行なっている@taku-0728です。
今回はいくつかあるPHPの文字列連結のやり方について、一番早いやり方を考えてみます。
あくまで一例なので、他に「こんなやり方もあるよ!」等あればコメントにてご指摘いただければ幸いです。
よろしくお願いいたします。背景
業務中にPHPで文字列連結をする場面があったのですがその時にふと
「普段は何も考えず'私の名前は' .$name .'です。'
とか書いてるけど、
"私の名前は $name です。"
とか書いても結果は同じだしどっちが早いんだろう」
と思ったので検証してみます。準備
今回は下記3パターンの文字列連結の書き方を準備します。
パターン1(シングルクォート)
パターン1はシングルクォートと連結演算子(.)で連結したパターンです。
サンプルコードはこちらpattern1.php<?php // パターン1 $dogName = 'ポチ'; $startTime1 = microtime(true); for($j = 0; $j < 10000; $j++){ $tmp = '私が飼っている犬の名前は' .$dogName .'です。'; } $endTime1 = microtime(true); $ret1 = $endTime1 - $startTime1; ?>パターン2(変数展開)
パターン2は文字列の中で変数展開したパターンです。
サンプルコードはこちらpattern2.php<?php // パターン2 $dogName = 'ポチ'; $startTime2 = microtime(true); for($j = 0; $j < 10000; $j++){ $tmp = "私が飼っている犬の名前は $dogName です"; } $endTime2 = microtime(true); $ret2 = $endTime2 - $startTime2; ?>パターン3(ダブルクォート)
パターン3はダブルクォートと連結演算子(.)で連結したパターンです。
サンプルコードはこちらpattern3.php<?php // パターン3 $startTime3 = microtime(true); for($j = 0; $j < 10000; $j++){ $tmp = "私が飼っている犬の名前は" .$dogName ."です。"; } $endTime3 = microtime(true); $ret3 = $endTime3 - $startTime3; ?>検証
上記3パターンのコードを実行し、速度を検証してみます。
複数回実行した時に必ずしも同じ速度になるとは限らないので、今回は10回実行した結果の平均値で検証を行います。
それを踏まえた最終的なコードは下記です。
雑に考えただけなので、より簡潔な書き方があればコメントにてご指摘いただければ幸いです。test.php<?php $dogName = 'ポチ'; $sum1 = 0; $sum2 = 0; $sum3 = 0; for($i = 0; $i < 10; $i++){ // パターン1 $startTime1 = microtime(true); for($j = 0; $j < 10000; $j++){ $tmp = '私が飼っている犬の名前は' .$dogName .'です。'; } $endTime1 = microtime(true); $ret1 = $endTime1 - $startTime1; $sum1 += $ret1; // パターン2 $startTime2 = microtime(true); for($j = 0; $j < 10000; $j++){ $tmp = "私が飼っている犬の名前は $dogName です"; } $endTime2 = microtime(true); $ret2 = $endTime2 - $startTime2; $sum2 += $ret2; // パターン3 $startTime3 = microtime(true); for($j = 0; $j < 10000; $j++){ $tmp = "私が飼っている犬の名前は" .$dogName ."です。"; } $endTime3 = microtime(true); $ret3 = $endTime3 - $startTime3; $sum3 += $ret3; } $avg1 = $sum1 / 10; $avg2 = $sum2 / 10; $avg3 = $sum3 / 10; echo("パターン1:平均時間:" .$avg1 ."秒\n"); echo("パターン2:平均時間:" .$avg2 ."秒\n"); echo("パターン3:平均時間:" .$avg3 ."秒\n"); ?>結果
10回実行した平均値は下記のようになりました。
パターン1:平均時間:0.00032708644866943秒 パターン2:平均時間:0.00034801959991455秒 パターン3:平均時間:0.00032320022583008秒パターン2の変数展開が一番遅いのは予想通りでしたが、パターン1のシングルクォートよりもパターン3のダブルクォートの方が(わずかではありますが)速かったのは意外でした。
環境や実際のコードにもよるのでこの結果が全てのパターンに当てはまるとは限りませんが、参考にしてみてください。まとめ
今回はPHPの文字列連結はどのやり方が一番早いのか、ふと疑問に思ったので検証してみました。
結果の方にも記載しましたが、あくまで一例ですので、参考程度にお考えください。
雑に書いたコードですので、もし誤りや改善点があればお手数ですがコメントにてご指摘いただけると幸いでございます。
ありがとうございました。
- 投稿日:2020-12-08T04:13:46+09:00
【WordPress】標準の投稿アーカイブのパーマリンクを任意のURLにする方法いろいろ【備忘録】
毎回忘れるので、備忘録として書いとこうと…
やりたいこと
- WordPress標準の投稿(post)のアーカイブページを作って、
https://www.example.com/スラッグ/
みたいな任意のパーマリンクにしたい。- 標準の投稿のシングルページのパーマリンクを、
https://www.example.com/スラッグ/ページスラッグorID
みたいな感じにしたい。早い話が
「カテゴリとかもないただのお知らせ的なの実装するだけでわざわざカスタム投稿タイプ作るのめんどいし抵抗がある、かといって標準の投稿だとアーカイブがカテゴリーベースのhttps://www.example.com/category/hoge/
みたいな感じでシングルページがhttps://www.example.com/123/
みたいな感じになってなんか気持ち悪い」
という人向け。やり方
方法① カスタム投稿タイプをつくる。
上に書いた早い話の鉤括弧内を見て『いやカスタム投稿でよくね?』と思う方はカスタム投稿タイプ作るほうが圧倒的に早いし、パーマリンク的にはすごい素直な感じの挙動します。標準の投稿が完全に無用の長物と化すけど。
やり方はregister_post_type
するときに'rewrite' => array( 'slug' => '任意のスラッグ', 'with_front' => true), 'has_archive' => trueとするだけ。ググればたくさん情報出てきますのでググってください。標準の投稿? 隠せばいいのだ。
方法② アーカイブページ用の固定ページを作る
標準の投稿を活かすならこっちの方法。解説は後ほど。
方法③
'has_archive' => true
にしてうまいことする→できないこの「うまいことする」をやろうとして調べて結局キレイな形でできないから②のやり方に移行する、というのを毎回やってます。
アーカイブページなのに固定ページってなんか気持ち悪いし、無駄な固定ページがあるのも気になるし、ていうかアーカイブページがarchive.phpじゃなくてpage-{slug}.phpで動いてるのがなんか気持ち悪いんでこのやり方でやりたいんですが、アーカイブページのパーマリンクをどう頑張っても取得できません。カテゴリでも日付でもない通常のアーカイブページを有効にするのは以下で可能。ググれば出てくる。
function.php// 通常アーカイブを有効にする function post_has_archive( $args, $post_type ) { if ( 'post' == $post_type ) { $args['rewrite'] = true $args['has_archive'] = '任意のスラッグ'; } return $args; } add_filter( 'register_post_type_args', 'post_has_archive', 10, 2 );で、例えばスラッグを
news
とかにした場合、https://www.example.com/news/
とURLを叩くとarchive.phpのテンプレで投稿一覧が表示される。
のだがこのhttps://www.example.com/news/
はWordPressの関数ではどう頑張っても引っ張ってこれない。
get_post_type_archive_link('post')
で引っ張ってこようとしても、引数が'post'
だと無常にもfalse
が返ってくる。
URL直接叩かせるなり、アーカイブのリンク返す関数やショートコードを適当に自作するなり、post_type_archive_link
にフィルターフックしてうまいことするなりでなんとかしようと思えばできるけど、
アーカイブをarchive.phpで表示するという『当たり前にできそうなこと』をしたいから頑張ってるのにそんな力技で解決するのはなんか気持ち悪い。というのと、WordPressのシステム上ではアーカイブページが無いものとして扱われるので(厳密には違うが)、例えばサイトマップ生成プラグインとか使ってるとアーカイブページが含まれなかったり、
それを解決しようとするとリライトルールをゴリゴリ書くハメになったりと、
とにかく不具合や複雑化の温床になりやすい。ので、最終的に固定ページが1ページ増えるだけで特に他には影響が出ない【方法②】に落ち着くわけである。一応WordPress推奨の方法だしな…
今更説明するまでもないと思うけど、一応手順は以下の通り。方法②-手順1. アーカイブ用の固定ページを作る
タイトルは任意のタイトル、本文は空でよい。このページのパーマリンクがアーカイブページのパーマリンクになる。
当然ながら、テンプレートとなるPHPファイルは用意しておくこと。手順2. 表示設定・パーマリンク設定を変更
↑表示設定の【投稿ページ】で今しがた作った固定ページを選択する。
↑パーマリンク設定。【カスタム構造】のラジオボタンをチェックして、/任意のスラッグ/(任意のパーマリンク構造)
とする。画像では%post_id%
にしているが%post_name%
でも構わない。手順3. アーカイブのパーマリンクの取得方法
example.phpecho get_permalink( get_page_by_path( '固定ページのスラッグ' )->ID );これで引っ張ることになる。うーん、やっぱりなんか…
ほんとお知らせ機能とか標準の投稿タイプそのまま使えたらそれで良いんだけど、パーマリンクが何か気持ち悪い感じになるのが如何ともし難い。
管理画面の見え方からカスタマイズするようなレベルならカスタム投稿タイプ使うことに抵抗はないんだけど、固定ページいくつか+αくらいのサイトを作るときはこの辺の仕様で毎回もにゃる。自分だけかもしれないけど。
- 投稿日:2020-12-08T01:17:59+09:00
30代ヨワヨワエンジニアがServerlessFramework(lambda)を使ってphpでslackにapiを使用して村上春樹風に語りながら猫画像を定期的に投稿するプログラムを書いてみた
早速ですが
僕の上司は猫です
なので、気まぐれで寂しがり屋な上司のためにも猫の画像を取得してslackに定期的に画像を投稿出来るように挑戦してみました。
今回の制作背景
上司のためでもありますが、入社して6ヶ月たちapiを使って連携する事が(cronも多い)ため実装する事でapi連携がわかるのではないかと思ったのと、lambdaの話をよく聞く事が多かったのでServerlessFrameworkを使用してslackに猫画像を定期的に投稿するプログラムを実装してみました
目次
1.開発環境
2.使用したAPI
3.コードとディレクトリ構造
4.苦労した事
5.良かった事
6.改善点
7.その他参考にした記事
8.おわりに1. 開発環境
ServerlessFrameWork
Framework Core: 2.12.0
Plugin: 4.1.2
SDK: 2.3.2
Components: 3.4.0php:7.3
bref:0.5.33ServerlessFrameworkとは
公式ドキュメントサーバーレスフレームワークを使用することで、オーバーヘッドとコストを大幅に削減して、サーバーレスアプリケーションを開発、展開、トラブルシューティング、および保護する
負荷やらお金を大幅削減できるらしいですね
lambdaはサーバーを用意する必要がなく、パッチ当てなどの保守作業を行わなくても良いため、利用者はインフラ管理の大部分をクラウドに任せて、開発リソースをつぎ込むことができるのがメリットのようです。
ServerlessFrameworkの中核的な存在ですね。サーバレスアーキテクチャに関する説明が下記となります。
https://mmmcorp.co.jp/column/serverless/2. 使用したAPI
Api導入にあたり参考にさせて頂いたQiita記事
Catapi関連(参考にさせて頂いた部分が多かったです):https://qiita.com/nkojima/items/ac28780c5e61c6a9f999v
SlackBot関連:https://qiita.com/gkzz/items/23a7d03aeadcec3417003. コードとディレクトリ構造
ディレクトリ構造
. ├── composer.json ├── composer.lock ├── index.php ├── serverless.yml └── vendor ├── async-aws ├── autoload.php ├── bin ├── bref ├── composer ├── hollodotme ├── mnapoli ├── nyholm ├── php-di ├── php-http ├── psr ├── riverline └── symfonyserverless.ymlservice: phpapp provider: name: aws region: ap-northeast-1 runtime: provided plugins: - ./vendor/bref/bref functions: function: handler: index.php description: '' layers: - ${bref:layer.php-73} events: - sqs: arn: Fn::GetAtt: - PhpQueue - Arn - schedule: cron(0/50 * * * ? *) # 毎日50分おき resources: Resources: PhpQueue: Type: AWS::SQS::Queue Properties: QueueName: phpqueueindex.php<?php declare(strict_types=1); require __DIR__.'/vendor/autoload.php'; lambda(function ($response) { $cat_url = 'https://api.thecatapi.com/v1/images/hogehoge'; // cat_apiのリクエストするURLとパラメータ // curlセッションを初期化する $curl = curl_init($cat_url); // リクエストのオプションをセットしていく curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET'); // メソッド指定 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // レスポンスを文字列で受け取る ここまでGET ------------------------------------------------------------- ここからPOST // レスポンスを変数に入れる $response = curl_exec($curl); // JSON文字列をデコードした結果を変数にいれる $result = json_decode($response, true); // urlのバリューを取り出して変数にいれる $image = $result[0]['url']; // slack_apiのURLとパラメーター $url = 'https://hooks.slack.com/services/hogehoge'; // 配列にキーとバリューをいれる $message = array( "username" => "ユーザー名", "icon_emoji" => ":slack:", "text" => "やれやれ\n".$image, ); // メッセージをjson化 $message_json = json_encode($message); // payloadの値としてURLエンコード $message_post = "payload=".urlencode($message_json); // curlセッションを初期化する $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); // curlオプションを設定する curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // curl_exec()を実行時、返り値を文字列で返す。 curl_setopt($ch, CURLOPT_POST, true); // POST curl_setopt($ch, CURLOPT_POSTFIELDS, $message_post); // CurlPostする際に必要 curl_exec($ch); // curlを実行する curl_close($ch); // curlセッションを終了する });lambdaでGETしたら....
取れた!後はURLをJSONからデコードしてURLのバリューを取り出して配列に入れてエンコードして....
結果
出来た!後は50分おきに投稿されるだけ
ドーン!!!
4. 苦労した事
- lambdaのruntimeにphpがなかったので、customruntimeのbrefを使ったが設定に若干、てこずった。参考にしたbrefの設定リンク
- jsonを一度、デコードしてから配列に変換し、その中の値を取り出して新たな配列に値を入れてエンコードする一連の作業に苦しめられた。
- 作った変数の中にどの値が入っているか毎回、dumpして今の形式がどうなっているか細かなデバッグをしながら作業した。
5. 良かった事
- curlでGETとPOSTする基本的な動作を行う事ができた
- データをどう取ってきて、どう処理するかの一連の流れをより理解する必要がある事がわかった
- apiの一連の流れを自分で考えながら(かなり助けていただきました)実装できた事。
- GETとPOSTの関数は別にするのが通常だが、ワンライナーで試しにやってみて試行錯誤しながら出来た事。
- serverless deployコマンドだけでlambdaにdeployが出来る事を知れたのは良かった。
6. 改善点
- 当初、laravelとphpで実装しようとしていたが、phpのみの実装に変えた(そもそもlaravelを使うならlambdaを使うメリット、デメリットやコストパフォーマンスを理解してから実装する必要がありそう)
- 今回は画像形式が文字列でslackに投稿してもslack上に画像を出す事が出来たが、本来あらゆる形式を扱えるようになる必要がある。
- やれやれしか呟けなかった。色んな種類の春樹風を呟きたかった。
7. その他参考にした記事や簡単な用語メモ
Curl
インターネットプロトコルを使用してURLとして指定されたリソースのインターネット転送を行うツール(色んな言語で使える)
Curlでapi
エンコードとデコード
その他いろいろ8. おわりに
個人的には勉強する部分が多かったので非常にためになりました!
プログラマ歴の浅い人でも知らない技術に果敢に挑戦出来るんだぞと勇気づける事が出来れば幸いです。
一緒に住んでいるシェアハウスの住民には色々と助けてもらいました。ありがとうございました。
業務ではまだまだ戦力としては雀の涙ですが、フロントとバックエンドも全て出来る職場で自由な環境で働けているのでありがたいです。つよつよ目指して引き続き頑張ります。
- 投稿日:2020-12-08T01:17:59+09:00
30代ヨワヨワエンジニアがServerlessFramework(lamda)を使ってphpでslackにapiを使用して村上春樹風に語りながら猫画像を定期的に投稿するプログラムを書いてみた
早速ですが
僕の上司は猫です
なので、気まぐれで寂しがり屋な上司のためにも猫の画像を取得してslackに定期的に画像を投稿出来るように挑戦してみました。
今回の制作背景
上司のためでもありますが、入社して6ヶ月たちapiを使って連携する事が(cronも多い)ため実装する事でapi連携がわかるのではないかと思ったのと、lambdaの話をよく聞く事が多かったのでServerlessFrameworkを使用してslackに猫画像を定期的に投稿するプログラムを実装してみました
目次
1.開発環境
2.使用したAPI
3.コードとディレクトリ構造
4.苦労した事
5.良かった事
6.改善点
7.その他参考にした記事
8.おわりに1. 開発環境
ServerlessFrameWork
Framework Core: 2.12.0
Plugin: 4.1.2
SDK: 2.3.2
Components: 3.4.0php:7.3
bref:0.5.33ServerlessFrameworkとは
公式ドキュメントサーバーレスフレームワークを使用することで、オーバーヘッドとコストを大幅に削減して、サーバーレスアプリケーションを開発、展開、トラブルシューティング、および保護する
負荷やらお金を大幅削減できるらしいですね
lamdaはサーバーを持たないため、パッチ当てなどの保守作業を行う必要がなく、利用者はインフラ管理の大部分をクラウドに任せて、開発リソースをつぎ込むことができるのがメリットのようです。
ServerlessFrameworkの中核的な存在ですね。サーバレスアーキテクチャに関する説明が下記となります。
https://mmmcorp.co.jp/column/serverless/2. 使用したAPI
Api導入にあたり参考にさせて頂いたQiita記事
Catapi関連(参考にさせて頂いた部分が多かったです):https://qiita.com/nkojima/items/ac28780c5e61c6a9f999v
SlackBot関連:https://qiita.com/gkzz/items/23a7d03aeadcec3417003. コードとディレクトリ構造
ディレクトリ構造
. ├── composer.json ├── composer.lock ├── index.php ├── serverless.yml └── vendor ├── async-aws ├── autoload.php ├── bin ├── bref ├── composer ├── hollodotme ├── mnapoli ├── nyholm ├── php-di ├── php-http ├── psr ├── riverline └── symfonyserverless.ymlservice: phpapp provider: name: aws region: ap-northeast-1 runtime: provided plugins: - ./vendor/bref/bref functions: function: handler: index.php description: '' layers: - ${bref:layer.php-73} events: - sqs: arn: Fn::GetAtt: - PhpQueue - Arn - schedule: cron(0/50 * * * ? *) # 毎日50分おき resources: Resources: PhpQueue: Type: AWS::SQS::Queue Properties: QueueName: phpqueueindex.php<?php declare(strict_types=1); require __DIR__.'/vendor/autoload.php'; lambda(function ($response) { $cat_url = 'https://api.thecatapi.com/v1/images/hogehoge'; // cat_apiのリクエストするURLとパラメータ // curlセッションを初期化する $curl = curl_init($cat_url); // リクエストのオプションをセットしていく curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET'); // メソッド指定 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // レスポンスを文字列で受け取る ここまでGET ------------------------------------------------------------- ここからPOST // レスポンスを変数に入れる $response = curl_exec($curl); // JSON文字列をデコードした結果を変数にいれる $result = json_decode($response, true); // urlのバリューを取り出して変数にいれる $image = $result[0]['url']; // slack_apiのURLとパラメーター $url = 'https://hooks.slack.com/services/hogehoge'; // 配列にキーとバリューをいれる $message = array( "username" => "ユーザー名", "icon_emoji" => ":slack:", "text" => "やれやれ\n".$image, ); // メッセージをjson化 $message_json = json_encode($message); // payloadの値としてURLエンコード $message_post = "payload=".urlencode($message_json); // curlセッションを初期化する $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); // curlオプションを設定する curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // curl_exec()を実行時、返り値を文字列で返す。 curl_setopt($ch, CURLOPT_POST, true); // POST curl_setopt($ch, CURLOPT_POSTFIELDS, $message_post); // CurlPostする際に必要 curl_exec($ch); // curlを実行する curl_close($ch); // curlセッションを終了する });lamudaでGETしたら....
取れた!後はURLをJSONからデコードしてURLのバリューを取り出して配列に入れてエンコードして....
結果
出来た!後は50分おきに投稿されるだけ
ドーン!!!
4. 苦労した事
- lambdaのruntimeにphpがなかったので、customruntimeのbrefを使ったが設定に若干、てこずった。参考にしたbrefの設定リンク
- jsonを一度、デコードしてから配列に変換し、その中の値を取り出して新たな配列に値を入れてエンコードする一連の作業に苦しめられた。
- 作った変数の中にどの値が入っているか毎回、dumpして今の形式がどうなっているか細かなデバッグをしながら作業した。
5. 良かった事
- curlでGETとPOSTする基本的な動作を行う事ができた
- データをどう取ってきて、どう処理するかの一連の流れをより理解する必要がある事がわかった
- apiの一連の流れを自分で考えながら(かなり助けていただきました)実装できた事。
- GETとPOSTの関数は別にするのが通常だが、ワンライナーで試しにやってみて試行錯誤しながら出来た事。
- serverless deployコマンドだけでlamdaにdeployが出来る事を知れたのは良かった。
6. 改善点
- 当初、laravelとphpで実装しようとしていたが、phpのみの実装に変えた(そもそもlaravelを使うならlambdaを使うメリット、デメリットやコストパフォーマンスを理解してから実装する必要がありそう)
- 今回は画像形式が文字列でslackに投稿してもslack上に画像を出す事が出来たが、本来あらゆる形式を扱えるようになる必要がある。
- やれやれしか呟けなかった。色んな種類の春樹風を呟きたかった。
7. その他参考にした記事や簡単な用語メモ
Curl
インターネットプロトコルを使用してURLとして指定されたリソースのインターネット転送を行うツール(色んな言語で使える)
Curlでapi
エンコードとデコード
その他いろいろ8. おわりに
個人的には勉強する部分が多かったので非常にためになりました!
プログラマ歴の浅い人でも知らない技術に果敢に挑戦出来るんだぞと勇気づける事が出来れば幸いです。
一緒に住んでいるシェアハウスの住民には色々と助けてもらいました。ありがとうございました。
業務ではまだまだ戦力としては雀の涙ですが、フロントとバックエンドも全て出来る職場で自由な環境で働けているのでありがたいです。つよつよ目指して引き続き頑張ります。
- 投稿日:2020-12-08T00:20:56+09:00
AWSのcloud9でPHPのバージョン古い件
WordPressのカレンダープラグインを使ってウェブサイトのカスタマイズの案件を受注したので、久々にAWSで開発環境を作ろうとしました。でも、ネットで書かれているWordPressの開発環境の手順で行っても、VFS connecion does not existとエラーになってしまいプレビューができない状態です。
そのため、色々とネットで調べたのですが、EC2インスタンスの再起動しか方法が見つからず、途方にくれていました。
こちらに記録しましたが、AWSのWordPressのサンプルのドキュメントを参考にして、設定作業を行っていました。
https://teratail.com/questions/308472
テラテイルの回答を見ていて、私のPHPのバージョンが古いことに気がつきました。
そのため、こちらの記事を基に、PHPの7.3をインストールしました。
https://qiita.com/gomiryo/items/28c07d5ee9afd1c325b0私がWordPressの開発環境作りに参考にした記事は、こちらです。
https://upd.world/wordpress-cloud9/https://qiita.com/acecrc/items/a72992a17968d3d9bc00
https://laboratory.kazuuu.net/what-to-do-when-oops-vfs-connection-does-not-exist-is-displayed/
PHPのバージョンを上げたらWordPressのインストールは、できました。
でも、管理画面へのログインができない状態です。
そのため、もう一度環境を作り直そうと思っています。クライアントの作業のこともあり、cloud9で開発環境作りばかりできないので、断続的に調査復旧を行っていきたいと思っています。
cloud9のIDEの中でタグを使ってアプリケーションの表示ができても、ブラウザにタグを追加して表示ができない場合もあります。
AWSのインターネット共有のドキュメントを読み、設定の確認を行っていきたいと思っています。