- 投稿日:2019-08-19T23:47:29+09:00
【php】メールフォーム
html フォームパーツ
<form action="sent.php" method="post"> Emailを入力 <input type="text" name="email"> 内容 <textarea name="content"></textarea> <select name="fruit"> <option value="apple">りんご</option> <option value="banana">ばなな</option> <option value="orange">みかん</option> </select> <input type="submit" value="送信"> </fomr>$_POST
フォームで送信した値を受け取るには、「$_POST」を使う。
「$_POST」は連想配列になっている。[ ]の中に、<input>と<textare>のname属性に指定した値を入れることで、それぞれの送信した値を受け取る事ができる。echo $_POST['name']; echo $_POST['email']; echo $_POST['fruit']; //選択されたvalueが入る //$_POSTの中身は連想配列になっている array( 'name' => 'formで入力した値', 'email' => 'formで入力した値'; ) //option for($i = 1; $1 < 4; $i++){ echo "<option value='{$i}'>{$i}</option>"; } //上記のfor文は下記と同じ意味 echo "<option value='1'>1</option>" echo "<option value='2'>2</option>" echo "<option value='3'>3</option>"
- 投稿日:2019-08-19T23:14:56+09:00
【php】戻り値【return】
- 投稿日:2019-08-19T23:00:54+09:00
【php】関数の作成
関数の作成
重複する処理を一箇所にまとめることで、コードに変更があった時に関数の中身を変更するだけ良くなる為便利。
円の面積を求める
$radius1 = 3; echo $radius1 * $radius1 * 3; $radius2 = 5; echo $radius2 * $radius2 * 3; //上記は同じ処理を実行している。 //関数の定義 function printCircleArea($radius){ echo $radius * $radius * 3; } //関数の呼び出し printCircleArea(3); //結果: 27 printCircleArea(5); //結果: 75関数の記述方法
関数を作るには「function 関数名(){処理}」の形式で記述する。関数名は自由。呼び出しは「関数名()」。
//関数の定義 function hello(){ echo 'Hello, world!'; } //関数の呼び出し hello(); //結果: Hello, world!引数有り
//関数の定義 function printSum($num1,$num2){ echo $num1 + $num2; } //関数の呼び出し printSum(8,14); //結果: 22;
- 投稿日:2019-08-19T23:00:54+09:00
【php】関数の作成【function】
関数の作成
重複する処理を一箇所にまとめることで、コードに変更があった時に関数の中身を変更するだけ良くなる為便利。
円の面積を求める
$radius1 = 3; echo $radius1 * $radius1 * 3; $radius2 = 5; echo $radius2 * $radius2 * 3; //上記は同じ処理を実行している。 //関数の定義 function printCircleArea($radius){ echo $radius * $radius * 3; } //関数の呼び出し printCircleArea(3); //結果: 27 printCircleArea(5); //結果: 75関数の記述方法
関数を作るには「function 関数名(){処理}」の形式で記述する。関数名は自由。呼び出しは「関数名()」。
//関数の定義 function hello(){ echo 'Hello, world!'; } //関数の呼び出し hello(); //結果: Hello, world!引数有り
//関数の定義 function printSum($num1,$num2){ echo $num1 + $num2; } //関数の呼び出し printSum(8,14); //結果: 22;
- 投稿日:2019-08-19T22:45:12+09:00
【php】組み込み関数
組み込み関数: strlen
phpには便利な関数がもとから組み込まれており、それを組み込み関数と呼ぶ。組み込み関数「strlen」は文字列の文字数を返す。
echo strlen('string'); //文字列を返す //結果: 6 $lang = 'japanese'; echo strlen($lang); //変数もok //結果: 8組み込み関数: count / rand
「count」は配列の要素の数を返す。
「rand」は1つ目の引数と2つ目の引数の間のランダムな整数を返す。$data = array('team1','team2','team3','team4'); echo count('$data'); //結果: 4 echo rand(10,15); //結果: 10~15のランダムな整数を返す
- 投稿日:2019-08-19T21:39:19+09:00
PHPフレームワーク Yii の仕組み
はじめに
フレームワークの仕組みでフレームワークを単純化して仕組みをまとめた。このエントリでは、Yiiフレームワークを利用して実用レベルのフレームワークの仕組みを確認してみたい。
PHPフレームワーク Yii の使い方ではとりあえず動くものを作れるだけの知識を簡単にまとめた。このエントリでは、Yiiフレームワークを使いこなせるようになるために、仕組みについてもう少し掘り下げてみたい。アプリケーションの設定
エントリスクリプトの中で構成情報(
$config
)を読み込む。そしてアプリケーションの実体となるクラス(Application)をインスタンス化する。ApplicationのAPI Documentationはこちら。yii-application/frontend/web/index.php// 1. 環境の切り替え defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); // 2. アプリケーションの設定 require __DIR__ . '/../../vendor/autoload.php'; require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php'; require __DIR__ . '/../../common/config/bootstrap.php'; require __DIR__ . '/../config/bootstrap.php'; $config = yii\helpers\ArrayHelper::merge( require __DIR__ . '/../../common/config/main.php', require __DIR__ . '/../../common/config/main-local.php', require __DIR__ . '/../config/main.php', require __DIR__ . '/../config/main-local.php' ); // 3. アプリケーションの実体 (new yii\web\Application($config))->run();Advancedテンプレート参照。エントリスクリプトで着目するのは主に3点。
環境の切り替え
上記は開発環境の場合の例。本番環境の場合は以下の通り。defined('YII_DEBUG') or define('YII_DEBUG', false); defined('YII_ENV') or define('YII_ENV', 'prod');アプリケーションの設定
backend, common, frontendディレクトリに注目する。それぞれにconfigディレクトリがある。(Yiiのディレクトリ構成はこちらを参照。)
backendというアプリケーションにのみ適用する設定はbackend/configに、frontendというアプリケーションにのみ適用する設定はfrontend/configに保存する。(そしてhogeというアプリケーションにのみ適用する設定はhoge/configに保存する。)アプリケーション間で共通の設定はcommon/configに保存する。
○○というアプリケーションのエントリスクリプトで○○/configとcommon/configの設定ファイルを読み込む。
Yiiではオートローダをここで読み込む。アプリケーションの実体
読み込んだ設定($config
)をyii\web\Application
の引数に指定してインスタンス化し、run()を実行する。GoFでいうところのfacadeパターンかしら。
ちなみに、インスタンス化する際にpreInit()を呼び出して、$config
の初期値を設定する。例えば、エントリスクリプトで読み込んだ$config
中にviewコンポーネントの設定がない場合、preInit()の中でデフォルト値(['class' => 'yii\web\View']
)が設定される。yii-application/vendor/yiisoft/yii2/base/Application.phppublic function __construct($config = []) { (省略) $this->preInit($config); (省略) Component::__construct($config); } public function preInit(&$config) { (省略) // merge core components with custom components foreach ($this->coreComponents() as $id => $component) { if (!isset($config['components'][$id])) { $config['components'][$id] = $component; } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) { $config['components'][$id]['class'] = $component['class']; } } } public function coreComponents() { return [ 'log' => ['class' => 'yii\log\Dispatcher'], 'view' => ['class' => 'yii\web\View'], 'formatter' => ['class' => 'yii\i18n\Formatter'], 'i18n' => ['class' => 'yii\i18n\I18N'], 'mailer' => ['class' => 'yii\swiftmailer\Mailer'], 'urlManager' => ['class' => 'yii\web\UrlManager'], 'assetManager' => ['class' => 'yii\web\AssetManager'], 'security' => ['class' => 'yii\base\Security'], ]; }preInit()の後に
Component::__construct($config)
を呼び出す。
yii-application/vendor/yiisoft/yii2/base/BaseObject.php
からBaseYii.php
に渡り、configure()を実行する。yii-application/vendor/yiisoft/yii2/BaseYii.phppublic static function configure($object, $properties) { // $propertiesは元を辿れば$config foreach ($properties as $name => $value) { // Componentの__set等が発火する。 $object->$name = $value; } return $object; }yii-application/vendor/yiisoft/yii2/base/Component.phppublic function __set($name, $value) { // $name = 'components'の時、setComponents()が実行される。 $setter = 'set' . $name; if (method_exists($this, $setter)) { // set property $this->$setter($value); (省略) }yii-application/vendor/yiisoft/yii2/di/ServiceLocator.phppublic function setComponents($components) { foreach ($components as $id => $component) { // 例 // $id = view // $component = ['class' => 'yii\web\View'] $this->set($id, $component); } } public function set($id, $definition) { (省略) // 後にコンポーネントをインスタンス化する際に利用する。 $this->_definitions[$id] = $definition; (省略) }オートローダ
上記の通りエントリスクリプトでオートローダを読み込む以外にもオートロードを実装する方法を提供している。spl_autoload_register()でパスを直書きするのは汎用性に欠けるため、composerのような気の利いた仕組みをフレームワークに組み込んである。
composerを利用したオートロードの仕組みについて、参考エントリはこちら。ルーティング
yii\web\Application
のrun()の中でURLを解釈してMVCに処理させるfunctionが呼び出される。yii-applicaiton/vendor/yiisoft/yii2/base/Application.phppublic function run() { try { $this->state = self::STATE_BEFORE_REQUEST; $this->trigger(self::EVENT_BEFORE_REQUEST); $this->state = self::STATE_HANDLING_REQUEST; // ここに注目。この中でURLを解釈している。 $response = $this->handleRequest($this->getRequest()); $this->state = self::STATE_AFTER_REQUEST; $this->trigger(self::EVENT_AFTER_REQUEST); $this->state = self::STATE_SENDING_RESPONSE; $response->send(); $this->state = self::STATE_END; return $response->exitStatus; } catch (ExitException $e) {handleRequest()の実装は
web/Application.php
yii-application/vendor/yiisoft/yii2/web/Application.phppublic function handleRequest($request) { if (empty($this->catchAll)) { try { list($route, $params) = $request->resolve(); (省略) $result = $this->runAction($route, $params); (省略)runAction()の実装は
base/Module.php
yii-application/vendor/yiisoft/yii2/base/Module.phppublic function runAction($route, $params = []) { // コントローラのクラスをインスタンス化する。 $parts = $this->createController($route); if (is_array($parts)) { list($controller, $actionID) = $parts; (省略) // コントローラのアクションを実行する。 $result = $controller->runAction($actionID, $params); (省略) return $result; } (省略)イベント
run()の
$this->trigger
にも注目してみる。アプリケーションの特定のタイミングでコードを挿入するイベントという仕組み。イベントと処理内容を予め定義し、登録しておくと、イベントが発生した時に、登録しておいたコードが実行される。ガイドはこちら。
$this->state
は例外をキャッチした時に呼び出されるend()の中で参照する。アプリケーションの終わらせ方を制御する。コントローラ
ガイドはこちら。ざっくりとした処理の流れは以下の通り。
1. 上記の通りhandleRequest()でURLを解釈して、コントローラ、アクション及びパラメータを特定する。
2. コントローラのクラスをインスタンス化する。
3. アクション(コントローラクラスのメソッド)を実行する。パラメータを渡して。継承関係は以下の通り。
開発者が作ったコントローラ ↓ yii\web\Controller(コンソールアプリケーションの場合yii\console\Controller) ↓ yii\base\Controller ↓ yii\base\Component ↓ yii\base\BaseObject例えばビューを呼び出すrender()は
base/Controller.php
で実装されている。yii-application/vendor/yiisoft/yii2/base/Controller.phppublic function render($view, $params = []) { // $this->getView()のrender()に委譲している。 // getView()により委譲先のビューオブジェクトを決定する。 // $contentはviews/layouts/main.phpが参照する$content $content = $this->getView()->render($view, $params, $this); return $this->renderContent($content); } // レイアウト(例えばviews/layouts/main.php)に$contentを渡して描画する。 public function renderContent($content) { $layoutFile = $this->findLayoutFile($this->getView()); if ($layoutFile !== false) { // ビューのクラスのrenderFile()に委譲している。 return $this->getView()->renderFile($layoutFile, ['content' => $content], $this); } return $content; }getView()
yii-application/vendor/yiisoft/yii2/base/Application.phppublic function getView() { return $this->get('view'); }yii-application/vendor/yiisoft/yii2/base/Module.phppublic function get($id, $throwException = true) { if (!isset($this->module)) { return parent::get($id, $throwException); } // Moduleの親クラスはServiceLocator $component = parent::get($id, false); if ($component === null) { $component = $this->module->get($id, $throwException); } return $component; }yii-application/vendor/yiisoft/yii2/di/ServiceLocator.phppublic function get($id, $throwException = true) { (省略) // コンポーネントのオブジェクトを作成する。 // 先の処理で$definitionに['class' => 'yii\web\View']が設定してあるからViewのオブジェクトが生成できる。 return $this->_components[$id] = Yii::createObject($definition); (省略) }ビュー
ガイドはこちら。
yii-appliation/vendor/yiisoft/yii2/base/View.php// base/Controllerのrender()から呼び出される。 public function render($view, $params = [], $context = null) { // 処理内容はメソッド名から察する。 $viewFile = $this->findViewFile($view, $context); return $this->renderFile($viewFile, $params, $context); } // base/ControllerのrenderContent()からも呼び出される。 public function renderFile($viewFile, $params = [], $context = null) { }ビューのクラスはコントローラと同じく階層構造になっている。
但し、開発者はyii\web\View
を継承してビューのクラスを作成するのではなく、ビューのクラスからから読み込まれるfrontend/views/site/index.php
などのファイルを作成する。yii\web\View ↓ yii\base\View ↓ yii\base\Component ↓ yii\base\BaseObjectモデル
ガイドはこちら。
モデルのクラスの継承関係はこんな感じ。
開発者が作ったモデル ↓ yii\db\ActiveRecord ↓ yii\db\BaseActiveRecord ↓ yii\base\Model ↓ yii\base\Component ↓ yii\base\BaseObjectコントローラの中でモデルのクラスをインスタンス化して(または静的に呼び出して)利用する。例えばユーザーの情報をDBから取得して表示するのは以下のような実装になる。
yii-appliation/backend/controllers/UserController.phppublic function actionView($id) { return $this->render('view', [ 'model' => $this->findModel($id), ]); } protected function findModel($id) { if (($model = User::findOne($id)) !== null) { return $model; } else { throw new NotFoundHttpException(Yii::t('yii', 'Page not found.')); } }yii-appliation/backend/models/User.phpclass User extends \yii\db\ActiveRecord { // Userモデルに固有の処理をここに書く。 // DBアクセスなど全てのモデルに共通の機能はActiveRecord等に実装されているそれらを利用する。 }yii-application/vendor/yiisoft/yii2/db/BaseActiveRecord.phppublic static function findOne($condition) { return static::findByCondition($condition)->one(); }yii-application/vendor/yiisoft/yii2/db/ActiveRecord.phpprotected static function findByCondition($condition) { // ここで呼出し元のモデル(例えばUserモデル)と関連づけられる。 $query = static::find(); if (!ArrayHelper::isAssociative($condition)) { $primaryKey = static::primaryKey(); if (isset($primaryKey[0])) { $pk = $primaryKey[0]; if (!empty($query->join) || !empty($query->joinWith)) { $pk = static::tableName() . '.' . $pk; } $condition = [$pk => is_array($condition) ? array_values($condition) : $condition]; } else { throw new InvalidConfigException('"' . get_called_class() . '" must have a primary key.'); } } elseif (is_array($condition)) { $aliases = static::filterValidAliases($query); $condition = static::filterCondition($condition, $aliases); } return $query->andWhere($condition); } public static function find() { // 例 // ActiveQuery::className() = 'yii\db\ActiveQuery' // [get_called_class()] = 'backend\models\User' return Yii::createObject(ActiveQuery::className(), [get_called_class()]); }DBの接続
yii-application/vendor/yiisoft/yii2/base/Application.php
のgetDb()でDBに接続しにいく。DBの接続に必要な情報は、以下のような感じで設定する。設定の仕組みについてはアプリケーションの設定参照。yii-application/common/config/main-local.phpreturn [ 'components' => [ 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=akou', 'username' => 'akakin', 'password' => 'gyagyagya', 'charset' => 'utf8', 'tablePrefix' => 'tbl_', ], ], ];
- 投稿日:2019-08-19T21:21:45+09:00
Visual Studio CodeでPHPをデバッグする方法
実施環境
- Windows10
- Visual Studio Code 1.37.1(以下VSCode)がインストール済
- xamppがC直下にインストール済(PHP 7.3.7)
手順
- VSCode設定ファイル追記
- VSCode拡張機能「PHP Debug」のインストール
- PHPデバッグツール「XDebug」のインストール
- 「XDebug」をVSCodeに紐づけ
- デバッグを行う
1. VSCode設定ファイル追記
VSCodeを立ち上げ
ファイル>基本設定>「settings.json」を検索>「settings.jsonで編集」をクリックJSONファイルに以下の内容を追記します。
"php.validate.executablePath": "C:\\xampp\\php\\php.exe" "php.validate.run": "onType"2. VSCode拡張機能「PHP Debug」のインストール
Ctrl + Shift + X で拡張機能検索ウィンドウを開きます。
「PHP Debug」と入力するとパッケージが表示されるので「インストール」をクリックします。
ボタンが「アンインストール」になったら成功。3. PHPデバッグツール「XDebug」のインストール
C:\xampp\htdocsの階層にphpinfo.phpというファイルを作成し、以下のコードを記述。
<?php phpinfo(); ?>xamppのコントロールパネルを開き、Apacheを起動。
C:\xampp\xampp-control.exe
起動後、http://localhost/phpinfo.php にアクセスすると下記のような画面が表示されるので、Ctrl + A で全文コピー。https://xdebug.org/wizard.php を開き、テキストボックスに貼り付け。
Analyse my phpinfo() outputボタンをクリックし、表示されたDLLファイルをダウンロード。
ダウンロードしたDLLファイルは下記ディレクトリ配下へ移動。
C:\xampp\php\ext4.「XDebug」をVSCodeに紐づけ
C:\xampp\php\php.iniに以下のコードを追記します。
※ zend_extensionの値は3.でダウンロードしたDLLファイル名... [XDebug] xdebug.remote_enable = 1 xdebug.remote_autostart = 1 xdebug.remote_connect_back = 1 xdebug.remote_port = 9000 zend_extension = C:\xampp\php\ext\php_xdebug-2.7.2-7.3-vc15-x86_64.dll5. デバッグを行う
ファイル>フォルダーを開く>デバッグしたいファイルのある階層を選択>デバッグするファイルを開く>左メニューバーからデバッグボタンをクリック
「デバッグ開始」ボタン横のプルダウンから「PHP」を選択。
再度プルダウンより「構成の追加」を選択すると、launch.jsonが開くので下記のように修正。{ // IntelliSense を使用して利用可能な属性を学べます。 // 既存の属性の説明をホバーして表示します。 // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Listen for XDebug", "type": "php", "request": "launch", "port": 9000, "pathMappings": { "${workspaceRoot}": "${workspaceRoot}" } }, { "name": "Launch currently open script", "type": "php", "request": "launch", "program": "${file}", "cwd": "${fileDirname}", "port": 9000, "pathMappings": { "C:\\xampp\\htdocs\\test": "${workspaceRoot}" }, "runtimeExecutable": "C:\\xampp\\php\\php.exe" } ] }デバッグしたいファイルにブレークポイントを置く。
「Launch currently open script」が選択されていることを確認し、デバック開始ボタンを押下。
- 投稿日:2019-08-19T17:10:57+09:00
PHPで書かれたスクリプトをAWS Lambda上で定期実行する
AWS上で動いているシステムがあって、さらに定期実行したいPHPで書かれたスクリプトをどこかで実行することになった。
適当なマシン上で定期実行することもできるが、今回AWSを使っているのでAWS Lambdaで動かすことにしてみた。以下ではスクリプトのサンプルとして、「AWS Lambdaの実行リージョンと同じリージョンにある、そのアカウントが持つEC2インスタンスのIDのリストを取得する」ものを実行することにする。
事前調査
AWS Lambdaをまだ使ったことがなかったので、最初に目的通りできそうか調査を行った。
- Lambda上で実行したものは最大15分で強制終了させられる。
- つまり最大15分で終了するインスタンスであると解釈することができる。実際のところAmazon Linuxが動いているようだ。
- 今のところそんなに時間が掛かるスクリプトはない想定だが、それ以上掛かる場合はAWS Batch辺りを使うのだろう。
- マシンサイジングできるのはメモリ量だけ。メモリ量は128MBから3008MBまで64MB刻みで割り当てることが可能。
- 現在Lambdaの標準ランタイムとして用意されているのはNode.js, Python, Ruby, Java, Go, .NETであり、PHPは用意されていない。しかし、カスタムランタイムを作ってやって渡してやればPHPも実行可能になる。また他の言語でもAmazon Linux上で動くものなら実行できるようにできると考えられる。
- PHPのカスタムランタイムは自分で作っても良いが、すでに提供してくれているところもあるので、問題なければこれを使えば良い。結局自分で作ることはなかったので作り方は調べていないがこのリンク先にあるshファイルを見れば良いと思う。
- トリガーにCloudwatch Eventsを使用すると時刻をトリガーにできるので定期実行可能。
- Lambdaには無期限の無料利用枠があり、割と頑張らないと無料枠から脱出できない。
というわけで行けそうなので進める。
デフォルト構成の調査
AWSマネジメントコンソールを使って、Lambda関数を"一から作成"の「カスタムランタイム」の「デフォルトのブートストラップを使用する」で1つ作ってみた。
「関数コード」を見ると、bootstrap, hello.sh, README.mdの計3ファイルが生成されたのが見える。README.mdを読んでわかることは、
- まず読むべきドキュメントは https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-custom.html
- 使いたい(自分で作った、あるいは誰かが提供している)カスタムランタイムはレイヤーに設定する。
- このLambda関数が実行される際に、実際に実行されるのは「関数コード」でルートにあるbootstrapである。
なお、これはREADME.mdではなく後で実行してみてわかったことだが、ルートにbootstrapがなければレイヤーに含まれるbootstrapファイルが実行されるようだ。
続けてbootstrapを確認。
bootstrap#!/bin/sh set -euo pipefail # Handler format: <script_name>.<function_name> # # The script file <script_name>.sh must be located at the root of your # function's deployment package, alongside this bootstrap executable. source $(dirname "$0")/"$(echo $_HANDLER | cut -d. -f1).sh" while true do # Request the next event from the Lambda runtime HEADERS="$(mktemp)" EVENT_DATA=$(curl -v -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next") INVOCATION_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) # Execute the handler function from the script RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA") # Send the response to Lambda runtime curl -v -sS -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$INVOCATION_ID/response" -d "$RESPONSE" doneつまり、
- http://\${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next をGETする。
- そのレスポンスヘッダの中にLambda-Runtime-Aws-Request-Idがあるので、その値を取得しINVOCATION_IDとする。
- 任意のスクリプトを実行する。
- http://\${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$INVOCATION_ID/response にスクリプトの実行結果をPOSTする。
- ここまでをwhileで延々とループさせる。
というのがbootstrapがやっていることであると読める。
AWSマネジメントコンソール上だと「関数コード」で設定できるハンドラというものがある。ここで設定した値は環境変数_HANDLERに入る。
ハンドラ名は"hello.handler"が初期設定である。そのため、bootstrapの
source $(dirname "$0")/"$(echo $_HANDLER | cut -d. -f1).sh"
行および、
RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")
行はhello.shのhandler関数を呼んでいることになる。
hello.shの中身は以下なので、スクリプトの実行結果はJSONを期待されているようだ。hello.shfunction handler () { EVENT_DATA=$1 RESPONSE="{\"statusCode\": 200, \"body\": \"Hello from Lambda!\"}" echo $RESPONSE }結局こちらでやるべきことは以下となる。
- レイヤーに https://github.com/stackery/php-lambda-layer に書かれているものを設定する。PHP 7.3なら「arn:aws:lambda:(リージョン):887080169480:layer:php73:3」。
- bootstrapを修正してPHPのスクリプトを呼ぶようにする。
- PHPのスクリプトはbootstrapから呼べる場所に置く。
Lambda側へ渡したいファイルの作成
というわけでbootstrapをPHPのスクリプトを呼ぶように修正してみる。
bootstrap#!/bin/sh set -euo pipefail while true do # Request the next event from the Lambda runtime HEADERS="$(mktemp)" EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next") INVOCATION_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) # Execute the handler /opt/bin/php -c "${LAMBDA_TASK_ROOT}/php.ini" "${LAMBDA_TASK_ROOT}/${_HANDLER}.php" if [ $? -eq 0 ]; then RESPONSE="{\"statusCode\": 200, \"body\": \"Success\"}" else RESPONSE="{\"statusCode\": 500, \"body\": \"Error\"}" fi # Send the response to Lambda runtime curl -sS -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$INVOCATION_ID/response" -d "$RESPONSE" > /dev/null done上記の通り
/opt/bin/php -c "${LAMBDA_TASK_ROOT}/php.ini" "${LAMBDA_TASK_ROOT}/${_HANDLER}.php"
としたので、スクリプトファイルはbootstrapと同じディレクトリに"(ハンドラ名).php"という名前で置くことになる。
もっとも、ファイル名は変化するわけではないのでハンドラ名なんて使わなくても良いのだが、設定必須項目が使われないのもちょっと、ということで。ここでphp.iniも使うようにしている。
これは今回のサンプルスクリプトがsimplexml.soとjson.soを使うので、それらをロードする必要があるためである。
simplexml.soとjson.soは https://github.com/stackery/php-lambda-layer に書かれている通りカスタムランタイム側で用意してくれているので、これらをロードすれば良い。
内容は以下となる。extension_dirでsoファイルが置かれているディレクトリを指定しないとロードできなかった。
これもbootstrapと同じディレクトリに配置する。php.iniextension_dir=/opt/lib/php/7.3/modules extension=simplexml extension=json説明をbootstrap側に戻す。
スクリプトの実行結果はスクリプトの実行時のリターンコードが0かどうかで中身を変えているだけに今回はしてある。他にも元のbootstrapと比べて特に欲しくない情報は出力しないようにしている。
これは標準出力や標準エラー出力へのすべての書き出しがCloudwatch Logsに出力されるからである。
逆に言えば、スクリプト側ではログ出力したい情報は標準出力か標準エラー出力に書き出すようにしておくと良い。また、 http://\${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next をGETした際のレスポンスボディ(EVENT_DATA変数に格納されるもの。JSONである)はまったく使わずに握り潰している。
今回のサンプルスクリプトファイルの内容は以下である。
先に書いた通り、単に「AWS Lambdaの実行リージョンと同じリージョンにある、そのアカウントが持つEC2インスタンスのIDのリストを取得する」だけのものとさせてもらっている。script.php<?php require 'aws/aws-autoloader.php'; use Aws\Ec2\Ec2Client; $ec2Client = new Ec2Client([ 'version' => 'latest', 'region' => $_ENV['AWS_REGION'], ]); $reservations = $ec2Client->describeInstances()['Reservations']; foreach ($reservations as $reservation) { echo $reservation['Instances'][0]['InstanceId'] . "\n"; } ?>このスクリプトのファイル名をscript.phpという名前にしたので、ハンドラ名はscriptとなる。
AWS SDK for PHPを呼んでいるがインストールは https://docs.aws.amazon.com/ja_jp/sdk-for-php/v3/developer-guide/getting-started_installation.html の一番下、「ZIPファイルを使用したインストール」で行っている。
展開位置はbootstrapと同じディレクトリであり、つまりbootstrap, php.ini, script.phpの3ファイルが置かれているディレクトリにawsディレクトリが作られている。
なお、私はPHPをほとんど触ったことないのでComposerの使い方とか知らないが、一般にはComposerを使うものだと思われる。Lambda側へファイルを渡すための準備
修正したbootstrapやphp.ini, script.php、それに展開したAWS SDK for PHPはLambda側に置く必要がある。
AWSマネジメントコンソールを使うなら「関数コード」にてzipにして渡したりできる。
今回はCloudformationを使う。その場合、zipをS3に置いておく必要がある。zipにする時の注意だが、bootstrapはLinuxファイルシステムにおける実行権限がついていなければならない。
特にWindows上で作業する場合は注意すること。WSLを使って作業するなどで問題ないと思われるが。また、ルートディレクトリがzip書庫内に含まれていてはならない。
私は以下のコマンドで圧縮している。
ここでlambda-phpはbootstrapやphp.ini, script.php, 展開したAWS SDK for PHPが置かれているディレクトリとする。$ cd lambda-php; zip -r ../src.zip .; cd -この作成したzip(ここではsrc.zipという名前にしている)をS3にアップロードするが、こちら側の注意点としては https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html のS3Bucketの項に書かれている通り、Cloudformationを実行する(=Lambda関数が作成される)リージョンと同じリージョンのバケットを使用しなければならないことである。
また、バケットは別のAWSアカウントのものでも良いが、その場合はCloudformationを実行するアカウントからsrc.zipがアクセス権限上ダウンロード可能になっていないとならない。簡単にはパブリックアクセス可能にしておくなど。
Cloudformationテンプレートの作成
Lambda関数の一式をCloudformationで作成するにあたり、 https://github.com/stackery/php-lambda-layer にはAWS SAMを使った例が出ている。
これをやりたいことに合わせて適当に修正したtemplate.ymlというファイルにしたものが以下である。
(先に言っておくと私はこの方法を使用していないので、やり方だけ書く)template.ymlAWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Resources: IamRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: "CreateLogPolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "logs:CreateLogGroup" Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*" - Effect: "Allow" Action: - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-function:*" - PolicyName: "ScriptPolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "ec2:DescribeInstances" Resource: "*" ServerlessFunction: Type: "AWS::Serverless::Function" Properties: FunctionName: !Sub "${AWS::StackName}-function" CodeUri: src Runtime: provided Handler: script Role: !GetAtt IamRole.Arn MemorySize: 128 Timeout: 10 Layers: - !Sub "arn:aws:lambda:${AWS::Region}:887080169480:layer:php73:3" Events: event: Type: Schedule Properties: Schedule: "cron(*/5 * * * ? *)"Eventsは5分ごとに定期実行するための設定にしてある。なお、実際には20から30秒程度遅れて実行されるようだ。実行環境の起動に掛かる時間だろうか。
スケジュールはcronの時刻設定書式が使用できるが、 https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html の通り一般的なcronのものの書式とは違いがあり、年を指定でき、日か曜日の使わない方は"?"にするなどの必要がある。
IAM roleはCloudwatch Logsにログを出力するのを許可するもの(CreateLogPolicy)と、スクリプトで必要なEC2インスタンスの一覧を取得するのを許可するもの(ScriptPolicy)を設定してある。
カスタムランタイムを使用する場合、通常Runtimeを"provided"にしてLayersに使用するカスタムランタイムを設定する。
また、与えるメモリ量は最小の128MB、強制終了までの時間は10秒に設定している。AWS CLIがインストールされた環境で、
aws cloudformation package --s3-bucket (deployを実行するのと同じリージョンにある適当な存在するバケット名) --template-file template.yml --output-template-file output.yml
を実行するとCodeUriで指定したディレクトリの中にあるファイルを再帰的にzip圧縮してS3の--s3-bucketで指定したバケットにアップロードしてくれ(ファイル名はzipファイルのmd5のように見える)、Cloudformationに食わせられるテンプレートファイルを--output-template-fileに指定した名前で生成してくれる。
ただ、このzip作成時にbootstrapに自動的に実行権限を付けてくれたら嬉しかったのだがそうはいかなかった。生成されたoutput.ymlの内容は以下である。
output.ymlAWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Resources: IamRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: CreateLogPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup Resource: Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:* - Effect: Allow Action: - logs:CreateLogStream - logs:PutLogEvents Resource: Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-function:* - PolicyName: ScriptPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - ec2:DescribeInstances Resource: '*' ServerlessFunction: Type: AWS::Serverless::Function Properties: FunctionName: Fn::Sub: ${AWS::StackName}-function CodeUri: s3://xxxxxxxxxxxx/6d54284568d5c9f126c866bc6483835d Runtime: provided Handler: script Role: Fn::GetAtt: - IamRole - Arn MemorySize: 128 Timeout: 10 Layers: - Fn::Sub: arn:aws:lambda:${AWS::Region}:887080169480:layer:php73:3 Events: event: Type: Schedule Properties: Schedule: cron(*/5 * * * ? *)この生成されたoutput.ymlを使用して
aws cloudformation deploy --template-file output.yml --capabilities CAPABILITY_IAM --stack-name (適当なスタック名)
を実行すると、Cloudformationを使用したデプロイが実行される。
https://github.com/stackery/php-lambda-layer ではこれらのコマンドはAWS SAM CLIを使用して実行されているが、インストールしてみてsam --help
するとsam package
はaws cloudformation package
の、sam deploy
はaws cloudformation deploy
のエイリアスであることが分かるので、たぶんSAM CLIをインストールする必要は実際にはないと思われる。生成されたoutput.ymlはSAMテンプレート形式で記述されており、Cloudformationで実行時にTransformによりCloudformationテンプレート形式に変換される。
実行しないと実際に何になるのかがわからないのがちょっと嫌だったので、実行後にAWSマネージメントコンソールのCloudformationのところで見られる変換後のテンプレートを参考に、最初からCloudformationテンプレート形式で書くことにした、というのが先に書いた通り上記のSAMテンプレートを使用する方式を使わなかった理由である。
aws cloudformation package
を使っていないので、上記「Lambda側へファイルを渡すための準備」の通りに圧縮して作ったsrc.zipをS3(以下の例では「xxxxxxxxxxxx-(リージョン名)」というバケット)にアップロードしてある。というわけで作成したCloudformationテンプレートが以下である。
AWSTemplateFormatVersion: "2010-09-09" Resources: IamRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: "CreateLogPolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "logs:CreateLogGroup" Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*" - Effect: "Allow" Action: - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-function:*" - PolicyName: "ScriptPolicy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "ec2:DescribeInstances" Resource: "*" LambdaFunction: Type: "AWS::Lambda::Function" Properties: FunctionName: !Sub "${AWS::StackName}-function" Code: S3Bucket: !Sub "xxxxxxxxxxxx-${AWS::Region}" S3Key: src.zip Handler: script MemorySize: 128 Timeout: 10 Role: !GetAtt IamRole.Arn Runtime: provided Layers: - !Sub "arn:aws:lambda:${AWS::Region}:887080169480:layer:php73:3" EventsRule: Type: "AWS::Events::Rule" Properties: ScheduleExpression: "cron(*/5 * * * ? *)" Targets: - Id: !Sub "${AWS::StackName}-rule-target" Arn: !GetAtt LambdaFunction.Arn LambdaPermission: Type: "AWS::Lambda::Permission" Properties: Action: "lambda:invokeFunction" Principal: "events.amazonaws.com" FunctionName: !Ref LambdaFunction SourceArn: !GetAtt EventsRule.ArnSAMテンプレートのAWS::Serverless::Functionタイプが、CloudformationテンプレートだとAWS::Lambda::Function, AWS::Events::Rule, AWS::Lambda::Permissionの3つになる感じか。
これを
aws cloudformation deploy --template-file (テンプレートファイル名) --capabilities CAPABILITY_IAM --stack-name (適当なスタック名)
を実行すると、こちらでもCloudformationを使用したデプロイが実行される。
AWSマネジメントコンソールを使うなら"スタックの作成"でテンプレートファイルをアップロードし、「スタックの名前」を入力して「AWS CloudFormationによってIAMリソースが作成される場合があることを承認します。」のチェックを入れて「スタックの作成」を行えば同じことになる。ここまでやってCloudwatch Logsを見ると5分置きに実行されているのがわかる。
- 投稿日:2019-08-19T15:59:52+09:00
webarenaでubuntu その18
- 投稿日:2019-08-19T14:45:27+09:00
migrateメモ
LarabelでのDB作成時に、migrateというものがあったので、使ってみました。
チームで全く同じDBをつかいたいときなどは、migratefileをそろえておくとまったくおなじDBがつかえます。DB作成
CREATE DATABASE DB名;マイグレーション生成
php artisan make:migration テーブル名テーブル作成
new_hoge_table.phppublic function up() { Schema::create('テーブル名', function (Blueprint $table) { $table->increments('カラム名');//autoincrement主キー $table->integer('カラム名')->unsigned();//int型つかえないのでmigrationの場合はinteger $table->string('カラム名', 文字数)->default("")->unique();//ユニークの書き方はこんな感じ $table->datetime('カラム名')->default(DB::raw('CURRENT_TIMESTAMP'));//デフォルトでINSERTした日付とってくる $table->boolean('カラム名')->default(0);//true or false // 外部キー $table->foreign('外部キー') ->references('参照カラム') ->on('参照テーブル名'); }); } public function down() { Schema::dropIfExists('テーブル名'); }おまじない
外部キー設定がうまくいかなかったら意味もなくこれつけてみる$table->engine = 'InnoDB';
※カラムに関してはよくわからないけど、使えない型があるので注意。intとか使えないみたいです。新規データベースならば、上のupメソッドを修正する
しかし、リリース後のDB定義修正の場合は、直接修正するのではなく、新たにファイルを作成し、修正を記述
fix_hoge_table.phppublic function up() { Schema::table('修正テーブル名', function (Blueprint $table) { $table->boolean('maintenance_flag')->after('start_date')->default(0)->nullable(false); $table->string('maintenance_message',40)->after('maintenance_flag')->nullable(true); $table->string('maintenance_message', 255)->change(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('修正したいテーブル', function (Blueprint $table) { //追加したいカラム(修正はかかなくてもいいはず) $table->dropColumn('maintenance_flag'); $table->dropColumn('maintenance_message'); }); } }マイグレーション実行
php artisan migrateロールバック
php artisan migrate:rollbackリセット
php artisan migrate:reset初期データをSeedでセット
シーダー生成のためのartisanコマンドを叩く
php artisan make:seeder テーブル作成したクラス名TableSeederpublic function run() { // DB::table('テーブル名')->insert([ 'id' => 300, 'room_id' => 100, 'status' => 1, 'created' => date('Y-m-d H:i:s'), 'updated' => date('Y-m-d H:i:s'), ], [ 'room_id' => 101, 'status' => 0, 'created' => date('Y-m-d H:i:s'), 'updated' => date('Y-m-d H:i:s'), ], }シーダクラスをコールする為の設定
シーダクラスの定義が完了したら、このシーダクラスをシーディング実行時にコール(呼び出す)できるようにします。seedsディレクトリにあるもう一つのファイルDatabaseSeeder.phpに以下を記述します。
laravel/database/seeds/DatabaseSeeder.phppublic function run() { $this->call([ 作成したテーブルのクラス名TableSeeder::class, ]); }シーディング実行
php artisan db:seed
- 投稿日:2019-08-19T11:54:33+09:00
【Laravel】migrationで複合主キーのうち1つをincrementsに指定したい
id
をオートインクリメント
id
、user_id
、type
を複合主キーに設定したい。class CreateFoosTable extends Migration { public function up() { Schema::create('foos', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id'); $table->tinyInteger('type'); $table->text('text'); $table->timestamps(); $table->primary(['id', 'user_id', 'type']); }); } }マイグレーションを実行しようとするとエラーが出る
Syntax error or access violation: 1068 Multiple primary key definedすでにプライマリーキーがすでに設定されているというエラー
$table->increments('id');
で主キーを設定して
$table->primary(['id', 'user_id', 'type']);
で再度複合主キーを設定しようとしている?そう思ったので、
id
を一度integer
に設定して主キーを設定した後にincrements
に変更してみた。class CreateFoosTable extends Migration { public function up() { Schema::create('foos', function (Blueprint $table) { $table->integer('id'); $table->integer('user_id'); $table->tinyInteger('type'); $table->text('text'); $table->timestamps(); $table->primary(['id', 'user_id', 'type']); }); Schema::table('foos', function (Blueprint $table) { $table->increments('id')->change(); }); } }
requires Doctrine DBAL; install "doctrine/dbal".
doctrine/dbal
を追加しろと言われたので追加https://readouble.com/laravel/5.8/ja/migrations.html
カラム変更
動作要件
カラムを変更する前に、composer.jsonファイルでdoctrine/dbalを確実に追加してください。Doctrine DBALライブラリーは現在のカラムの状態を決め、指定されたカラムに対する修正を行うSQLクエリを生成するために、使用しています。$composer require doctrine/dbalできた。
- 投稿日:2019-08-19T08:17:41+09:00
php-master-changes 2019-08-18
今日は不要コードを削除する修正があった!
2019-08-18
beberlei: Cleanup unnecessary if guard clause to free buffer.
- https://github.com/php/php-src/commit/58d661cd7d0c60aba2708993e584ef47b6cd3cd8
- 不要コードの削除
- どっちにしても free() して return じゃねえか!っていう
- 投稿日:2019-08-19T02:19:46+09:00
プログラマーがWordPressが使いにくい理由を整理してみた。
きっかけ
仕事でWordPressを扱っているが、WordPressは正直使いにくい。
趣味でRuby on Rails やLaravelに触れたことのある私だが、WordPressはどうも好きになれない。
構築は楽だし慣れれば早いんだけど、WP全体の仕様に理解ができない。
どうやら、プログラマー側から出てきた人間であればあるほどそう思うらしく、
PHPのフレームワークでWordPressしかやったことがない人間からすれば、別に違和感を感じないらしい。
その理由として極一部であるが、どうしてそう思うのか、整理してみた。使いにくい理由
値取得とテキスト表示が別関数
例えば、サイトのタイトルを表示させたいときは
<?php the_title(); ?>なんだけど、タイトルを関数に入れて表示させたいときは
<?php $title = get_the_title(); echo $title ?>と、別の関数が用意されている。
全ての表記が統一されておらず、サイトのURLを示すhome_url()
はechoをつけないと表示されない。カテゴリーは配列であるが、それが関数で分からないのがすごくややこしい。テーマ内で色々指定するファイルがfunction.phpだけ
function.phpでは
・ 投稿設定(登校時にpタグを削除する等)
・ フロント表示設定(自動で表示されるmetaタグ削除等)
・ 管理画面表示設定(メニュー非表示・ログイン時アイコン設定等)
・ 独自関数設定
・ オリジナル投稿(カスタム投稿)設定
・ 投稿画面で使える関数を設定(ショートコード)
・ ウィジェット作成
・ プラグインの詳細設定
などなど、
設定しようと思えば、なんでも出来てしまう。
しかし、これらを設定する場所がfunction.phpしかない。
カスタマイズをすればするほど、ファイルが長くなり、5000行を軽く超えるファイルになる。
そうなると、修正したいときに探すだけで一苦労だし、ダブっているソースに気付きにくいのも同然である。管理画面上でしか設定出来ないこともあるし、どっちでも設定できることがある煩わしさ
全部function.phpで設定出来るならば移植も楽なんだけど、そうではない。
固定ページは一度管理画面で作成しなくちゃならないし、
投稿数を適切に設定しないと投稿表示がうまく表示されない。
管理画面の設定なのか、function.phpの設定なのか、はたまたプラグインの衝突なのかわかりにくい。
バグによってはこれという解決方法が無いものもあるらしく、ため息が出てしまう。独特の専門用語が多すぎる
変に言葉を変えなくてもいいようなところを、専門用語を使ってややこしくしている節があるように思える。
デフォルトの記事の区分分けは「カテゴリー」「タグ」で、
オリジナルで設定する場合は「タクソノミー」。
オリジナルの投稿設定を「カスタム投稿」と言って、
カスタム投稿内での区分分けは「カスタムタクソノミー」。
カテゴリーやタクソノミー毎の「スラッグ(カテゴリー毎のID)名」を「ターム」。
通常の投稿枠にプラスしてオリジナルの入力枠を増やすのは「カスタムフィールド」書いているだけでも混乱するし、言葉で言われたらもっと理解できない。
命名の仕方の統一性すらない。
「ターム」は「スラッグ名」じゃダメなの?
なぜ、「タクソノミー」と「オリジナルカテゴリー」に命名を分ける仕様にしている???総括
ワードプレスは作り方が独特なように思う。
ノンエンジニアの為のフレームワークであり、Webデザイナーのための、コーダーの為のシステムって感じる。
PHPを学ぶよりも、一番最初にWordPressを学んだ人が、他でここの知識を転用出来たらいいんだけど、それが今のところ全くないんですよね。
WordPressの沼にハマった人は、そこから抜け出すのは大変だよなぁ。Wordpressの闇だよなぁ。
そのように思いました。