- 投稿日:2019-09-27T21:23:40+09:00
CentOS 7にPHP 7.2をインストール(Remi's RPM repository)
はじめに
Remi's RPM repositoryを利用してCentOS7にPHP7.3をインストール
親記事:PHP EOLと各種インストール方法
参考:Remi's RPM repositoryサポート
本手法で導入した場合、PHP: Supported Versions/PHP: Unsupported Branchesより、2021-12-06がEOLになると思われる。
それ以降に報告された脆弱性への対応は実施されない可能性がある。note
- EPEL repositoryも入れたいたほうが良いかも(本LOGでは入れてない)
# yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
- インストール後の更新は
yum --enablerepo=remi-php73 update
LOG
インストール
# cat /etc/redhat-release CentOS Linux release 7.7.1908 (Core) # yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm ... 略 # yum install -y --enablerepo=remi-php73 php which ... 略各種確認
# which php /usr/bin/php # php -v PHP 7.3.10 (cli) (built: Sep 24 2019 09:20:18) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.10, Copyright (c) 1998-2018 Zend Technologies # php -i | grep php.ini Configuration File (php.ini) Path => /etc Loaded Configuration File => /etc/php.ini # yum info php Loaded plugins: fastestmirror, ovl Loading mirror speeds from cached hostfile * base: ftp.iij.ad.jp * epel: nrt.edge.kernel.org * extras: ftp.iij.ad.jp * remi-safe: ftp.riken.jp * updates: ftp.iij.ad.jp Installed Packages Name : php Arch : x86_64 Version : 7.3.10 Release : 1.el7.remi Size : 10 M Repo : installed From repo : remi-php73 Summary : PHP scripting language for creating dynamic web sites URL : http://www.php.net/ License : PHP and Zend and BSD and MIT and ASL 1.0 and NCSA Description : PHP is an HTML-embedded scripting language. PHP attempts to make it : easy for developers to write dynamically generated web pages. PHP also : offers built-in database integration for several commercial and : non-commercial database management systems, so writing a : database-enabled webpage with PHP is fairly simple. The most common : use of PHP coding is probably as a replacement for CGI scripts. : : The php package contains the module (often referred to as mod_php) : which adds support for the PHP language to Apache HTTP Server.
- 投稿日:2019-09-27T21:23:40+09:00
CentOS 7にPHP 7.3をインストール(Remi's RPM repository)
はじめに
Remi's RPM repositoryを利用してCentOS7にPHP7.3をインストール
親記事:PHP EOLと各種インストール方法
参考:Remi's RPM repositoryサポート
本手法で導入した場合、PHP: Supported Versions/PHP: Unsupported Branchesより、2021-12-06がEOLになると思われる。
それ以降に報告された脆弱性への対応は実施されない可能性がある。note
- インストール後の更新は
yum --enablerepo=remi-php73 update
LOG
インストール
# cat /etc/redhat-release CentOS Linux release 7.7.1908 (Core) # yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm ... 略 # yum install -y --enablerepo=remi-php73 php which ... 略各種確認
# which php /usr/bin/php # php -v PHP 7.3.10 (cli) (built: Sep 24 2019 09:20:18) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.10, Copyright (c) 1998-2018 Zend Technologies # php -i | grep php.ini Configuration File (php.ini) Path => /etc Loaded Configuration File => /etc/php.ini # yum info php Loaded plugins: fastestmirror, ovl Loading mirror speeds from cached hostfile * base: ftp.iij.ad.jp * epel: nrt.edge.kernel.org * extras: ftp.iij.ad.jp * remi-safe: ftp.riken.jp * updates: ftp.iij.ad.jp Installed Packages Name : php Arch : x86_64 Version : 7.3.10 Release : 1.el7.remi Size : 10 M Repo : installed From repo : remi-php73 Summary : PHP scripting language for creating dynamic web sites URL : http://www.php.net/ License : PHP and Zend and BSD and MIT and ASL 1.0 and NCSA Description : PHP is an HTML-embedded scripting language. PHP attempts to make it : easy for developers to write dynamically generated web pages. PHP also : offers built-in database integration for several commercial and : non-commercial database management systems, so writing a : database-enabled webpage with PHP is fairly simple. The most common : use of PHP coding is probably as a replacement for CGI scripts. : : The php package contains the module (often referred to as mod_php) : which adds support for the PHP language to Apache HTTP Server.
- 投稿日:2019-09-27T20:44:38+09:00
PHPテストフレームワーク Codeception の使い方
YiiフレームワークはCodeceptionを公式にサポートしている。本稿ではYiiで使うことを前提にCodeceptionの使い方をざっくりまとめる。Yiiの使い方はこちら。
YiiでCodeceptionを利用するための設定
- devでアプリケーションを初期化する。(php init で devを選択する)
common/config/test.php 等の構成に従ってテスト用のDBを作成する。
$ mysql -u root -p mysql> DROP DATABASE IF EXISTS yii2advanced_test; mysql> CREATE DATABASE yii2advanced_test; mysql> DROP USER IF EXISTS 'yii2advanced_test'@'localhost'; mysql> CREATE USER 'yii2advanced_test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'xxxx'; mysql> GRANT ALL PRIVILEGES ON `yii2advanced_test`.* TO 'yii2advanced_test'@'localhost';migrationのコードを作成する。
PHPフレームワーク Yii の使い方#RBAC
PHPフレームワーク Yii の使い方#DBを定義する
を参考に。yii2advanced_testにテーブルを作成する。
$ ./yii_test migrate --migrationPath=@yii/rbac/migrations $ ./yii_test migrateテスト・スイートをビルドする。
$ vendor/bin/codecept buildCodeceptionがサポートするテストの種類と設定ファイル名
各テストの設定ファイルは
yii-application/frontend/tests/
以下にある。backend, commonも概ね同様。
テストの種類 設定ファイル 単体テスト(Unit Test) unit.suite.yml 機能テスト(Functional Test) functional.suite.yml 受入テスト(Acceptance Test) acceptance.suite.yml グローバルな設定は
yii-application/codeception.yml
に書く。yii-application/frontend/codeception.ymlnamespace: frontend\tests actor_suffix: Tester paths: tests: tests output: tests/_output data: tests/_data support: tests/_support settings: bootstrap: _bootstrap.php colors: true memory_limit: 1024M modules: config: Yii2: configFile: 'config/codeception-local.php'テストの実行方法
// 全てのテストを実行する $ vendor/bin/codecept run // frontend/testsのテストを実行する $ vendor/bin/codecept run -- -c frontend // 全ての単体テストを実行する $ vendor/bin/codecept run unit -- -c frontend // 指定したテストを実行する $ vendor/bin/codecept run unit models/ContactFormTest -- -c frontendテストに使うメソッド
ガイドのdocsのModulesなどを参照。
単体テスト
概ねPHPUnitの様式でテストコードを書いて、上記の要領で実行する。
PHPUnitでいうところのsetUp()が_before()だったり、Codeception独自の様式もある。Codeceptionの便利機能を利用することもできる。詳細はこちら。テストコードの雛形を作成する
// generate:test <suite> <class> $ vendor/bin/codecept generate:test unit ExampleTest -- -c frontendアサーション
verify_that()はempty valueかどうかを検査する。verify_not()はその逆。
verify_that($user->isActivated()); verify_not($user->isBanned());expectはverifyのエイリアス。verifyよりもTDD/BDDライク。
expect_that($user->isActive()); expect_not($user->isBanned()); expect($test_output)->isInstanceOf('yii\mail\MessageInterface'); expect($test_output)->hasKey('xxxx'); expect($test_output)->equals('xxxx'); expect($test_output)->contains('xxxx'); expect($test_output)->true(); expect($test_output)->false();expect()は
yii-application/vendor/codeception/verify/src/Codeception/function.php
に実装されている。検証に使うメソッドはこちら。例外のテスト
callback中で例外が発生することを検証する方法は下記の通り。
$this->tester->expectException('\yii\base\InvalidArgumentException', function() { new ResetPasswordForm(''); });フィクスチャを使用したテスト
ガイド参照。
yii-application/frontend/tests/unit/models/PasswordResetRequestFormTest.phpuse common\fixtures\UserFixture; public function _before() { $this->tester->haveFixtures([ 'user' => [ 'class' => UserFixture::className(), // codecept_data_dir() は yii-application/frontend/tests/_data/ 'dataFile' => codecept_data_dir() . 'user.php' ] ]); } public function testNotSendEmailsToInactiveUser() { // フィクスチャのデータをselectする $user = $this->tester->grabFixture('user', 1); $model = new PasswordResetRequestForm(); $model->email = $user['email']; expect_not($model->sendEmail()); }DBを使用したテスト
yii-application/frontend/tests/unit/models/SignupFormTest.php$user = $this->tester->grabRecord('common\models\User', [ 'username' => 'some_username', 'email' => 'some_email@example.com', 'status' => \common\models\User::STATUS_INACTIVE ]);Codeceptionの本体コード
yii-application/frontend/tests/unit/models/PasswordResetRequestFormTest.phpclass PasswordResetRequestFormTest extends \Codeception\Test\Unit // ① { /** * @var \frontend\tests\UnitTester // ② */ protected $tester; public function _before() { $this->tester->haveFixtures([ // ③ 'user' => [ 'class' => UserFixture::className(), 'dataFile' => codecept_data_dir() . 'user.php' ] ]); }①
yii-application/vendor/codeception/base/src/Codeception/Test/Unit.php
②yii-application/frontend/tests/_support/UnitTester.php
③yii-application/frontend/tests/_support/_generated/UnitTesterActions.php
機能テスト
機能テストはブラウザのエミュレーション(HTTP通信を使わず、アプリケーションのインスタンスをコードから直接に実行する)により、ユーザー視点の検証を可能にする。ブラウザからアクセスした場合と異る挙動を示すことがある。
機能テストは受入テストに似ているけれどもWebサーバを必要としないのが大きな違い。受入テストよりも高速に実行でき、詳細なスタックトレースを出力する一方で制約もある。詳細はこちら。
Webサーバの設定やJavaScriptが絡まないような検証であれば受入テストよりも機能テストの方が適しているとのこと。
注意事項
上記の通りだが再度念押しすると、Webサーバの制御やJavaScriptにより実現されている処理の検証には適さない。
また、Yiiについて言うと、ブラウザからアクセスした場合にはエントリスクリプトで各種設定をロードする。
yii-application/frontend/web/index.phpdefined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); 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' ); (new yii\web\Application($config))->run();一方、codeceptコマンドで実行される機能テストでは、それと同様の設定をロードしないので、異る挙動を示す。(ちなみに受入テストではエントリスクリプト
index-test.php
で処理を行い設定をロードさせる。)vendor/bin/codecept#!/usr/bin/env php <?php /** * Codeception CLI */ require_once __DIR__.'/autoload.php'; use Codeception\Application; $app = new Application('Codeception', Codeception\Codecept::VERSION); $app->add(new Codeception\Command\Build('build')); $app->add(new Codeception\Command\Run('run')); $app->add(new Codeception\Command\Init('init')); $app->add(new Codeception\Command\Console('console')); $app->add(new Codeception\Command\Bootstrap('bootstrap')); $app->add(new Codeception\Command\GenerateCept('generate:cept')); $app->add(new Codeception\Command\GenerateCest('generate:cest')); $app->add(new Codeception\Command\GenerateTest('generate:test')); $app->add(new Codeception\Command\GenerateSuite('generate:suite')); $app->add(new Codeception\Command\GenerateHelper('generate:helper')); $app->add(new Codeception\Command\GenerateScenarios('generate:scenarios')); $app->add(new Codeception\Command\Clean('clean')); $app->add(new Codeception\Command\GenerateGroup('generate:groupobject')); $app->add(new Codeception\Command\GeneratePageObject('generate:pageobject')); $app->add(new Codeception\Command\GenerateSnapshot('generate:snapshot')); $app->add(new Codeception\Command\GenerateStepObject('generate:stepobject')); $app->add(new Codeception\Command\GenerateEnvironment('generate:environment')); $app->add(new Codeception\Command\GenerateFeature('generate:feature')); $app->add(new Codeception\Command\GherkinSnippets('gherkin:snippets')); $app->add(new Codeception\Command\GherkinSteps('gherkin:steps')); $app->add(new Codeception\Command\DryRun('dry-run')); $app->add(new Codeception\Command\ConfigValidate('config:validate')); // Suggests package if (class_exists('Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand')) { $app->add(new Codeception\Command\Completion()); } else { $app->add(new Codeception\Command\CompletionFallback()); } $app->registerCustomCommands(); $app->run();例えば、PHPフレームワーク Yii の使い方で紹介したLanguageDropdownを以下のように実装している場合、上記の様に設定をロードしないと$codeにen-USが設定された結果nullを返す。
components/LanguageDropdown.phppublic static function label($code) { if (self::$_labels === null) { self::$_labels = [ 'ja' => '日本語', 'en' => 'English', ]; } return isset(self::$_labels[$code]) ? self::$_labels[$code] : null; }すると、以下のコードで
[yii\base\InvalidConfigException] The 'label' option is required.
が発せられることになる。views/layouts/main.php$menuItems[] = ['label' => LanguageDropdown::label(Yii::$app->language), 'items' => LanguageDropdown::widget()];対策としてはテストコード中で
\Yii::$app->language = 'en';
のように指定するなどが考えられる。機能テストの大まかな流れ
amOnRoute()、click()やsubmitForm()などで画面上の操作を実行し、seeなんちゃらやassertなんちゃらで操作の結果を検証する。
必要に応じて、テストのスコープ等をwantTo()などで記載したり、getなんちゃらやgrabなんちゃらでテストに使用する情報を取得することもできる。
ログインのテスト
テストに利用するメソッドはdocs -> Modules -> Yii2やdocs -> Modules -> Assertsなどを参照。メソッド名を見ればだいたい何をやっているか想像がつく。詳細はガイドやソースコードで確認する。
tests/functional/LoginCest.php<?php namespace frontend\tests\functional; // FunctionalTesterをuseしている use frontend\tests\FunctionalTester; use common\fixtures\UserFixture; class LoginCest { /** * Load fixtures before db transaction begin * Called in _before() * @see \Codeception\Module\Yii2::_before() * @see \Codeception\Module\Yii2::loadFixtures() * @return array */ public function _fixtures() { return [ 'user' => [ 'class' => UserFixture::className(), // 予めUserモデルに合わせてテスト用ユーザーを定義しておく 'dataFile' => codecept_data_dir() . 'login_data.php', ], ]; } public function _before(FunctionalTester $I) { $I->amOnRoute('site/login'); } protected function formParams($login, $password) { return [ 'LoginForm[username]' => $login, 'LoginForm[password]' => $password, ]; } public function checkEmpty(FunctionalTester $I) { $I->submitForm('#login-form', $this->formParams('', '')); $I->seeValidationError('Username cannot be blank.'); $I->seeValidationError('Password cannot be blank.'); } public function checkWrongPassword(FunctionalTester $I) { $I->submitForm('#login-form', $this->formParams('admin', 'wrong')); $I->seeValidationError('Incorrect username or password.'); } public function checkValidLogin(FunctionalTester $I) { $I->submitForm('#login-form', $this->formParams('erau', 'password_0')); $I->see('Logout (erau)', 'form button[type=submit]'); $I->dontSeeLink('Login'); $I->dontSeeLink('Signup'); } }tests/_support/FunctionalTester.php<?php namespace frontend\tests; // Actorを継承している class FunctionalTester extends \Codeception\Actor { use _generated\FunctionalTesterActions; public function seeValidationError($message) { $this->see($message, '.help-block'); } public function dontSeeValidationError($message) { $this->dontSee($message, '.help-block'); } }vendor/codeception/base/src/Codeception/Actor.php<?php namespace Codeception; use Codeception\Lib\Actor\Shared\Comment; use Codeception\Lib\Actor\Shared\Friend; use Codeception\Step\Executor; abstract class Actor { // Comment Traitをuseしている use Comment; use Friend; /** * @var \Codeception\Scenario */ protected $scenario; public function __construct(Scenario $scenario) { $this->scenario = $scenario; } /** * @return \Codeception\Scenario */ protected function getScenario() { return $this->scenario; } public function wantToTest($text) { $this->wantTo('test ' . $text); } public function wantTo($text) { $this->scenario->setFeature($text); } public function __call($method, $arguments) { $class = get_class($this); throw new \RuntimeException("Call to undefined method $class::$method"); } /** * Lazy-execution given anonymous function * @param $callable \Closure * @return $this */ public function execute($callable) { $this->scenario->addStep(new Executor($callable, [])); $callable(); return $this; } }expectTo()などはComment Traitに実装されている。
vendor/codeception/base/src/Codeception/Lib/Actor/Shared/Comment.php<?php namespace Codeception\Lib\Actor\Shared; trait Comment { /** * @return \Codeception\Scenario */ abstract protected function getScenario(); public function expectTo($prediction) { return $this->comment('I expect to ' . $prediction); } public function expect($prediction) { return $this->comment('I expect ' . $prediction); } public function amGoingTo($argumentation) { return $this->comment('I am going to ' . $argumentation); } public function am($role) { $role = trim($role); if (stripos('aeiou', $role[0]) !== false) { return $this->comment('As an ' . $role); } return $this->comment('As a ' . $role); } public function lookForwardTo($achieveValue) { return $this->comment('So that I ' . $achieveValue); } public function comment($description) { $this->getScenario()->comment($description); return $this; } }機能テストと受入テストで利用できるテストコードの再利用の方法はこちら。ログインのテストの再利用について紹介されている。
入力フォームのテスト
tests/functional/SignupCest.php<?php namespace frontend\tests\functional; use frontend\tests\FunctionalTester; class SignupCest { // クリックするsubmitボタンのidを指定する protected $formId = '#form-signup'; public function _before(FunctionalTester $I) { // テスト対象のページに遷移する $I->amOnRoute('site/signup'); } public function signupWithEmptyFields(FunctionalTester $I) { $I->see('Signup', 'h1'); $I->see('Please fill out the following fields to signup:'); // フォームに何も入力せずに送信する $I->submitForm($this->formId, []); // エラーになることを確認する $I->seeValidationError('Username cannot be blank.'); $I->seeValidationError('Email cannot be blank.'); $I->seeValidationError('Password cannot be blank.'); } public function signupWithWrongEmail(FunctionalTester $I) { $I->submitForm( $this->formId, [ // フォームを入力する(不適切な入力) 'SignupForm[username]' => 'tester', 'SignupForm[email]' => 'ttttt', 'SignupForm[password]' => 'tester_password', ] ); // 未入力エラーではないことを確認する $I->dontSee('Username cannot be blank.', '.help-block'); $I->dontSee('Password cannot be blank.', '.help-block'); // 入力内容が不適切であることを確認する $I->see('Email is not a valid email address.', '.help-block'); } public function signupSuccessfully(FunctionalTester $I) { // SignupFormモデルとUserモデルも確認すると理解が深まる $I->submitForm($this->formId, [ // フォームを入力する(適切な入力) 'SignupForm[username]' => 'tester', 'SignupForm[email]' => 'tester.email@example.com', 'SignupForm[password]' => 'tester_password', ]); // ユーザが存在することを確認する $I->seeRecord('common\models\User', [ 'username' => 'tester', 'email' => 'tester.email@example.com', 'status' => \common\models\User::STATUS_INACTIVE ]); // メールが送信されたことを確認する $I->seeEmailIsSent(); // フォーム送信が正しく行わた時の画面であることを確認する $I->see('Thank you for registration. Please check your inbox for verification email.'); } }メールのテスト
// メールが送信できたかどうかを検証する。 $I->seeEmailIsSent(); // メールの内容を取得する。 $message = $I->grabLastSentEmail(); // メールの内容を検証する。 $I->assertEquals('admin@site,com', $message->getTo());受入テスト
受入テストを実行する前に環境構築しておかないとエラーが発生する。
環境構築
YiiのテストのクイックスタートガイドやSelenium 環境構築を参考に環境をつくる。
概ね以下のような感じ。
$ cd /somewhere/yii-application $ mv frontend/tests/acceptance.suite.yml.example frontend/tests/acceptance.suite.yml $ vendor/bin/codecept build -- -c frontendSeleniumの環境構築に関して、Webドライバのインストールは以下のような感じ。
$ cd /somewhere/yii-application $ vi composer.json $ php composer.phar require --dev facebook/webdriveryii-application/composer.json"require-dev": { "facebook/webdriver": "^1.0.1",環境構築が済んだらSeleniumサーバを起動する。
$ ls /somewhere/yii-application/vendor/facebook/ webdriver $ ls /opt/selenium/ selenium-server-standalone-3.141.59.jar $ ls /usr/local/bin chromedriver geckodriver $ java -jar /opt/selenium/selenium-server-standalone-3.141.59.jaryii-application/frontend/tests/acceptance.suite.ymlsuite_namespace: frontend\tests\acceptance actor: AcceptanceTester modules: enabled: - WebDriver: # URL、ポート番号は環境に応じて書き換える url: http://localhost:8080 # ブラウザドライバを指定する browser: chrome - Yii2: part: - init - fixturesエラー事例
エラーメッセージ 原因 Suite 'acceptance' could not be found acceptance.suite.yml.exampleをリネームしていないなど環境構築が未済 WebDriver: Class Facebook\WebDriver\Remote\RemoteWebDriver can't be loaded, please add "facebook/webdriver": "^1.0.1" to composer.json WebDriverがインストールされていない [ConnectionException] Can't connect to Webdriver at http://127.0.0.1:4444/wd/hub. Please make sure that Selenium Server or PhantomJS is running. Seleniumサーバが起動していない [yii\base\ErrorException] Undefined index: ELEMENT ブラウザドライバ(geckodriver)のバグ? ドライバ周辺の不具合についてはバージョンを変えるなり、chrome driverなど別のドライバに変えると解消することがある。
テストの書き方
機能テスト同様CESTの様式で書くが、利用できるメソッドに違いがある。ブラウザには、PhpBrowserとWebDriver(+ブラウザ)があり、用途に応じて使い分ける。wait系のメソッドはWebDriverに特有の機能。詳細はこちら。
$ vendor/bin/codecept run acceptance HomeCest -- -c frontendDBを使用したテスト
DB Moduleを利用できるように設定する。
yii-application/frontend/tests/acceptance.suite.ymlmodules: enabled: - Db: dsn: "mysql:host=localhost;dbname=yii2advanced_test" user: "yii2advanced_test" password: "xxxx"テストコードを書き、実行する。
tests/acceptance/LoginCest.php<?php namespace frontend\tests\acceptance; use frontend\tests\AcceptanceTester; use yii\helpers\Url; use common\fixtures\UserFixture; class LoginCest { public function _fixtures() { return [ 'user' => [ 'class' => UserFixture::className(), 'dataFile' => codecept_data_dir() . 'user.php', ], ]; } public function _before(AcceptanceTester $I) { $I->amOnPage(Url::toRoute('/site/login')); $I->wait(1); } protected function formParams($login, $password) { return [ 'LoginForm[username]' => $login, 'LoginForm[password]' => $password, ]; } public function checkValidLogin(AcceptanceTester $I) { $I->see('Login', 'h1'); $I->seeInDatabase('user', ['erau' => 'sfriesen@jenkins.info']); $I->submitForm('#login-form', $this->formParams('erau', 'password_0')); $I->wait(1); $I->see('Logout (erau)', 'form button[type=submit]'); $I->dontSeeLink('Login'); $I->dontSeeLink('Signup'); } }注意事項
Pretty URLを適用している場合、
.htaccess
でindex.php
で処理させるように書いてあるためindex-test.php
で処理されない。よってテスト用の設定(config/test.php
など)が読み込まれない。結果、テスト用に作成したデータベースに接続されない。対策は少なくとも3通りある。
index.php
で読み込む設定ファイル中でテスト用のDBに接続する。.htaccess
を書き換えてindex-test.php
で処理させる。index.php
にindex-test.php
を統合し、環境に応じてYII_ENVを書き換える。(下記)yii-application/frontend/web/index.php<?php // NOTE: Make sure this file is not accessible when deployed to production // 適宜IPアドレスを追加する if (!in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) { die('You are not allowed to access this file.'); } defined('YII_DEBUG') or define('YII_DEBUG', true); // 受入テストを行う環境ではYII_ENVにtestを設定する defined('YII_ENV') or define('YII_ENV', 'test'); require __DIR__ . '/../../vendor/autoload.php'; require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php'; require __DIR__ . '/../../common/config/bootstrap.php'; require __DIR__ . '/../config/bootstrap.php'; if (YII_ENV === 'test') { $config = yii\helpers\ArrayHelper::merge( require __DIR__ . '/../../common/config/main.php', require __DIR__ . '/../../common/config/main-local.php', require __DIR__ . '/../../common/config/test.php', require __DIR__ . '/../../common/config/test-local.php', require __DIR__ . '/../config/main.php', require __DIR__ . '/../config/main-local.php', require __DIR__ . '/../config/test.php', require __DIR__ . '/../config/test-local.php' ); } else { $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' ); } (new yii\web\Application($config))->run();Tips
cliでステップ実行する
vendor/bin/codecept
を真似たcli_debug.php
を作成し、IDEのデバッグ機能でcli_debug.php run unit -- -c frontend
のようにして実行する。yii-application/cli_debug.php<?php /** * Codeception PHP script runner */ require_once dirname(__FILE__).'/vendor/codeception/base/autoload.php'; use Codeception\Application; $app = new Application('Codeception', Codeception\Codecept::VERSION); $app->add(new Codeception\Command\Build('build')); $app->add(new Codeception\Command\Run('run')); $app->add(new Codeception\Command\Init('init')); $app->add(new Codeception\Command\Console('console')); $app->add(new Codeception\Command\Bootstrap('bootstrap')); $app->add(new Codeception\Command\GenerateCept('generate:cept')); $app->add(new Codeception\Command\GenerateCest('generate:cest')); $app->add(new Codeception\Command\GenerateTest('generate:test')); $app->add(new Codeception\Command\GenerateSuite('generate:suite')); $app->add(new Codeception\Command\GenerateHelper('generate:helper')); $app->add(new Codeception\Command\GenerateScenarios('generate:scenarios')); $app->add(new Codeception\Command\Clean('clean')); $app->add(new Codeception\Command\GenerateGroup('generate:groupobject')); $app->add(new Codeception\Command\GeneratePageObject('generate:pageobject')); $app->add(new Codeception\Command\GenerateSnapshot('generate:snapshot')); $app->add(new Codeception\Command\GenerateStepObject('generate:stepobject')); $app->add(new Codeception\Command\GenerateEnvironment('generate:environment')); $app->add(new Codeception\Command\GenerateFeature('generate:feature')); $app->add(new Codeception\Command\GherkinSnippets('gherkin:snippets')); $app->add(new Codeception\Command\GherkinSteps('gherkin:steps')); $app->add(new Codeception\Command\DryRun('dry-run')); $app->add(new Codeception\Command\ConfigValidate('config:validate')); // Suggests package if (class_exists('Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand')) { $app->add(new Codeception\Command\Completion()); } else { $app->add(new Codeception\Command\CompletionFallback()); } $app->registerCustomCommands(); $app->run();
- 投稿日:2019-09-27T20:20:35+09:00
CentOS 7にPHP 7.2をインストール(Remi's RPM repository)
はじめに
Remi's RPM repositoryを利用してCentOS7にPHP7.2をインストール
親記事:PHP EOLと各種インストール方法
参考:Remi's RPM repositoryサポート
本手法で導入した場合、PHP: Supported Versions/PHP: Unsupported Branchesより、2020-11-30がEOLになると思われる。
それ以降に報告された脆弱性への対応は実施されない可能性がある。note
- インストール後の更新は
yum --enablerepo=remi-php72 update
LOG
インストール
# cat /etc/redhat-release CentOS Linux release 7.7.1908 (Core) # yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm ... 略 # yum install -y --enablerepo=remi-php72 php which ... 略各種確認
# which php /usr/bin/php # php -v PHP 7.2.23 (cli) (built: Sep 25 2019 07:38:48) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies # php -i | grep php.ini Configuration File (php.ini) Path => /etc Loaded Configuration File => /etc/php.ini # yum info php Loaded plugins: fastestmirror, ovl Loading mirror speeds from cached hostfile * base: ftp.yz.yamagata-u.ac.jp * epel: nrt.edge.kernel.org * extras: ftp.yz.yamagata-u.ac.jp * remi-safe: ftp.riken.jp * updates: ftp.yz.yamagata-u.ac.jp Installed Packages Name : php Arch : x86_64 Version : 7.2.23 Release : 1.el7.remi Size : 10 M Repo : installed From repo : remi-php72 Summary : PHP scripting language for creating dynamic web sites URL : http://www.php.net/ License : PHP and Zend and BSD and MIT and ASL 1.0 and NCSA Description : PHP is an HTML-embedded scripting language. PHP attempts to make it : easy for developers to write dynamically generated web pages. PHP also : offers built-in database integration for several commercial and : non-commercial database management systems, so writing a : database-enabled webpage with PHP is fairly simple. The most common : use of PHP coding is probably as a replacement for CGI scripts. : : The php package contains the module (often referred to as mod_php) : which adds support for the PHP language to Apache HTTP Server.
- 投稿日:2019-09-27T18:38:24+09:00
【Laravel】LengthAwarePaginatorで1つのページに複数ページネーションを実装 -Laravel
LengthAwarePaginatorを使って1つのページに複数ページネーションを実装。
参考
Laravel5で1ページに複数のPaginationを実装する
https://qiita.com/sano1202/items/371a59ea46cb66993b8bこちらを参考にさせていただきました!
が、LengthAwarePaginatorを使用して複数ページネーションを実装しようとした際に詰まったので備忘録。
Paginationのパラメータ名を変更する
パラメータ名が変更できずに随分困りましたが第5引数に
['pageName' => 'hogehoge']
を入れるだけでした。。
$players = new LengthAwarePaginator($list , $all_num, $disp_limit, $page, ['pageName' => 'hogehoge']);これで、
user/index?hogehoge=2
のようになります。初心者なので、、こんなのもわからないの?!と言わずお手柔らかにmm
備忘です。
- 投稿日:2019-09-27T18:38:24+09:00
【Laravel】LengthAwarePaginatorで1つのページに複数ページネーションを実装 -Laravel5
LengthAwarePaginatorを使って1つのページに複数ページネーションを実装。
参考
Laravel5で1ページに複数のPaginationを実装する
https://qiita.com/sano1202/items/371a59ea46cb66993b8b基本的にはこちらを参考にさせていただきました!
が、LengthAwarePaginatorを使用して複数ページネーションを実装しようとした際に詰まったので備忘録。
Paginationのパラメータ名を変更する
パラメータ名が変更できずに随分困りましたが第5引数に
['pageName' => 'hogehoge']
を入れるだけでした。。
$users = new LengthAwarePaginator($users , $count, $per_page, $page, ['pageName' => 'hogehoge']); $users->setPath(url('/users'));これで、
/users?hogehoge=2
のようになります。初心者なので、、こんなのもわからないの?!と言わずお手柔らかにmm
備忘です。
- 投稿日:2019-09-27T16:58:35+09:00
Laravelでフォーム保存後のフィードバックにSessionのフラッシュデータを使うのと、そのテスト方法
Webページでは何らかのフォームで保存ボタンを押した後に次のページにリダイレクトすることがあり、
リダイレクトされた次のページでフィードバックしたいということがよくあります。Laravelにおいてこのフィードバックを出す方法と、そのテスト方法について説明します。
バックエンドでセッションのフラッシュデータに値を入れる
Controller で以下のようにwithでフラッシュデータに値を入れておきます。
public function add() { // 保存とか色々する // 最後に次のページへリダイレクトさせる return redirect()->route('/')->with('success', '保存が成功しました'); }公式ドキュメントでいうと https://readouble.com/laravel/5.8/ja/responses.html
フラッシュデータを保存するリダイレクト
あたりの章です。
フィードバックの HTML と SCSS
Laravelのヘルパ関数でsession()というのがあるので、それで値をとります。
LaravelのBladeファイルで書くとHTMLは以下のようになります。
@if(session('success')) <div class="feedback-wrapper"> <div class="feedback success"> {{ session('success') }} </div> </div> @endif対応するSCSS
.feedback-wrapper { .feedback { margin: auto; padding: 12px; border-radius: 4px; text-align: center; &.success { border: 1px solid green; color: green; font-weight: bold } } }テスト方法
Featureテストを書く時に、フラッシュデータが何が入っているかというのをテストに書きます。
public function testAdd() { $this->post('/test/add', [ 'text' => '入力したテキストです', ])->assertRedirect('/text')->assertSessionHas('success'); }assertSessionHas() を使えば、仮にリダイレクト先が同じでも、入っているフラッシュデータが違う 'success' と 'warning' であっても区別することができます。
- 投稿日:2019-09-27T16:26:04+09:00
PHP Slim 環境構築(8) S3
PHP Slim 環境構築(8) S3
Introduction
前回は、ローカルDynamoDBにアクセスしてみました。
今回は、ローカル用のS3互換サービスMinIOを使って、S3へのアクセスを行います。MinIO
MinIOは、エンタープライズレベルのオブジェクトストレージサービスです。
・・・なんですが、手軽に使えるS3互換ストレージとして、ローカル開発環境によく使用されているようです。変更点
ソースツリー
前回からの変更・追加ソースは以下の通りです。
$(PROJECTROOT) /compose docker-compose.yml /src /hoge /lib /Controller StorageController.php (NEW!) /Model /public index.phpdocker-compose.yml
新たにS3互換のminIO用コンテナを追加します。
なお、この記事を書いている途中でdocker-composeのlinksオプションはすでに時代遅れなのを知ってしまったので、今回からlinksは全て削除しています。すでにnetworksを使っているので、linksは冗長だったんですね。
..(略).. storage: image: minio/minio volumes: - storage-data:/data container_name: storage ports: - "19000:9000" environment: MINIO_ACCESS_KEY: minio MINIO_SECRET_KEY: miniminio command: server /data networks: - local_netindex.php
DIにstorage用の設定とstorageオブジェクトの定義を行い、storage用のrouteを追加します。
/src/hoge/public/index.php..(略).. use Hoge\Controller\RedisController; use Hoge\Controller\DynamodbController; use Hoge\Controller\StorageController; (追加) ..(略).. $containerBuilder = new ContainerBuilder(); $containerBuilder->addDefinitions([ 'settings' => [ ..(略).. 'storage' => [ 'endpoint' => 'http://storage:9000', 'region' => 'ap-northeast-1' ] ], ..(略).. 'storage' => function (ContainerInterface $container) { $settings = $container->get('settings')['storage']; $sdk = new Aws\Sdk([ 'endpoint' => $settings['endpoint'], 'region' => $settings['region'], 'version' => '2006-03-01', 'credentials' => [ 'key' => 'minio', 'secret' => 'miniminio' ], //'bucket_endpoint' => true, 'use_path_style_endpoint' => true ]); $s3 = $sdk->createS3(); return $s3; } ..(略).. $app->group('/storage', function (RouteCollectorProxy $group) { $group->get('/{filename}', StorageController::class . ':get'); $group->post('', StorageController::class . ':post'); }); $app->run(); ### StorageController ファイルのアップロードと、ファイルの表示(ダウンロードにするためにはcontent-typeとかいじればok)に対応しています。 ```php:/src/hoge/lib/Controller/StorageController.php <?php namespace Hoge\Controller; use Aws\S3\Exception\S3Exception; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Container\ContainerInterface; class StorageController { /** * @var ContainerInterface */ protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; /** @var \Aws\S3\S3Client $s3 */ $s3 = $this->container->get('storage'); try { // バケット確認 $s3->headBucket(['Bucket' => 'dummy']); } catch (S3Exception $ex) { $errorCode = $ex->getAwsErrorCode(); if ($errorCode === 'NotFound') { // バケットが無ければ作る $s3->createBucket([ 'Bucket' => 'dummy', 'CreateBucketConfiguration' => [ 'LocationConstraint' => 'ap-northeast-1' ] ]); } } } public function get(Request $request, Response $response, array $args) : Response { // parameters (id) $key = $args['filename']; /** @var \Aws\S3\S3Client $s3 */ $s3 = $this->container->get('storage'); try { $result = $s3->getObject([ 'Bucket' => 'dummy', 'Key' => $key ]); } catch (S3Exception $ex) { return $response->withStatus(404); } $body = $result->get('Body'); $newResponse = $response->withBody($body); return $newResponse; } public function post(Request $request, Response $response, array $args) : Response { $files = $request->getUploadedFiles(); /** @var \Psr\Http\Message\UploadedFileInterface $uploadedFile */ $uploadedFile = $files['upload']; $err = $uploadedFile->getError(); if ($err !== 0) { return $response->withStatus(500); } $clientFileName = $uploadedFile->getClientFilename(); /** @var \Aws\S3\S3Client $s3 */ $s3 = $this->container->get('storage'); $result = $s3->putObject([ 'Bucket' => 'dummy', 'Key' => $clientFileName, 'Body' => $uploadedFile->getStream() ]); $url = $result->get('ObjectURL'); $response->getBody()->write($url); return $response; } }テスト
wgetでpost fileuploadをテストするために、以下のようなコマンドを実行しました。
postdata.txt--cuthere Content-Disposition: form-data; name="upload"; filename="videodrome.txt" Content-Type: text/plain THIS IS YOUR TAPE. --cuthere--$ wget http://hoge.localhost/.storage --header='Content-Type: multipart/form-data; boundary=cuthere' --post-file=postdata.txtここまでのソース
こちらでどうぞ。
- 投稿日:2019-09-27T15:58:45+09:00
Laravelでレコード生成、更新時に自動更新されるタイムスタンプをミリ秒にする
概要
Laravelでレコード生成、更新時に自動更新されるタイムスタンプをミリ秒にする。
実装
モデルクラスclass モデル名 extends Model { // このメソッドをオーバーライドする public function getDateFormat() { return 'Y-m-d H:i:s.u'; } }マイグレートクラスclass マイグレート名 extends Migration { public function up() { Schema::create('table_name', function (Blueprint $table) { $table->bigIncrements('id'); $table->dateTime('created_at', 6); // 精度を6にする。 $table->dateTime('updated_at', 6); // 精度を6にする。 }); } }
- 投稿日:2019-09-27T15:48:33+09:00
【FuelPHP】クエリビルダでSQLを出力する
- 投稿日:2019-09-27T12:54:08+09:00
サーバアプリケーションのログを取る方針、脳死でも出来る事
ログを取る時に考える事
PHP, Laravel を今扱ってるからとりあえず前提は PHP, Laravel で。
他の環境の時も別にやる事は変わらないと思う。サーバ業務をやってると幸運な事に色々な設計を任せて貰えるんだけど、自分が直接担当しない時にログどうしましょうかと相談されたので、そういう時に伝える基本方針を書いておく。
というか、何も知らない人は頼むからこれだけは最低限やっておいてくれって感じの内容 (そこも人によって違うからあれだけど参考にして損はないはず)。
- Laravel5.6 のログについて: https://readouble.com/laravel/5.6/ja/logging.html
ログとは
ログと一括りにすると色々あるだろと突っ込みを貰う事になるので、ここではアプリケーション保守を念頭に置いたログの事を指す。
それも (ry みたいな話はあれど、OS に踏み込んだりはせず、サーバサイドアプリケーションエンジニア (長い...) が気にするポイントを書きだしておけば迷いは消える筈。アクセスログを取る
別に PHP に限らない話になってきそうな気がすでにしてるけど、一旦無視して、前提として HTTPD (Apache や Nginx など) のアクセスログは取る。
アプリケーションログを取る
API のリクエスト時に送信されてくる POST 等のデータ変更系のメソッドは、リクエストデータ加工前にログを吐いておく。
また、レスポンスは有無を言わさず全て取る。GET, POST, PUT, DELETE はそれぞれ取るようにしておく (POST で投げて _method: 'delete' を DELETE として扱うようなフレームワークの場合、そこの考慮も忘れずやる)。
パラメータは全て保存と言いたい所だけど、クレジットカードなどの平文保存がまずいものに関しては任意のフィールドをマスク (番号を xxxxx... と他の文字列で置き換えて戻せなくする) する事ができる仕組みを入れておく。いつ誰がサーバに訪問したか、どういうリクエストを投げたのかという情報はサービス全ての起点になるため、これを取らなかったりすると調査が不可能になったりする。
ログレベルは INFO で脳死 (プロジェクト的に理由があって変える場合は違うログレベルで吐く)。
DB 接続とかその多詳細なプログラムレベルのログについて
DB 接続エラーとか、個々の要件毎のエラーケースみたいなものはサービスレベルでどういうログが欲しいとかがあるし、この変は脳死では対応できないので、深刻度に合わせて話して決めておく。
この辺りは INFO というよりはもっと上の深刻度の高いログの為、適切な対応が必要 (本当に脳死は良くない)。
ただ、そういう書き方をすると何も設定しない人がでてきたりするので、\Exception で catch して error 詳細と使ったパラメータを吐くくらいは入れとくと良い (そのままエラーを握り潰さないようにとりあえず同じ例外を再度 throw しておく)。DEBUG を上手く使う
少し余談。
開発していると皆 dd 挟んだりブレークポイント入れたり工夫してデバッグするけど、止めて眺める系は作業効率は良くなかったりするし、一連のデータを俯瞰して見る事に適さないので、log で吐く事を覚えると幸せになれる。
不要なコードやログを入れたくないという意見がたまにあるけど、そもそもそれは DEBUG が担う部分なので、気兼ねなく Log::DEBUG を仕込むといい。
特に共通化しているような処理 (Model レベルでのデータ整形や Service 層、Job とか視認しづらい部分は特に) に前提にしているデータと加工後のデータや返却したデータなどを吐いておくとどこでおかしくなってるかが分かるようになるのでバンバン差し込んでおくこと。ログローテーションは必ず動作確認する
未設定の場合は必ず設定する事。
吐く場所がどこかによって変わるけど、ボリュームの空きを使いきったら (usage 100% とか) サーバが動かなくなったり心あたりの無い挙動をする事になるので。
多分監視とか入れるだろうから基本的には大丈夫だと思うけど、たまにログで使いきって死ぬサーバがいる (そもそも log のボリューム分けてないのかという話はあるが、あくまでもリスクコントロールの話)。またローテーション設計はログの回収、バックアップ頻度、ボリュームサイズなどインフラ依存のため、担当と相談して決める。
この辺りは使ってるフレームワークでローテーションが動いてる事を確認するのと、設定、変更方法を確認しておく (後から変更が難しい場合や、変更箇所が多い場合は先に決めてしまう)。
最後に
書いてると全部脳死って訳にはいかないなーという感想になったけど、とりあえず細かい所は置いておいて、最低ないとやばいのは書けたと思う。
- 投稿日:2019-09-27T01:05:46+09:00
名前空間の意義
名前空間とは
別ファイルの関数やviewファイルを読み込みたいときはrequire()やinclude()を使用することで、そのファイルの中身を利用することができる。
上記方法では全部の内容を読み込むが、名前空間を利用することでクラス名のみを同じように使うことができる。
名前空間については以前に投稿したが、改めて復習すると、重複するクラス名やメソッド名を同じ名前であったとしても、使うことができる概念である、と説明した。
というのも、ファイルが多くなってくるとどの関数名やクラス名を使用したか分からなくなってしまうので、それを気にせずに利用したいときに非常に便利である。
例えば以下のような書き方ができる。
namespace dirA\dirB\dirC(現在ディレクトリ) use dirD\...\クラス名X use dirE\...\クラス名Y上は何を示しているかというと、
まず、利用したいクラスが書かれているファイルがdirA/dirB/dirCの下のDとEのさらに下にあるとした場合、
2つのクラスのファイルの上流のディリレクトリをnamespaceで宣言する。
こうすることで、共通のディレクトリはuse以降に書かなくて済む。そして、useにはそれぞれ利用したいクラスがあるファイルまでのディレクトリと最後にクラス名を書くことでそのクラス名をこのファイルの中で使うことができる。
補足だが、オートローダーを利用することで、クラスをuseしなくても利用する方法がある。これについては、別の機会に解説する。
- 投稿日:2019-09-27T00:34:09+09:00
Null 合体演算子(??)と三項演算子(?:)の空白('')の結果の違いについて
以下のような違いがあります。
- Null 合体演算子(??)では、空白('')はtrue
扱いになる
- 三項演算子(?:)では、空白('')はfalse
扱いになる>>> '' ?? false => "" >>> '' ?: false => false >>> '' ? true : false => false
- 投稿日:2019-09-27T00:34:09+09:00
Null 合体演算子(??)とエルビス演算子(?:)と三項演算子(A?B:C)の空白('')の結果の違いについて
以下のような違いがあります。
- Null 合体演算子(??
)では、空白('')はtrue
扱いになる
- エルビス演算子(?:
)と三項演算子(A?B:C
)では、空白('')はfalse
扱いになる>>> '' ?? false => "" >>> '' ?: false => false >>> '' ? true : false => false