20201204のPHPに関する記事は13件です。

Laravel5.5 Eloquentモデルでupdated_atカラムがないテーブルを使うとき

Laravelの便利な機能Eloquentを使う際に、使い勝手が言い分困った!っと感じたこととその対応。

テーブルにupdated_atカラムが必要ない時ってありますよね。
updated_atがないテーブルにEloquentを使って、DBに新規登録するとエラーが出ます!

カラムがないよ!ってアラートです。
しかし!!出てきても大丈夫!

 const UPDATED_AT = null;

これを、新規登録するテーブルのモデルにかけばOKです〜!

以上になります!
今回、updated_atに少し翻弄されてしまったので備忘録として書きました。
LGTM大好物なんで、ポチッと投げ銭感覚でおねがいします〜! :sunglasses:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Hackのルーターを触ってみよう

Hack-Router

今回は、Hack-Routerを使って、
簡単にHTTPに関する処理を実装してみましょう。

Hackはライブラリが多くなく(むしろ少ない)、
ルーターはこのライブラリくらいしかありません。
ライブラリを作りたい方はすでにあるHackのライブラリを触って、
コードを読んでから作ってみると良いでしょう。
今回はそんな定番とも言えるルーターライブラリを触ります。

PHPと異なり、Generics、Enumsを利用していますので、
PHPしか利用していない方は少しばかり難しいですが、
大したことはありません。

開発環境の用意

手元にHHVMなんてないよ!という方は、
HHVM(proxygen) 環境を構築してみようにありますので、
事前に準備しておきましょう。

インストール

まずは前日の[2020] HHVM/Hackの始め方 導入編にある通り、
hhvm/hhvm-autoloadをインストールします。

$ composer require hhvm/hhvm-autoload

続いてfacebook/hack-routerをインストールします。

$ composer install facebook/hack-router

HTTPリクエストをそのまま使うこともできますが、
初めてHackを触る方で手抜をしたい方はHTTP Message implementationライブラリを使った方が楽でしょう。
下記のコマンドでライブラリをインストールします。

$ composer require ytake/hungrr

続いて src、tests(今回は使いませんが!)ディレクトリを作成し、
composer.jsonと同じルートディレクトリにhh_autoload.jsonを作成して下記の通りに記述します。

hh_autoload.json
{
  "roots": [
    "src/"
  ],
  "devRoots": [
    "tests/"
  ]
}

そして最後に.hhconfigを作成して以下の通りに記述しておきましょう。

.hh_config
assume_php=false
ignored_paths = [ "vendor/.+/tests/.+" ]
disallow_elvis_space=true
disallow_non_arraykey_keys=true
disallow_unsafe_comparisons=true
decl_override_require_hint=true
enable_experimental_tc_features=shape_field_check,sealed_classes
disable_primitive_refinement=true
disable_static_local_variables = true
disallow_array_literal = true
allowed_decl_fixme_codes=1002,2053,4045,4047
allowed_fixme_codes_strict=1002,2011,2049,2050,2053,4007,4027,4045,4047,4053,4104,4106,4107,4108,4110,4128,4135,4188,4240,4323

allowed_fixme_codes_strictはライブラリでエラーになっているものもいくつかありますので、
その中で代表的なエラーは警告しないように指定しています。

composer.jsonで任意のautoloadの設定を記述しておきましょう。

composer.json
{
    "name": "acme/sample",
    "require": {
        "facebook/hack-router": "^0.19.7",
        "hhvm/hhvm-autoload": "^3.1",
        "ytake/hungrr": "^0.13.3"
    },
    "autoload": {
        "psr-4": {
            "Acme\\Sample\\": "src/"
        }
    }
}

これでルーターを使うまでの準備が整いました。
$ composer dump-autoloadなどは実行しておきましょう

Hack Routerを理解する

まずはルーターの定義を記述します。

src/Router/ExampleRouter.hack
namespace Acme\Example\Router;

use type Facebook\HackRouter\{BaseRouter, HttpMethod};

type TResponder = (function(dict<string, string>):string);

final class ExampleRouter extends BaseRouter<TResponder> {

  <<__Override>>
  protected function getRoutes(
  ): ImmMap<HttpMethod, ImmMap<string, TResponder>> {
    return ImmMap {
      HttpMethod::GET => ImmMap {
        '/' => ($_) ==> 'Hello, world',
      },
    };
  }
}

Facebook\HackRouter\Routerは、<+TResponder>を要求する規定クラスになっています。
この<+TResponder>はGenericsになっているので、
利用者が必要とする形を指定することができます。

この例では type TResponder = (function(dict<string, string>):string);としており、
これはルーティングが見つかれば、コールバックで該当する処理を返却し、
引数としてdict<string, string>を渡す、という指定です。

具体的なルーティングは、下記の部分となります。

  <<__Override>>
  protected function getRoutes(
  ): ImmMap<HttpMethod, ImmMap<string, TResponder>> {
    return ImmMap {
      HttpMethod::GET => ImmMap {
        '/' => ($_) ==> 'Hello, world',
      },
    };
  }

HTTPメソッドは、Facebook\HackRouter\HttpMethod enumsを利用する様に
Facebook\HackRouter\Routerの抽象メソッドで指定されているものです。

  abstract protected function getRoutes(
  ): KeyedContainer<HttpMethod, KeyedContainer<string, TResponder>>;

KeyedContainerはHackで用意されている型で、HH\KeyedContainerを指します。
これはディクショナリであれば利用できる様になっていますので、
次の様にdictを利用しても問題ありません。

  <<__Override>>
  protected function getRoutes(
  ): dict<HttpMethod, dict<string, TResponder>> {
    return dict [
      HttpMethod::GET => dict[
        '/' => ($_) ==> 'Hello, world',
      ],
    ];
  }

ですが、通常ルーティングを動的に操作することはまずないと思いますので、
ImmMapを使うのが適切です。
*ImmMapはイミュータブルなMapなのでそれ以上変更はできません。

Facebook\HackRouter\HttpMethod enumsでは下記のものが用意されています。

namespace Facebook\HackRouter;

enum HttpMethod: string {
  HEAD = 'HEAD';
  GET = 'GET';
  POST = 'POST';
  PUT = 'PUT';
  PATCH = 'PATCH';
  DELETE = 'DELETE';
  OPTIONS = 'OPTIONS';
  PURGE = 'PURGE';
  TRACE = 'TRACE';
  CONNECT = 'CONNECT';
  REPORT = 'REPORT';
  LOCK = 'LOCK';
  UNLOCK = 'UNLOCK';
  COPY = 'COPY';
  MOVE = 'MOVE';
  MERGE = 'MERGE';
  NOTIFY = 'NOTIFY';
  SUBSCRIBE = 'SUBSCRIBE';
  UNSUBSCRIBE = 'UNSUBSCRIBE';
}

十分ですね!

Routerを起動する

それでは実際にHTTPリクエストを受けた時にルーティングが起動する様にしてみましょう。

public/index.hack
use type Acme\Example\Router\ExampleRouter;
use type Facebook\HackRouter\{BaseRouter, HttpMethod};
use type Ytake\Hungrr\ServerRequestFactory;

<<__EntryPoint>>
async function mainAsync(): Awaitable<void> {
  require_once __DIR__.'/../vendor/autoload.hack';
  \Facebook\AutoloadMap\initialize();

  $router = new ExampleRouter();
  $request = ServerRequestFactory::fromGlobals();
  $vec = $router->routeRequest($request);
  $c = $vec[0];
  echo $c($request->getQueryParams());
}

先ほど記述したルータークラスのインスタンスを生成し、
ServerRequestFactory::fromGlobals()でHTTPリクエストを取得し、
リクエスト情報をルーターオブジェクトに渡すことで、
マッチするルートがあるかを調べて返却します。

存在しないルートのリクエストであれば
Facebook\HackRouter\NotFoundExceptionがスローされます。

マッチするものがあれば、TResponderで指定した(function(dict<string, string>):string)
が返却されます。
これをコールすることで、
'Hello, world',が返却される、という仕組みです。
callbackされるならPHPでよく使う__invokeがあるのに、と多くの方が思うと思いますが、
Hackでは_invokeはcallableとみなされませんので利用することができません。

実際に利用する場合はこのままだとさすがに少し厳しいので、
以下の様にクラスを用意するといいでしょう。

src/Action/ActionInterface.hack
namespace Acme\Example\Action;

use type Ytake\Extended\HttpMessage\ServerRequestInterface;

<<__ConsistentConstruct>>
interface ActionInterface {

  public function process(
    ServerRequestInterface $request
  ): string;
}

<<__ConsistentConstruct>>Attributeは、コンスタラクタに対するものです。
このAttributeが記述されたクラスは、
継承する場合にかならずコンストラクタの引数などは同一のものとならなければなりません。
記述することでインスタンス生成でコンストラクタに必要な引数が保証されます。
インターフェースの記述すると、コンストラクタで必要な引数はない、ということになります。
*例のため簡単にできる様に記述していますが、実際にはこうしません!注意!

このインターフェースを実装したアクションクラスを用意します。

namespace Acme\Example\Action;

use type Ytake\Extended\HttpMessage\ServerRequestInterface;
use function json_encode;

final class HomeAction implements ActionInterface {

  public function process(
    ServerRequestInterface $request
  ): string {
    return 'Sample ' . json_encode($request->getQueryParams());
  }
}

続いてルータークラスを修正します。

src/Router/ExampleRouter.hack
namespace Acme\Example\Router;

use type Ytake\Extended\HttpMessage\ServerRequestInterface;
use type Facebook\HackRouter\{BaseRouter, HttpMethod};
use type Acme\Example\Action\{HomeAction, ActionInterface};

type TResponder = classname<ActionInterface>;

final class ExampleRouter extends BaseRouter<TResponder> {
  <<__Override>>
  protected function getRoutes(
  ): ImmMap<HttpMethod, ImmMap<string, classname<ActionInterface>>> {
    return ImmMap {
      HttpMethod::GET => ImmMap {
        '/' => HomeAction::class,
      },
    };
  }
}

classname<>はHackで用意された特殊な型で、
className::classで記述することを指示するもので、
Interfaceを指定することでそのインターフェースを実装したクラス名のみ記述ができるようになります。
こうすることでtypecheckerがImmMapの中身を認識して、
型安全かどうかがわかる、という仕組みです。

最後にエントリポイントを修正します。

src/Router/ExampleRouter.hack
use type Acme\Example\Router\ExampleRouter;
use type Facebook\HackRouter\{BaseRouter, HttpMethod};
use type Ytake\Hungrr\ServerRequestFactory;

<<__EntryPoint>>
async function mainAsync(): Awaitable<void> {
  require_once __DIR__.'/../vendor/autoload.hack';
  \Facebook\AutoloadMap\initialize();

  $router = new ExampleRouter();
  $request = ServerRequestFactory::fromGlobals();
  $vec = $router->routeRequest($request);
  $c = $vec[0];
  echo (new $c())->process($request);
}

これで / にアクセスするとActionクラスのprocessメソッドで指定した文字列が返却されます。
リクエストパラメーターをつけるとjson_encodeされて表示されます。
ここでポイントは先ほどの<<__ConsistentConstruct>>です。
TResponderで指定した型をtypecheckerが認識しているため、
<<__ConsistentConstruct>>がなければ不安定なコンストラクタとしてエラーで実行できません。
そのためここでは<<__ConsistentConstruct>>を記述し、
動的にインスタンス生成をできる様にしています。

今回はHackのルーターを通してHackの型にも触れる内容をお届けしました。
次回はXHPを使ってHTMLを安全に返却してみましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでの文字列送信方法

21803
項目表示の為には以下の項目を書く。
こんにちは。今回はPHPでのデータ送受信方法が分からないという人の為に説明します。

php>
ここのPOSTというのはデータ送信を示しています。もう一つGETという物が有りますが、このGETという物はURLのところの最後のところは相手側から送られてきたデータの事ですので、その内容が見えてしまいます。なので、データ送受信の際にはPOSTを使う事をお勧めします。
次にこのaction属性を使う事で送信先を指定することが出来ます。この場合ですとtestform.phpにデータを送信します。次にこのtypeについて説明します。このtypeで"text"と指定する事により、テキストボックスでデータを送信することが出来ます。
次に<?phpの内容について説明します。このphpというのは実行したい内容を書くところです。この中のコマンドを実行します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php artisan serveに任意のphp.iniを指定する方法

結論

php -c php.ini artisan serve

背景

php artisan serveで動かしている開発用のサーバー上で大きなファイルをアップロードする必要があり、実際にアップロードしたところIlluminate\Http\Exceptions\PostTooLargeException とエラーが出た。

ここはphp.iniを変更すれば通るだろうと考え、upload_max_filesizeとpost_max_sizeを変更したものの、なぜか同じエラー。phpinfoで確認すると値はきちんと更新されている。

原因

いろいろ調べてみたところ、php artisan serveは別のphp.iniを読みに行っているということまでは判明。

解決

ただ、どのphp.iniを読みに行っているのか分からない。

php.iniと同じディレクトリにphp-cli.iniを作成するとそれを読みに行ってくれるとか、その他色々な情報があったものの結局、動いたのは冒頭で示したやり方。

元々読みに行っていたファイルを変更したほうがスマートな気もするが、Webサーバーで使用される設定ファイルと同じものを指定できるならそれはそれで分かりやすくて便利な気がする。

コマンド中での指定される"php.ini"は現在のWebサーバーで使用されているファイルを見に行くようなので、php -iで場所を確認してから編集する。

$ php -i | grep php.ini
Configuration File (php.ini) Path => /etc/php/7.4/cli
Loaded Configuration File => /etc/php/7.4/cli/php.ini

編集後、php artisan serveを実行すると変更したパラメーターが反映された設定でサーバーが起動する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

10年戦士のレガシーPHPを改善するためにやってきたこと

ドワンゴで、ニコニコ動画とプレミアム課金の開発をしている @yoshikyoto です。

最近では、総合TOPのリニューアルや、↓の記事で書かれているプレミアム課金の移行をやっていました。

レガシーなPHPを改善してきた知見

ニコニコは古くからあるサービスで、レガシーなコードも多く存在しています。

僕は新卒でニコニコ動画に配属され、5年間ずっと PHP を触ってきました。その中で、多くの新機能の開発、古いコードのリファクタリング、いろいろやってきました。その知見をまとめます。

Composer, Laravel (フレームワーク)の導入

PHP を使っていて Composer を導入していないシステムは珍しい思いますが、10年もののシステムには、Composer が導入されていない場合もあります。導入されていないなら導入します。(Composer は JavaScript でいうところの npm 、 Ruby でいうところの Bundler です)

10年もののPHPには、フレームワークは導入されていません。URLのエンドポイントごとに PHP ファイルが存在しています。フレームワークを導入しましょう。基本的には Laravel を導入するのが良いです。

レガシーなプロダクトに Laravel を導入する記事も以前に書きました。

Slim Framework を導入する場合もありました。 DI、DB、Redis、Log といったライブラリが導入済みで、ルーティングだけなんとかしたい場合は Slim Framework でもよいです。

仙台の作並温泉のとある宿の本棚

ユニットテスト(PHPUnit)

私にとって、レガシーコードとは、単にテストの無いコードです

『レガシーコード改善ガイド』にはこう書いてあります。

コードに変更を加えづらくなる一番の原因は、テストが無いことです。ユニットテストがあれば、コード修正時にある程度の動作が保証できます。コードが綺麗でも、テストがなければ、コードの修正がしづらくなるのです。

ユニットテスト導入時には同時に、以下の2点を考えると良いです。

  • CIの導入
    • CIを導入しないと、いつの間にかテストが落ちている事に気づかない
  • IDE から phpunit コマンドを実行できるようにする
    • CIでしかテストを実行できないと、開発効率が良くないので、当然手元のPCでもテストを実行できる必要があるのですが、IDE の設定をすることで、開発効率が格段によくなる

CI の導入

特に説明不要かと思います。僕のチームでは Jenkins を利用していますが、CircleCI を使っているチームもあります。

Jenkinsfile という、CI の実行内容を定義できる仕組みがあるので(CircleCI でいう .circleci/config.yml みたいなもん)、これを使って phpunit コマンドを実行し、結果を表示させます。

pipeline {
  agent any

  // composer などのコマンドを叩くために必要
  environment {
    PATH = "/usr/local/bin:$PATH"
  }

  stages {
    stage ('setup') {
      steps {
        checkout scm
        // phpunit 等をインストールするために --dev を付ける
        sh 'composer install --dev'

        // テスト結果を格納しているディレクトリをきれいにする
        sh 'rm -rf results'
        sh 'mkdir results'
      }
    }
    stage ('phpunit') {
      steps {
        sh './vendor/bin/phpunit --log-junit results/phpunit_junit.xml'
      }
    }
  }
  post {
    always {
      junit 'results/phpunit_junit.xml'
    }
  }
}

IDE(IntelliJ / PHPStorm)の設定

僕は開発に IntelliJ を使っているのですが、テスト関数の横のボタンを押すだけで、テストを実行できるようになります。この機能を知った時には衝撃的でした。

手元で毎回すべてのテストを実行するには時間がかかるので、自分が修正した部分のテストだけ実行し、全体のテストは、CIで保証しますが、「自分の実装したテストだけを実行するコマンド」を忘れがちなので、それを IDE におまかせできるのも良い点です。

image.png

これを出すためには

  • PHP Interpreter の設定
  • composer.json の設定
  • Test Frameworks の設定

が必要です。

PHP Interpreter の設定 は、 Preferences > Languages & Frameworks > PHP の「CLI Interpreter」から設定できます。PCにインストールされているPHPのバージョンから選択できるので、適切なバージョンをインストールしてください。Docker上のPHPを設定するとかもできるはずです。

スクリーンショット 2020-12-04 16.11.28.png

composer.json の設定 は、Languages & Frameworks > PHP > Composer から設定できます。

Test Frameworks の設定は、 Languages & Frameworks > PHP > Test Frameworks から設定できます。composer で PHPUnit を入れていれば、「User composer autoloader」を選択し、autoload.php のバージョンを指定すれば OK です。うまく設定できていれば PHPUnit のバージョンが表示されます。

image.png

モック

PHPUnit にはモックの仕組みがありますが、コードとして読みづらいので、 Mockery というライブラリを使うのがおすすめです。

コーディング規約

括弧や演算子の前後のスペースの有無がぐちゃぐちゃなコードを見た経験はありますか?インデントにタブとスペースが混ざっていると、とても芸術的な見た目になります。しかし、コードにはそのような芸術性より、レビューしやすさが必要です。

PHP には PSR-12 というコーディング規約があるので、これをもとにコードを書きます。

コーディング規約についても PHPUnit と同じ理由で、CI と IDE の設定を両方設定しておくと良いです。

設定の方法については、僕が過去のブログで書いているので参考にしてください。

ログ

ログは、基本的に1つのファイルにすべて出力するのが良いかなと思います(ファイル数を増やしすぎないのが良いかなと思います)。ログファイルが増えると、そのたびに監視対象のファイルを追加する必要があります。また、もう出さなくなったログのファイルが残り、空のログファイルが無限にログローテートされていたりします。

ログを ElasticSearch などに突っ込めば、検索性については問題ありません。むしろ、複数ファイルを横断して検索する必要もなくなり便利です。

PHP の標準規約である PSR-3 ( https://www.php-fig.org/psr/psr-3/ )では、ログレベルは9段階が用意されています。Laravel で利用している Monolog はこれに従っていますが、全部を利用する必要は無いです。

ただ、最低でも、3段階くらいには分けておいたほうが良いです。

  • 即時対応が必要なログ(アラートが上がって電話がかかってくるタイプのやつ)
  • 翌営業日の対応でも問題ないが、検知しておく必要があるログ(Slackとかメールで流れるやつ)
  • ユーザーや関連システムからの問い合わせがあった場合のみ参照するログ(プッシュ通知の必要は無い)

ログには、

  • ログの日時(Laravel だと自動で付与される)
  • どのサーバーで出たか
  • どのエンドポイント(URL)で出たか
  • アクセスしたユーザーのID
  • スタックトレースや、スクリプトファイル名
  • 必要に応じてその他の情報

などを出します。

ログには、「何のログなのか・対応が必要なログなのか」「対応が必要な場合どのような対応をすれば良いのか」も出しておきましょう。エラーっぽいログなのに、必要な対応が書かれていないと、どうしたら良いのかわかりません。

仙台駅の近くにあるマンホール

コードの設計について

ニコニコ動画のコードは、以前はレイヤードアーキテクチャでしたが、最近は「依存性の逆転」をかなり意識したコードになっています(レイヤードアーキテクチャより前は、スパゲッティアーキテクチャでした)。

一方で、プレミアム課金については、まだほとんどがスパゲッティアーキテクチャです。まずはレイヤードアーキテクチャを目指してコードをリファクタリングして行こうと思っています。

どういう設計を目指すかについては、現状のコードとメンバーのスキルを考えてやっていくことが重要です。プレミアム課金システムのことを考えると

  • とりあえずテストを拡充していくことが重要だと思うので、シンプルでわかりやすいレイヤードアーキテクチャでよい
    • テストが充実すれば、レイヤードアーキテクチャ→クリーンアーキテクチャの変更もしやすくなる
  • メンバーのレベル感も考える
    • プレミアム課金システムに詳しいメンバーが少ないので、適切な設計をしづらい(配属されて浅い人が多い)
      • 教育コストとかもかかる
    • レイヤードアーキテクチャとクリーンアーキテクチャを両方つかって初めて、それぞれの良さがわかる。
      • この経験がメンバーの成長にもつながる

といった感じです。

PHPDoc

PHPDoc で、クラスの Doc を書くことは重要だと僕は思っています。

/**
 * 何をするクラスなのか、何をしないクラスなのか書くことで、
 * 適切なメソッドを適切なクラスに実装できるようになる。
 * @see 仕様ページへのリンクなど
 */
class Xxxxx
{
    ...
}

また、@param@return についても、PHP の型宣言より詳細な情報を書け、IDE の補完による恩恵も受けられるので、書いておいたほうがいいです。

/**
 * @return string[] <- array の中身の型も書ける
 */
public function get(): array;

/**
 * @return int|false <- mixed の詳細まで書ける
 * @throw RuntimeExeption なんの時のエラーなのかちゃんと説明を書いてね
 */
public function get(): mixed;

GuzzleHttp

レガシーなコードでは API Client の実装に file_get_contents()curl を使っていたと思いますが、 Composer があるなら Guzzle を使うほうが良いです。

Guzzle は PSR-18 のインタフェースに従って実装されています。

PSR-18 で重要なのが、エラーハンドリングの部分です。400エラーや、500エラーが返ってきた時には例外を投げるようになっており、Guzzle のエラーは以下のような構造になっています。

  • \RuntimeException
    • TransferException -- HTTPリクエストに関する例外
      • ConnectException -- 接続がtimeoutした場合などの例外
      • TooManyRedirectsException -- リダイレクトループの場合の例外
      • BadResponseException -- 4XXまたは5XXエラー
        • ClientException -- 4XXエラー
        • ServerException -- 5XXエラー

エラーについて理解し、どのようなエラーの時にどのようなハンドリングをすればいいのか、注意しましょう。

まとめ

仙台市作並のあたりにあるニッカウヰスキー宮城峡蒸溜所

先日、ウィスキーの蒸留所に行ってきたのですが、できたばかりのウィスキーの色は、12年後のウィスキーの色と全然違いました。

このように、ものは10年経つと全然変わります。先日 PHP 8 がリリースされたり、去年には PSR-12 が発表されたり、PHP も進歩しているので、我々も、常に新しい技術を取り入れて、改善を続けています。

レガシーコードばっかりいじっていて大変そうに思えますが、このレベルのレガシーコード改善は、ほぼ新規アプリケーションの開発のようなものです。ニコニコ動画のサーバーの規模は大きく、その中でどのようにコードを組み上げて行くか考えるのは非常に面白いです。

こういうリファクタリングのための工数を取ってもらえるのは、ドワンゴの非常に良い文化ですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel ログインテスト

Laravel Unitテスト

 ポートフォリオを作成して、ユニットテストを実行するまでの一連の流れを備忘録として記述します。
 作成したポートフォリオでは初期画面からホーム画面に遷移するのにログインする必要があるシステムでした。コントローラーのテストで下記のように実行してもエラーが表示された為、ログインテストを別で作成してモデルのテストを実行しました。

pubulic function testBasicTest() {
    $this->get('/home')->assertStatusOk();
    /// /homeにはログインが必要な為アクセスできない
}

1.テスト用のDatabaseの準備

今回はMySQLを使用しました。使用するデータベースに応じてテスト用のデータベースを準備。

$ mysql -uroot -p
$ ?(password入力)
mysql> create database データベース名
mysql> show databases
    今回作成したデータベースがあるか確認
mysql> exit

Config/database.phpの'connections'にテスト用のデータベースの設定を追加。

'testing' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => "作成したデータベース名",
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

phpunit.xmlのタグ内に下記を追加。

    <server name="DB_CONNECTION" value="mysql"/>
    <server name="DB_DATABASE" value="[データベース名]"/>

2.ファクトリーを準備する

 今回作ったポートフォリオはLaravelに標準で準備されているUserテーブルを使用した為、変更しませんが追加などしている場合は変更する。(database/factories/UserFactory.php)

3.テスト用ファイルの作成

下記コマンドを実行するとtests/Featureにテスト用ファイルが作成されます。

$ php artisan make:test LoginTest

作成されたLoginTest.phpを下記に変更

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
use App\User;
use Auth;

class LoginTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testExample()
    {
        $user = factory(User::class)->create([
            'password'  => bcrypt('laraveltest123')
            //パスワードは好きな言葉で大丈夫です
        ]);

        // 認証されないことを確認
        $this->assertFalse(Auth::check());

        // ログインを実行
        $response = $this->post('login', [
            'email'    => $user->email,
            'password' => 'laraveltest123'
            //先ほど設定したパスワードを入力
        ]);

        // 認証されていることを確認
        $this->assertTrue(Auth::check());

        // ログイン後にホームページにリダイレクトされるのを確認
        $response->assertRedirect('home');
     //作成したサイトでログイン後にリダイレクトされるルート情報を記述
    }
}

4.マイグレーションの実行

 下記コマンドを実行するとテスト用のデータベースにテーブルが作成されます。
--database==testingを忘れないでください。この記述によりdatabase.phpに用意したtesing設定で実行されます。

$ php artisan migrate:refresh --database==testing

5.テストの実行

$ vendor/bin/phpunit

以上でテスト完了です!
作成したデータベースのUserテーブルには新規レコードが登録されていて正しくログイン出来ています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

windows php file grep 方法

php -i | find '"fileinfo"'

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フレームワーク各種自作サーバ接続用チートシート

いろいろなフレームワークを、centOS7で接続するときに便利なローカルサーバ接続用のコマンド集です。ローカルホスト接続はよく見るのですが、この条件を毎回調べるのが面倒なので、テスト起動した上で備忘録としてまとめたものです。

※随時修正

PHP編

Laravel

デフォルトポートは8000

#php artisan serve -host 0.0.0.0

192.68.11.xx:8000/プロジェクト名

cakePHP

サーバ起動不要

192.168.11.xx/プロジェクト名

CodeIgniter3

サーバ起動不要

192.168.11.xx/プロジェクト名

Symfony

デフォルトポートは8000

#php bin/console server.run 0.0.0.0

192.168.11.xx:8000/

Yii2

デフォルトポートは8000

#php yii serve-port=8000

192.168.11.xx/yii2/web/

Zend Framework

サーバ起動不要

192.168.11.xx/プロジェクト名/public/

Javascript編

React

記述中

Vue.js

記述中

Ruby編

デフォルトポートは3000

Rails

#bundle exec rails s -b 0.0.0.0

※bundle execを省略する方法もあるので、その場合はbundle execは不要。

192.168.11.xx:3000/

Sinatra

デフォルトポートは4567

#bundle exec ruby touch app.rb -o 0.0.0.0

192.168.11.xx:4567

Python編

Django

デフォルトポートは8000

①起動設定

#vi /プロジェクト名/setting.py

以下のように書き換えて保存

②起動

setting.py
ALLOWED_HOSTS = ["*"]
#python manage.py runserver 0.0.0.0:8000

192.168.11.xx:8000/プロジェクト名

Flask

デフォルトポートは5000

各種pyファイルのサーバ設定に以下のように書き込む。

#サーバ接続設定
if __name__ == "__main__":
 app.run(host='0.0.0.0')
#python pyファイル名

192.168.11.xx:5000/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フレームワーク各種ローカルサーバ接続用コマンドチートシート

いろいろなフレームワークを、centOS7で接続するときに便利なローカルサーバ接続用のコマンド集です。ローカルホスト接続はよく見るのですが、この条件を毎回調べるのが面倒なので、今までにテスト起動した上で備忘録としてまとめたものです。

※随時修正

①PHP編

1:Laravel8

デフォルトポートは8000

プロジェクトに遷移し、以下のサーバ起動コマンドを打ち込む

#php artisan serve --host 0.0.0.0

192.68.11.xx:8000

2:cakePHP4

サーバ起動不要

192.168.11.xx/プロジェクト名/index.php

3:CodeIgniter4

サーバ起動不要

192.168.11.xx/プロジェクト名/public

4:Symfony4

デフォルトポートは8000

#php bin/console server.run 0.0.0.0

192.168.11.xx:8000/

5:Yii2

デフォルトポートは8000

#php yii serve-port=8000

192.168.11.xx/yii2/web/

6:Zend Framework2

サーバ起動不要

192.168.11.xx/プロジェクト名/public/

②Javascript編

1:React

デフォルトポートは3000

①webpacker.config.jsに追記する

webpacker.config.js
devServer:{
    host: '0.0.0.0'
}

②起動

$npm start

192.168.11.xx:3000

2:Vue.js

デフォルトポートは8080

①package.jsonに追記

package.json
"serve": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --host 0.0.0.0 --port=xxxx

②起動

#npm run serve

192.168.11.xx:8080

③Ruby編

1:Rails

デフォルトポートは3000

#bundle exec rails s -b 0.0.0.0

※bundle execを省略する方法もあるので、その場合はbundle execは不要。

192.168.11.xx:3000/

2:Sinatra

デフォルトポートは4567

#bundle exec ruby touch 任意のrbファイル -o 0.0.0.0

192.168.11.xx:4567

④Python編

1:Django

デフォルトポートは8000

①起動設定

#vi /プロジェクト名/setting.py

以下のように書き換えて保存

setting.py
ALLOWED_HOSTS = ["*"]

②起動

#python manage.py runserver 0.0.0.0:8000

192.168.11.xx:8000/プロジェクト名

2:Flask

デフォルトポートは5000

各種pyファイルのサーバ設定に以下のように書き込む。

#サーバ接続設定
if __name__ == "__main__":
 app.run(host='0.0.0.0')
#python 任意のpyファイル

192.168.11.xx:5000/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP 連想配列の値とキーを取得する方法

目的

  • 連想配列に格納された値とそのキーを取得する方法をまとめる。

前提情報

  • 下記サービスを用いて動作を確認した。
  • 超基礎の内容であるが理解できていなかったのでまとめる。

詳細

  • 下記のようにforeach文を使用することで値とキーを取得する事が可能である。

    <?php
    $strList = [
        'str1' => 'こんにちは',
        'str2' => 'おはようございます'
    ];
    
    foreach ($strList as $key => $value){
      echo "キーは $key" . "\n";
      echo "値は $value" . "\n";
    }
    ?>
    
  • 上記のコードを実行すると下記のように出力される。

    キーは str1
    値は こんにちは
    キーは str2
    値は おはようございます
    
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

※学習用メモ デザインパターン:Decorator編[PHP]

Decoratorパターンとはどういうものか

Decoratorとは装飾者という意味です。
このパターンは既存のオブジェクトを新しいDecoratorオブジェクトでラップすることで機能やその状態の拡張を実現します。
また、ラップされていることで、基の状態を維持する事につながり、1つのベースに対して状態のバリエーションを作り出す事も出来ます。

Decoratorパターンの方針は、既存のオブジェクトを新しいDecoratorオブジェクトでラップすることである。その方法として、Decoratorのコンストラクタの引数でラップ対象のComponentオブジェクトを読み込み、コンストラクタの内部でそのオブジェクトをメンバに設定することが一般的である。(wikipediaより)

[参考サイト]
https://ja.wikipedia.org/wiki/Decorator_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
https://www.ritolab.com/entry/132
https://qiita.com/i-tanaka730/items/a0f53d70b0830cfd150b

Decoratorパターンを用いるメリット

・このパターンはComponent、Decorator双方が同様のインターフェースを有しており、あらゆるDecoratorで包めば包むほど機能が追加されていきます。その際に包まれる側のオブジェクトを変更する必要がないため、変更を行うことなく機能の追加を用意に行うことができます。

サンプルコード

仕様

・ガチャを引く
・基本として、与えられた配列をset,get,その中からランダムで一つ抽出する機能をもつ。
・追加機能として、「特定のアイテムを強化版に」、「特定のアイテムの獲得率を〇倍」という機能を持つことが可能。

ガチャのインターフェース

GachaInterface.php
interface GachaInterface
{
    public function getGachaContents();
    public function setGachaContents($gachaContentArray);
    public function getGachaName();
    public function setGachaName($gachaName);
}

ガチャ情報のベース(Component)

BaseGacha.class.php
class BaseGacha implements GachaInterface
{
    private $gachaContentArray = array();
    private $gachaName = '';

    public function getGachaContents()
    {
        return $this->gachaContentArray;
    }

    public function setGachaContents($gachaContentArray)
    {
        $this->gachaContentArray = $gachaContentArray;
    }

    public function getGachaName()
    {
        return $this->gachaName;
    }

    public function setGachaName($gachaName)
    {
        $this->gachaName = $gachaName;
    }
}

ガチャのDecoratorクラス

GachaDecorator.class.php
abstract class GachaDecorator implements GachaInterface
{
    private $obj;

    public function __construct(GachaInterface $obj)
    {
        $this->obj = $obj;
    }

    public function getGachaContents()
    {
        return $this->obj->getGachaContents();
    }

    public function setGachaContents($gachaContentArray)
    {
        $this->obj->setGachaContents($gachaContentArray);
    }

    public function getGachaName()
    {
        return $this->obj->getGachaName();
    }

    public function setGachaName($gachaName)
    {
        $this->obj->setGachaName($gachaName);
    }
}

特定アイテムのみ強化する

EvolutionGacha.class.php
class EvolutionGacha extends GachaDecorator
{
    private $itemName; // アイテム名

    public function __construct(GachaInterface $obj, $itemName)
    {
        parent::__construct($obj);
        $this->itemName = $itemName;
    }

    public function getGachaContents()
    {
        $resultArray = array();

        foreach (parent::getGachaContents() as $itemData) {
            $itemDataArray = array();
            if ($itemData['name'] == $this->itemName) {
                $itemDataArray['name'] = $itemData['name'] . '+';
            } else {
                $itemDataArray['name'] = $itemData['name'];
            }
            $itemDataArray['weight'] = $itemData['weight'];

            $resultArray[] = $itemDataArray;
        }

        return $resultArray;
    }

    public function getGachaName()
    {
        return sprintf('【%s強化中!】' . parent::getGachaName(), $this->itemName);
    }
}

特定アイテムの出現率を〇倍に

MagnificationGacha.class.php
class MagnificationGacha extends GachaDecorator
{
    private $itemName; // アイテム名
    private $magnification = 0; // 倍率

    public function __construct(GachaInterface $obj, $itemName, $magnification)
    {
        parent::__construct($obj);
        $this->itemName = $itemName;
        $this->magnification = $magnification;
    }

    public function getGachaContents()
    {
        $resultArray = array();

        foreach (parent::getGachaContents() as $itemData) {
            $itemDataArray = array();
            if ($itemData['name'] == $this->itemName) {
                $itemDataArray['weight'] = $itemData['weight'] * $this->magnification;
            } else {
                $itemDataArray['weight'] = $itemData['weight'];
            }
            $itemDataArray['name'] = $itemData['name'];

            $resultArray[] = $itemDataArray;
        }
        return $resultArray;
    }

    public function getGachaName()
    {
        return sprintf('【%s出現率%s倍中!】' . parent::getGachaName(), $this->itemName, $this->magnification);
    }
}

使ってみる

あらかじめデータを用意し、ガチャ結果を取得

$gachaContentArray = array(
    array(
        'weight' => 10,
        'name' => '剣',
    ),
    array(
        'weight' => 10,
        'name' => '盾',
    ),
    array(
        'weight' => 10,
        'name' => '鎧',
    ),
    array(
        'weight' => 10,
        'name' => '靴',
    ),
    array(
        'weight' => 10,
        'name' => '腕輪',
    ),
);

class DrawGacha
{
    public static function getGachaResult($gachaArray)
    {
        if (! $gachaArray) {
            return array();
        }

        $totalWeight = 0;

        foreach ($gachaArray as $itemData) {
            $totalWeight += $itemData['weight'];
        }

        $num = mt_rand(1, $totalWeight);

        $judgeTotalWeight = 0;
        foreach ($gachaArray as $itemData) {
            $judgeTotalWeight += $itemData['weight'];

            if ($num < $judgeTotalWeight) {
                return $itemData['name'];
            }
        }
    }
}

$baseGacha = new BaseGacha();
$baseGacha->setGachaContents($gachaContentArray);
$baseGacha->setGachaName('アイテムガチャ');

$evolutionGacha = new EvolutionGacha($baseGacha, '剣');
echo $evolutionGacha->getGachaName() , 'の結果:' . DrawGacha::getGachaResult($evolutionGacha->getGachaContents()) . PHP_EOL;

$magnificationGacha = new MagnificationGacha($baseGacha, '盾', 20);
echo $magnificationGacha->getGachaName() , 'の結果:' . DrawGacha::getGachaResult($magnificationGacha->getGachaContents()) . PHP_EOL;

$extremeGacha = new EvolutionGacha(new MagnificationGacha($baseGacha, '靴', 2), '鎧');
echo $extremeGacha->getGachaName() , 'の結果:' . DrawGacha::getGachaResult($extremeGacha->getGachaContents()) . PHP_EOL;

/*結果
【剣強化中!】アイテムガチャの結果:剣+
【盾出現率20倍中!】アイテムガチャの結果:盾
【鎧強化中!】【靴出現率2倍中!】アイテムガチャの結果:鎧+
*/

まとめ

・Decorator パターンは様々な機能追加を柔軟に行いたい場合に利用すると効果を発揮します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでの入力フォーム作成

PHPでの入力フォーム作成について

以下のような入力フォームをPHPで作成し、
別ファイルへ出力するようにした。

スクリーンショット 2020-12-03 20.20.14.png

ソースコード(ファイル名index.php)

<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<form action="submit.php" method="get" >
<p> 名前: <input type="text" name="your_name" value=""></p>
<p> 年齢: <input type="text" name="your_age" value=""></p>
<input type="submit" >
</form>
</body>
</html>

名前と年齢を入力して、送信ボタンを押すと以下の通り、出力される(出力ファイル名:submit.php)

スクリーンショット 2020-12-03 20.23.20.png

スクリーンショット 2020-12-03 20.25.28.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<p><?php echo $_POST["your_name"] ?>です</p>

<p>年齢は<?php echo $_POST["your_age"] ?>です</p>
</body>
</html>

今回はGETでサンプルコードを記載したが、GETをPOSTに変更すれば同様に出力される。

GETとPOSTの違い

GETについて

GETでデータを取得した場合、URLの後ろに送りたいデータを付与して、送信する。
以下の通り、入力した名前と年齢がURLに反映されている。

GETを使う場合、URLにデータが残ってしまうというデメリットあり

スクリーンショット 2020-12-03 20.28.44.png

POSTについて

POSTを使用する場合、HTTPリクエスト内のメッセージボディにデータを含めて送信する。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<form action="submit.php" method="post" >
<p> 名前: <input type="text" name="your_name" value=""></p>
<p> 年齢: <input type="text" name="your_age" value=""></p>
<input type="submit" >

</form>
</body>
</html>

postでデータを取得した場合、URLには送信したデータが記載されない。
URLに記載されるべきではない情報(PW等のログイン情報)がPOSTで送信される。

スクリーンショット 2020-12-04 5.34.06.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最近やったことのメモ(MVVM, asyncとdefer)

MVVM

Webサービスの設計思想(ソフトウェアアーキテクチャ)
MはModel、VはView、VMはViewModelのこと

View -> View Model -> Model の順

役割でいうと、
表示 → 表示するために値の取得と加工 → 値  こんな感じ?

View Modelが値と表示の間を取り持ってくれます。超優秀ですね☆

asyncとdefer

JavaScriptファイルの非同期での読み込み
<script async src="/abc.js"></script>
<script defer src="/xyz.js"></script>

asyncとdeferどちらもドキュメントのパース中にスクリプトをダウンロード実行。

async・・・パースが完了前に実行。asyncスクリプトの実行は必ずしも順番通りではない。
defer・・・パース完了後に実行。deferスクリプトの実行は順番に行われる。

スクリプトが独立 → async
スクリプトが依存 → defer

今日の名言

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
(付け足すものがなくなったときでなく、取り除くものがなくなったとき、それが完璧になるということだ)
-アントワーヌ・ド・サン=テグジュペリ(作家)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む