Composerでrequireしたときのエラー解決法

 

どうも!名古屋生活満喫してます。
大須が好きで毎週行ってしまってます。
ただ、友達がいないので一人で大体いってますw
名古屋で遊んでくれる友達欲しい。。。

今回の記事は、
PHPerなら、とても助かっているであろうComposerについてです。
いろんなパッケージ使おうとrequireしてたら、なんか導入できない!
英語でわかわからんってなって諦めてました。
そろそろ次に進めなければと思い、私が問題解決を行った方法をまとめます。

 

composerコマンドだけで、できる方法はありそうなので見つけたら教えてください!
私も見つけたら書きます\(^^)/

 

Composerとは…

公式サイト:https://getcomposer.org/

PHPで何かを作るときに、
既存のライブラリやパッケージを導入/更新するときに使います。
そのとき、依存管理もしてくれるツールです。
JavaScriptでいうnpm的なやつだと思っています。

Composerの公式サイトに飛んだときに気づいたのですが、
ロゴのおじさん、いろんなカラーパターンがある!!!
リロードするたびに変化するのでぜひ遊んでみてください!

composer_uncle

(多分5パターン)

 

 

本題に入ります。

例えばこんな例

hautelook/alice-bundleをdev環境に導入する時….
下記のエラーで導入できなかった例です。

$ composer require hautelook/alice-bundle --dev
Using version ^2.5 for hautelook/alice-bundle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for hautelook/alice-bundle ^2.5 -> satisfiable by hautelook/alice-bundle[2.5.0].
    - hautelook/alice-bundle 2.5.0 requires doctrine/orm ^2.5.11 -> satisfiable by doctrine/orm[2.5.x-dev, 2.6.x-dev, 2.7.x-dev, v2.5.11, v2.5.12, v2.5.13, v2.5.14, v2.6.0, v2.6.1, v2.6.2, v2.6.3] but these conflict with your requirements or minimum-stability.


Installation failed, reverting ./composer.json to its original content.

 

日本語でざっくり訳すと(わたしの解釈です)

hautelook/alice-bundleのv2.5.0(バージョン指定してないので最新版をインストールしようとします)は、
doctrine/ormのv2.5.0以上が必要です。

 

 

ということは、doctrine/ormが今v2.5.0以下なんです。
いざ、確認!!!

$ composer show doctrine/orm    //導入中のpackage(doctrine/orm)詳細を表示
name     : doctrine/orm
descrip. : Object-Relational-Mapper for PHP
keywords : database, orm
versions : * v2.4.7
...

 

エラー警告通りで、doctrine/ormのバージョンは、2.5.0以下の2.4.7でした。
ということで、現状のパッケージ群のままでhautelook/alice-bundleを導入する想定で問題解決していきたいと思います。

そんなとき使うのが、「packagist 」です!
packagistでは、composerで導入できるパッケージ群が管理されていて検索ができてバージョンごとの詳細も確認できます。

そこで導入したいパッケージ(hautelook/alice-bundle)を検索して、左側のバージョン選択欄からバージョンを下げながら見ていって、
導入ができない原因のパッケージ(doctrine/orm)のバージョン(現在v2.4.7)が通る導入したいパッケージ(hautelook/alice-bundle)のバージョンを見つけます。

\日本語下手でごめんなさい…/

packagist検索結果

 

この方法により
hautelook/alice-bundlev1.4.1であれば、
doctrine/orm: ^2.4doctrine/ormv2.4以上なので
導入できることがわかりました!

 

したがって、hautelook/alice-bundleを最新版ではなくv1.4.1をバージョン指定で導入してみます。

$ composer require hautelook/alice-bundle:1.4.1 --dev
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 3 installs, 0 updates, 0 removals
  - Installing fzaninotto/faker (v1.8.0): Downloading (70%)
  ....

いけましたー!!!

$ composer show hautelook/alice-bundle
name     : hautelook/alice-bundle
descrip. : Symfony2 Bundle to manage fixtures with Alice and Faker.
keywords : Fixture, alice, faker, orm, symfony
versions : * v1.4.1
...

 

便利なComposerコマンド一覧

参考:https://qiita.com/KEINOS/items/86a16b06af6e936a1841

インストール済みのパッケージ情報の一覧を取得

$  composer show -i
You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.
...
phpunit/phpunit                    7.5.12   The PHP Unit Testing framework.

 

パッケージ名指定もできます。

$ composer show [vendor]/[package]
$ composer show phpunit/phpunit
name     : phpunit/phpunit
descrip. : The PHP Unit Testing framework.
keywords : phpunit, testing, xunit
versions : * 7.5.12
type     : library
license  : BSD 3-Clause "New" or "Revised" License (BSD-3-Clause) (OSI approved) https://spdx.org/licenses/BSD-3-Clause.html#licenseText
source   : [git] https://github.com/sebastianbergmann/phpunit.git 9ba59817745b0fe0c1a5a3032dfd4a6d2994ad1c
dist     : [zip] https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ba59817745b0fe0c1a5a3032dfd4a6d2994ad1c 9ba59817745b0fe0c1a5a3032dfd4a6d2994ad1c
path     : /path/vendor/phpunit/phpunit
names    : phpunit/phpunit
...

 

まとめ

英語のエラーって怖いって思いがちですが、
問題解決方法だったりヒントを表示してくれるだけでほんと助かるなーって気持ちに改めてなりました。

phpenvでいろんなPHPバージョンを操る

 

どうも、PHP大好きなんでPHPの記事ばっかで、すみません。

PHPってバージョンごとに書き方違うし、
レガシーな業務ではPHP5系だけど、個人ではPHP7系使いたいってことがよくあります。
そんなとき、composerで管理しちゃえばいいんですが、phpenv使ってバージョン切り替えできるとpsyshでも試せるので良いです!
そこで、phpenvでのバージョン切り替え方法をいっつも忘れてしまうので備忘録として書きます。

phpenvとphp-buildの導入

以下の記事を参考に導入しました。
phpenvとは別に、php-buildが必要なのを知らず私は導入に手こずりました^^;
https://qiita.com/ispern/items/97e3e6d910eb98b5de75

PHPのバージョンを切り替える

今回はグローバルのPHPバージョンを切り替える方法を記載します。
ローカルの場合はglobalの部分をlocalに置換すれば大体行けると思います。(多分)

▼現在のバージョンを確認

$ php -v
PHP 7.1.30 (cli) (built: Jul  4 2019 21:55:42) ( NTS )
...

 

▼現在適用されているphp.ini(設定ファイル)の確認
現在、phpenvで7.1.30が適用されいることがわかります。

$ php --ini
Configuration File (php.ini) Path: /Users/use_name/.phpenv/versions/7.1.30/etc
Loaded Configuration File:         /Users/use_name/.phpenv/versions/7.1.30/etc/php.ini
Scan for additional .ini files in: 
...

 

▼phpenvでインストールされているバージョン一覧と適用中バージョンの確認
*がついているのが、現在適用中のバージョンになります。
7.1.30の他に7.2.20を導入しています。

$ phpenv versions
  system
* 7.1.30 (set by /Users/user_name/.phpenv/version)
  7.2.20

 

▼phpenvでインストール可能なバージョンリスト一覧を表示

$ phpenv install -l
Available versions:
  5.2.17
...
  7.3.6
  7.3.7
...

 

▼バージョンを指定して、インストールをする。
今回は新たに、v5.6.1を入れます。
!!!時間かかります!!!

$ phpenv install 5.6.40
[Info]: Loaded extension plugin
[Info]: Loaded apc Plugin.
[Info]: Loaded composer Plugin.
...

 

インストールし終わったら、、、
▼インストール済みの一覧にあるか確認します。
5.6.1が新しく追加されています。

$ phpenv versions
  system
  5.6.1
* 7.1.30 (set by /Users/user_name/.phpenv/version)
  7.2.20

 

▼hashの更新をします。
!!!rehash忘れがちなので、忘れずに!!!

$ phpenv rehash

 

▼グローバルのPHPバージョンを切り替えます。

$ phpenv global 5.6.1

 

▼現在のバージョンを確認します。
見事に5.6.1に切り替えれています\(^^)/

$ phpenv versions
  system
* 5.6.1 (set by /Users/user_name/.phpenv/version)
  7.1.30
  7.2.20
$ php -v
PHP 5.6.1 (cli) (built: Jul 14 2019 18:29:23)
...

参考:https://www.aiship.jp/knowhow/archives/25452

まとめ

phpenvってpyenvって打ち間違えますよね!!!?
わたしだけですか?
(多分、みんなそうなはず)

 

これほしい!

DateTime::modifyに気をつける

 

どうも!実は、
この一ヶ月有給消化と言う名の
ニートしてまして、
明日から社会人に戻ります。。。
遅刻しないかとかすっごい心配です^^
新しい環境にdkwk(ドキドキワクワク)してます!

ちょっと遅刻に絡めて、
DateTime::modify
を使用する上で、気をつけるべきところを
備忘録として書いておきます。

 

※知ってる人は知ってるであろうことですし、
まあ、そりゃそうでしょって思う方もいると思います。

DateTime::modifyは使うたびに、
タイムスタンプを変更しています。
なので、何度も何度も使用すると予期せぬことに…

参考:https://www.php.net/manual/ja/datetime.modify.php

 

test.php

<?php

$d = new DateTime();
echo "{$d->format('Y-m-d')} \n";
// 本日、2019-06-30が起点になる

echo "{$d->modify('+1 days')->format('Y-m-d')} \n";
// 予想: 2019-06-30の1日後の「2019-07-01」

echo "{$d->modify('+1 month')->format('Y-m-d')} \n";
//予想: 2019-06-30の1ヶ月後の「2019-07-30」
$ php test.php
2019-06-30
2019-07-01
2019-08-01  //実は、2019-07-01の1ヶ月後になる!

$d->modify(‘+1 month’)の前に、
$d->modify(‘+1 days’)を実行しているために
このような結果になっています。

 

 

予想通りにしたいときは、
newし直したり、cloneしなおすといい感じになります。

<?php

$d = new DateTime();
echo "{$d->format('Y-m-d')} \n";
// 本日、2019-06-30が起点になる

echo "{$d->modify('+1 days')->format('Y-m-d')} \n";
// 予想: 2019-06-30の1日後の「2019-07-01」

/* ここから変更 */
$d_1 = new DateTime(); //newし直す
echo "{$d_1->modify('+1 month')->format('Y-m-d')} \n";
//予想: 2019-06-30の1ヶ月後の「2019-07-30」
$ php test.php
2019-06-30
2019-07-01
2019-07-30  //2019-06-30の1ヶ月後になる!(予想通り)

 

 

date_modify — DateTime::modify() のエイリアス

っていうのは、知らなかったです・・・。
時間を操るって癖があって難しいですねー
っていうことで、明日から遅刻しないように頑張ります!!!

 

Herokuのアドオン「Heroku Postgres」を使ってみる

 

どうも!
大好きな宮崎を旅立ちまして、名古屋市民になりました。
4日間荷物が来なかったので、
床に毛布一枚でねるという生活で腰が痛かったです。
荷物がきた時は嬉しくて、
4時間以内にすべてダンボールを開封し部屋完成させました。

Heroku好きなんですが、DB使えるの知らなかったです。
!! アドオンでPostgreSQLを使えるのです !!(無料枠あり)

使い方

1.Heroku Postgresの導入・データベースの作成

– コマンドより

$ cd [APP_NAME]
$ heroku addons:create heroku-postgresql:hobby-dev
Creating heroku-postgresql:hobby-dev on ⬢ [APP_NAME]... ⣾
Creating heroku-postgresql:hobby-dev on ⬢ [APP_NAME]... free
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pg:copy
Created [DATABASE_NAME] as HEROKU_POSTGRESQL_GRAY_URL
Use heroku addons:docs heroku-postgresql to view documentation

データベースが作成されます。
[DATABASE_NAME]の部分に具体的なデータベース名が入ります。

$ heroku pg:info
(node:6031) [DEP0066] ...
=== DATABASE_URL
Plan:                  Hobby-dev
Status:                Available
Connections:           0/20
PG Version:            11.3
Created:               2019-06-19 01:54 UTC
Data Size:             8.1 MB
Tables:                3
Rows:                  1/10000 (In compliance)
Fork/Follow:           Unsupported
Rollback:              Unsupported
Continuous Protection: Off
Add-on:                [DATABASE_NAME]

 

– Herokuダッシュボードより
Herokuダッシュボード > Heroku Postgresを導入したいAPPを選択後、
Resorce  > Add-ons に遷移する。
「Heroku Postgres」を検索し、プランを選んでをProvisionする
今回はフリープランの、「Hobby Dev – Free」を選びました。

データベースが作成されます。
[DATABASE_NAME]の部分に具体的なデータベース名が入ります。

 

2.データベース操作

– コマンドより
※ローカルPCより、HerokuPostgresに接続して直接SQL操作をしたい場合、
ローカルPCにpsqlコマンドが導入されていないとできませんので以下を参考に導入してください。
参考:https://qiita.com/ucan-lab/items/fb74af3d78e71407db7b

$ heroku pg:psql [DATABASE_NAME]  //データベースに接続
(node:6110) [DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated
--> Connecting to [DATABASE_NAME]
psql (11.3)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.



-- テーブル作成
[APP_NAME]::DATABASE_URL=> CREATE TABLE member_tb (id serial PRIMARY KEY,user_id TEXT NOT NULL,display_name TEXT NOT NULL);
CREATE TABLE

[APP_NAME]::DATABASE_URL=> SELECT * FROM member_tb;
 id | user_id | display_name
----+---------+--------------
(0 rows)



-- レコード挿入
[APP_NAME]::DATABASE_URL=> INSERT INTO member_tb(user_id, display_name) VALUES ('abc123', 'taro');
INSERT 0 1

[APP_NAME]::DATABASE_URL=> SELECT * FROM member_tb;
 id | user_id | display_name
----+---------+--------------
  1 | abc123  | taro
(1 row)

 

– Herokuダッシュボードより
Datestoresにてデータベースを選択後、
Dataclips > Create a new dataclip

ここでは、読み込み(SELECT)のみ実行可能。
書き込み(CREATE TABLEやINSERT)はできませんでした・・・。

 

 

3.アプリとデータベースの接続例

データベースが作成されると自動で、
Setting > Config Varsに「DATABASE_URL」が追加されます。
これをgetenvで使用します!
※データベースが複数存在する場合、「HEROKU_POSTGRESQL_GRAY_URL」「HEROKU_POSTGRESQL_MAROON_URL」のように追加されます。

PHP Data Objects (PDO) 拡張モジュールを使いました。

<?php

$url = parse_url(getenv('DATABASE_URL'));
$dsn = sprintf('pgsql:host=%s;dbname=%s', $url['host'], substr($url['path'], 1));
$pdo = new PDO($dsn, $url['user'], $url['pass']);


$stmt = $pdo->prepare("SELECT * FROM member_tb WHERE id = ?");
$stmt->execute([$id]);
$result = $stmt->fetch();

 

参考:

http://neos21.hatenablog.com/entry/2018/12/06/080000
https://qiita.com/ucan-lab/items/fb74af3d78e71407db7b

まとめ

無料枠あるのすごいありがたいです^^
これでいっぱいアプリ作れますね。
わたしはHerokuPostgresを使ってLINEBOT作成中です。
近々、BOTの記事投稿できるように頑張りまーす!

複数チケットサービス検索用ライブラリを作って、はじめてのpackagist登録

どうもー!
5月なのに暑いですね、そして梅雨こないですねー
宮崎にも来る気配なしです。

 

今日も音楽フェスに行くのですが、
皆さん、チケットを取る際にどのチケット販売サービスを使っていますか?
どういう基準で選んでますか?
ポイントだったり会員ランクなどてしょうか。

 

入手困難なバンドなんかだと、
私の経験でよくあるのがA,Bというサービスではsold outにもかかわらず、
Cでは残りわずかで残っていたりするのです。
ありがたく即購入するんですが、
この事実に辿りつくまで、
サービスAで検索→sold outを確認
→サービスBで検索→sold outを確認
→サービスCで検索→残りわずかを確認
→嬉し恥ずかし焦る気持ちを抑える
→無事ゲット!!!(ゴール)

ゴールに行くまで3サイトも順番に回るのです。
順番や早さによっては、
先に早く見ておけば取れたかもしれないチケットを
失ってしまうのです。
そんな自分の経験から、複数サービス
(現在チケットぴあ、eplus、ローチケ)を
一括検索できるライブラリを作りました!
スクレイピングを使ってるので、
リニューアルとかされると困りますw

Github→kin29/ticket-hunter

 

Packagistにも登録済みですので、

$ composer require kin29/ticket-hunter

からも取得可能でございます。

 

今回、Packagistへの登録を初めてしました!

こちらを参考にさせてもらいました。

https://qiita.com/niikunihiro/items/fbd696e506e734782d8f

6. GithubのサービスフックにPackagistを登録する

は、自動になったのか勝手に登録された気がしました。

 

 

使い方は、簡単です!

  • 使いたい販売サービス名(TicketPia/Eplus/LawsonTicketのどれか、複数指定可)
  • キーワード(「雨のパレード」など)

を渡すだけです。

<?php

try {
  $ticketVendors = new Kin29\TicketHunter\TicketHunter(['TicketPia', 'Eplus', 'LawsonTicket'], '雨のパレード');
  $ticketVendors->echoJson($ticketVendors->getList());
} catch (\Exception $e) {
    die($e->getMessage());
}

出力結果はこんな感じです。

{
  "TicketPia": [
    {
      "title": "GRAPEVINE/雨のパレード〔愛知〕",
      "date_time": "2019/8/2(金)",
      "pref_id": "23",
      "pref_name": "愛知県",
      "stage": "名古屋クラブクアトロ",
      "sale_method": "先行抽選",
      "sale_status": "近日抽選受付2019/6/8(土) 10:00 ~ 2019/6/10(月) 18:00",
      "link": "http://ticket.pia.jp/pia/ticketInformation.do?eventCd=1922500&rlsCd=&lotRlsCd=27947"
    },
    ...
  ],
  "Eplus": [
    {...省略...},
  ],
  "LawsonTicket": [
    {...省略...},
  ]
}

販売前の場合、販売開始日を取得できるといいな
と今気づきました、issueに入れておこう!
日付のフォーマットも入れてないな、、、入れよう。
アウトプットしてると気づくことて多いですよね、大切。

 

このライブラリを作っていた矢先、先を越されたかのように、
Freaxがリリースれました、、便利ですね。

内容が被っていますが、ライブラリはなさそうなので作り続けました^^;

 

今後は、このライブラリを使ってAPIを作りたいなとおもっています。
BearSundayでまた作りたいなあと思っております。
さらに、検索フォームをSymfonyで作ろうかと思っています。

 

怠けないように宣言しておきます!

では、森道市場2019行ってきます!

 

 

php-cs-fixerとphp_codesnifferの違いって?

 

サボり気味でした。。。
五月病ですね、気引き締めます。
ネタもちょっと尽きてきた感ありますが、頑張ります!

 

コード整形系のツール

  • php-cs-fixer
  • php_codesniffer

って大変お世話になるんですが、
名前が似すぎて同じものだと思っていました。
なのでちょっと違いを調べてみました。

 

おまけで、

  • phpmd/phpmd
  • phpstan/phpstan-shim

についても、書きました!

 

php-cs-fixer

[friendsofphp/php-cs-fixer]

.php_cs/.php_cs.dist(ルール設定ファイル)に沿って、
PHPコードの修正を提示してくれたり、修正を実行してくれる。

 

(例)配列の書き方はshortに統一することを宣言

.php_cs.dist

...
'array_syntax' => ['syntax' => 'short'],
...

実行結果

// コード修正の提示
$ vendor/bin/php-cs-fixer fix -v --dry-run
Loaded config default from "/Users/XXXX/ticket-hunter-api/.php_cs.dist".
Using cache file ".php_cs.cache".
SSSSSSSFSSSSS
Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error
1) src/test.php (array_syntax)

以下のように、書き直すことで S-Skipped となり解消されます。

-        $arrRet = array();
+        $arrRet = [];

 

 

ただし、実行結果の出力より対象ファイル名は分かりますが、
何行目かなどの詳細はわかりません。
そこで、修正の実行もしてもらう事もできます。「–dry-run」を外すだけです。

$ vendor/bin/php-cs-fixer fix -v --dry-run

 

 

さらに、.php_cs.distでルールを設定する際、
各ルールの具体例みたいなーって時は、以下コマンドで確認できます。

$ vendor/bin/php-cs-fixer describe [name]
$ vendor/bin/php-cs-fixer describe array_syntax
Description of array_syntax rule.
PHP arrays should be declared using the configured syntax.

Fixer is configurable using following option:
* syntax ('long', 'short'): whether to use the `long` or `short` array syntax; defaults to 'long'

Fixing examples:
 * Example #1. Fixing with the default configuration.
   ---------- begin diff ----------
   --- Original
   +++ New
   @@ -1,2 +1,2 @@
    <?php
   -[1,2];
   +array(1,2);

   ----------- end diff -----------

 * Example #2. Fixing with configuration: ['syntax' => 'short'].
   ---------- begin diff ----------
   --- Original
   +++ New
   @@ -1,2 +1,2 @@
    <?php
   -array(1,2);
   +[1,2];

   ----------- end diff -----------

 

 

 

php_codesniffer

[squizlabs/php_codesniffer]

phpcs(PHP Code Sniffer) …判定
phpcbf(PHP Code Beautifier and Fixer) …整形

既定のコーディング標準として用意されているもの(vendor/bin/phpcs -i)をベース
に判定/整形を行う。

phpcs.xml(設定ファイル)にexclude(除外)するものや追加ルールを記載し、カスタマイズすることが可能です。

 

//現在、デフォで適用されているコーディング規約の確認
$ vendor/bin/phpcs -i
The installed coding standards are PEAR, Zend, PSR2, MySource, Squiz, PSR1 and PSR12

 

例えば、既定のコーディング標準のままだとPEARはfunctionコメントが必須なので、
phpcs.xmlでexcludeする定義を書いたりしてカスタマイズすることができます。

phpcs.xml の例

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="PHP_CodeSniffer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="./vendor/squizlabs/php_codesniffer/phpcs.xsd">
 <rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/>
 <rule ref="PEAR.Commenting.FunctionComment">
 <exclude name="PEAR.Commenting.FunctionComment.MissingReturn"/>
 <exclude name="PEAR.Commenting.FunctionComment.MissingParamComment"/>
 <exclude name="PEAR.Commenting.FunctionComment.SpacingBeforeTags"/>
 <exclude name="PEAR.Commenting.FunctionComment.MissingParamTag"/>
 <exclude name="PEAR.Commenting.FunctionComment.Missing"/>
 <exclude name="PEAR.Commenting.FunctionComment.ParameterCommentsNotAligned"/>
 </rule>
</ruleset>

 

実行方法

$ vendor/bin/phpcs [適用コーディング(--standard=PSR2とか)] [対象ファイル名]
$ vendor/bin/phpcbf [適用コーディング(--standard=PSR2とか)] [対象ファイル名]

 

phpcs.xmlに沿って、src/以下を判定してもらう。

$ vendor/bin/phpcs --standard=phpcs.xml src

FILE: /Users/XXXX/YYY/src/test.php
----------------------------------------------------------------------
FOUND 0 ERRORS AND 2 WARNINGS AFFECTING 1 LINE
----------------------------------------------------------------------
 9 | WARNING | The method parameter $arrString is never used
 9 | WARNING | The method parameter $arrRet is never used
----------------------------------------------------------------------

Time: 57ms; Memory: 6MB

使われていないメソッドの引数があるよーって教えてくれました。

 

phpcbfでフォーマットしてもらう例は以下です。

$ vendor/bin/phpcbf --standard=PEAR src

PHPCBF RESULT SUMMARY
-----------------------------------------------------------------------------
FILE                                                         FIXED  REMAINING
-----------------------------------------------------------------------------
/Users/XXXX/YYY/src/test1.php                                 7      10
/Users/XXXX/YYY/src/test2.php                                 2      5
/Users/XXXX/YYY/src/test3.php                                 2      8
-----------------------------------------------------------------------------
A TOTAL OF 11 ERRORS WERE FIXED IN 3 FILES
-----------------------------------------------------------------------------

 

 

ついでに、、、

phpmd

[phpmd/phpmd]

phpmd.xml(ルール設定ファイル)に沿って、
「ここに使われてない変数あるよ」や、
「コンストラクタ関数が__constractじゃなくてclassnameで宣言されてるやつはダメよ」とか、「ここキャメルケースになってないよ」とかの注意をしてくれる。
若干、phpcsなどとルール設定がかぶる可能性はありそうです。

 

phpmd.xml の例

...
 <!--naming-->
 <rule ref="rulesets/naming.xml/ConstantNamingConventions"/>
 
 <!--unusedcode-->
 <rule ref="rulesets/unusedcode.xml/UnusedFormalParameter"/>
 <rule ref="rulesets/unusedcode.xml/UnusedLocalVariable"/>
 <rule ref="rulesets/unusedcode.xml/UnusedPrivateField"/>
 <rule ref="rulesets/unusedcode.xml/UnusedPrivateMethod"/>
 
 <!--controversial-->
 <rule ref="rulesets/controversial.xml/CamelCaseClassName"/>
 <rule ref="rulesets/controversial.xml/CamelCasePropertyName"/>
 <rule ref="rulesets/controversial.xml/CamelCaseMethodName"/>
...

 

実行方法

$ phpmd [file_path] [結果出力フォーマット(text/xml)] [ルール設定ファイル]
$ vendor/bin/phpmd src/TicketHunter.php text phpmd.xml

./Users/XXX/YYY/src/test.php:9 Avoid unused parameters such as '$arrString'
./Users/XXX/YYY/src/test.php:9 Avoid unused parameters such as '$arrRet'

 

 

phpstan

phpstan/phpstan-shim

静的解析ツールで、
– 構文エラーはないか?
– 関数に渡すパラメータの数が適切か?
– 未定義のものにアクセスしようとしていないか?
– 関数に渡す値が関数のパラメータの型宣言と一致するか?
– PHPDocの内容と関数の戻り値は同じか?
などをチェックしてくれます。

除外したいルールがあればphpstan.neonで設定のカスタマイズできるようです。

$ vendor/bin/phpstan analyse -l [レベル] [ファイルパス] -c phpstan.neon

レベルは0~7,maxがあり、レベルが高くなるにつれて厳しくチェックしてくれるとのことです。
またオプションで「–no-progress」を付与すると、進捗を表すバーを非表示にしてくれます。
その他詳細は $ vendor/bin/phpstan analyse -h で確認ができます。

 

$ vendor/bin/phpstan analyse -l max src tests -c phpstan.neon --no-progress
 ------ ----------------------------------
  Line   src/test1.php
 ------ ----------------------------------
  11     Undefined variable: $arrString
 ------ ----------------------------------


 [ERROR] Found 1 errors

参考:https://qiita.com/qiita_masaharu/items/24bf34579119628eefe2

 

 

 

まとめ

各ツールでルール設定が異なっている場合、
(変数名をphpcsではキャメルケース、phpmdではスネークケースを許容している等)
phpcsでは判定が通るけど、phpmdでは判定が通らない
ということが起きかねないなと感じました。
なので、ガチガチにルールを決めちゃうのも
手間がかかるし結構大変だなと思いました。

私はこちらをよく使わせてもらってます。
[Koriym.PhpSkeleton]

 

 

TemplateMethodパターン

どうも、今月ブログ更新をサボり気味で後悔してます。
「ブログ続けてるのすごい」って言われて嬉しかったので、
更新ちゃんと頑張ります。
遡ってみると2017年1月からブログを始めて早2年経ってました。
累計113記事でした。

\目標、1000記事/

今回は、TemplateMethodについてです。

 

TemplateMethodとは?

メソッドにおけるアルゴリズムの骨組みを定義し、
いくつかの手順をサブクラスに先送りします。

→ アルゴリズムとなる一連の手順(メソッド)を
定義するメソッド(テンプレート)があり、
その一連の手順の中のメソッドの1つ以上を
抽象メソッドとして定義し、
サブクラスがそれを実装する

 

クラス図

テンプレートメソッドのクラス図

 

コード

abstract class AbstractClass
{
    public function templateMethod(): void
    {
        $this->primitiveOperation1();
        $this->primitiveOperation2();
    }

    //それぞれ異なる手順→サブクラスにまかせる
    abstract public function primitiveOperation1();

    public function primitiveOperation2(): void
    {
        //共通の手順
    }
}
class ConcreteClass extends AbstractClass
{
    public function primitiveOperation1(): void
    {
        // TODO: Implement primitiveOperation1() method.
    }
}

 

いざ、実装!

Head Firstデザインパターンの具体例では、
コーヒーと紅茶の作り方がでてきました。

レシピ

▶︎コーヒーの作り方
1.お湯を沸かす
2.フィルターをセットして淹れる
3.コップにコーヒーを注ぐ
4.ミルクや砂糖を加える

▶︎紅茶の作り方
1.お湯を沸かす
2.ティパックをセットしてお湯をいれる
3.レモンをいれる
4.コップに注ぐ

 

ざっくりみると、コーヒーと紅茶の作り方は似ているので、
1~4の一連の手順をテンプレートメソッドとしてスーパークラスに定義し、
「1.お湯を沸かす」の部分は共通化できるので、スーパークラスで実装します。
コーヒーと紅茶で異なる部分はサブクラスで実装します。

実際のコードはこちら↓
[kin29/HeadFirstDezaPata]

テンプレートメソッド適用前と後のbefore-after

 

クラス図

カフェインでのテンプレートメソッド

 

コード

abstract class CaffeineBeverage
{
    public function prepareRecipe(): void
    {
        $this->boilWater();
        $this->brew();
        $this->pourInCup();
        $this->addCondiments();
    }

    abstract public function brew(): void;

    abstract public function addCondiments(): void;

    public function boilWater(): void
    {
        echo "お湯を沸かします\n";
    }

    public function pourInCup(): void
    {
        echo "カップに注ぎます\n";
    }
}

 

class Coffee extends CaffeineBeverage
{
    public function __toString(): string
    {
        return "--- コーヒーのレシピ ---\n";
    }

    public function brew(): void
    {
        echo "フィルターでコーヒをドリップします\n";
    }

    public function addCondiments(): void
    {
        echo "砂糖とミルクを追加します\n";
    }
}

class Tea extends CaffeineBeverage
{
    public function __toString(): string
    {
        return "--- 紅茶のレシピ ---\n";
    }

    public function brew(): void
    {
        echo "紅茶を浸します\n";
    }

    public function addCondiments(): void
    {
        echo "レモンを追加します\n";
    }
}
$ php src/TemplateMethod/main.php
--- コーヒーのレシピ ---
お湯を沸かします
フィルターでコーヒをドリップします
カップに注ぎます
砂糖とミルクを追加します
--- 紅茶のレシピ ---
お湯を沸かします
紅茶を浸します
カップに注ぎます
レモンを追加します

メリット

・共通メソッドの変更が必要となった場合、
スーパクラスの変更のみでいい。

・スーパクラスで骨組みを定義しているので、
新しいカフェインクラスの追加が必要になった場合(例えばココアとか?)、
実装がしやすい

 

 

令和もがんばろーっっと

Facadeパターン

 

どうも!四年ぶりくらいに風邪ひいて病院いきました。
病院の看護婦さんが母のように優しくてホッとしました。

 

\今回はFacadeパターンです!/

 

前回の最小知識の原則~PrincipleOfLeastKnowledge

でも出しましたが、Facadeパターンについてまとめます。

 

Facadeパターンとは…

サブシステムの一連のインターフェースに対する、
統合されたインターフェースを提供する。
ファサードは、サブシステムをより使いやすくする
高水準インターフェースを定義する。

 

クラス図

・Facade   …Subsystemをより使いやすく統合したインターフェース

・Client     …ファサードのおかげで作業が簡単になった幸福なクライアン

・Subsystem   …より複雑なサブシステムたち

 

具体的な例

コードはこちら!

 

いろんな電気機器(アンプ/DVDプレーヤー/ポップコーンマシーン)の一連オペレーションを統括したインターフェースをもつ、ホームシアター[Facade]を実装します。

 

・HomeTheaterFacade.php[Facade]
…Subsystemの中で、ホームシアターに必要な
一連のオペレーションを統一したインターフェース

・main.php[Client]
…ホームシアターを使うクライアント

・Subsystem/[Subsystem]
…アンプやDVDプレーヤ、ポップコーンマシーン、
スクリーン、ライトなどの電気機器クラス

 

映画を見るための電気機器の一連のオペレーションを
Facadeパターンで作って、実行した例です。

main.php

$homeTheater = new HomeTheaterFacade(
    $amp,
    $tuner,
    $dvd,
    $cd,
    $projector,
    $screen,
    $light,
    $pop
);
$homeTheater->watchMovie('アナ雪');
$ php src/Facade/main.php
▶︎ 映画を見る準備をします!
ボップコーンマシーンをONにします
ボップコーンを作ります
シアターライトを10にします
スクリーンを下げてセットします
プロジェクターをONにします
プロジェクターをワイドスクリーンモードにします
アンプをONにします
アンプにDVDプレーヤをセットします
アンプにサラウンドサウンドをセットします
アンプにボリュームを5にします
DVDプレーヤーをONにします
DVD「アナ雪」をスタートします

 

Facadeにいろいろ統括系は任せてるので…

Subsystem側は自分の役目に専念できる。
Client側に使いやすいインターフェースをFacadeが提供できるので、
Clientも使いやすい!

 

依存性の高いコードになってしまうこともある!

最小知識の原則~PrincipleOfLeastKnowledgeにも書いたように、
複数のオブジェクトのメソッドを呼び出すことが多くなるので、
ある程度の決められた範囲内のメソッドのみを呼び出さないと、
依存性の高いコードになってしまいます。

なので、以下に気をつけて実装するとマルっ!
1. そのオブジェクト自身のメソッド
2. メソッドの引数として渡されたオブジェクトのメソッド
3. メソッドが作成またはインスタンス化したオブジェクトのメソッド
4. そのオブジェクトのコンポーネント(インスタンス変数で参照されるオブジェクト)[HAS-A関係]のメソッド

 

最小知識の原則~PrincipleOfLeastKnowledge~

「最小知識の原則」についての説明は、
Facadeパターンの章で出てきました。

Facadeパターンは、
サブシステムの一連のインターフェースに対する
統合されたインターフェースを提供するものです。

したがって、Facadeパターンは
複数のオブジェクトのメソッドを呼び出すことが想定されます。
しかし、ある程度の決められた範囲内のメソッドのみを呼び出さないと、
依存性の高いコードになってしまいます。
それを防ぐための原則が
「最小知識の原則」となります。

 

最小知識の原則

「直接の友達とだけ話すこと」
オブジェクト間のやりとりを最小限にするべき。

→多数のクラスが互いに結合し、
システムのある部分に対する変更が他部分に
連鎖してしまうような設計を回避する。

※デメテルの法則 (Law of Demeter, LoD) ともいう。

 

 

以下に属するメソッドだけを呼び出すべき

1. そのオブジェクト自身のメソッド
2. メソッドの引数として渡されたオブジェクトのメソッド
3. メソッドが作成またはインスタンス化したオブジェクトのメソッド
4. そのオブジェクトのコンポーネント(インスタンス変数で参照されるオブジェクト)[HAS-A関係]のメソッド

 

<?php

class Car
{
    //4.このクラスのコンポーネント(HAS-A)の
    //メソッドである、$engine.start()はOK!
    public $engin;

    public function __construct(){}

    //2.メソッドの引数として渡されたオブジェクト$keyの
    //メソッドである$key.turns()はOK!
    public function start(Key $key): void
    {
             //3.メソッドが作成したオブジェクト$doorsの
        //メソッドである$door.lock()はOK
        $doors = new Doors();  
        
        $authorized = $key.turns();
        
        if($authorized){
            $engine.start();
             
               //1.オブジェクト自身のメソッド
                    //である、updateDashboardDisplay()はOK!
            this->updateDashboardDisplay(); 
       
            $door.lock();
        }

    }

    public function updateDashboardDisplay()
    {
        //
    }

}

デメリット

この原則を意識しすぎると、ラッパークラスの記述が多くなり、
複雑になってしまうこともある。

なので、適当に活用する!

Adapterパターン

 

今年から、花粉症になったみたいです・・・。
鼻がムズムズして頭も痛くなるし大変ですね、なめてました。

 

 

今回は、
Adapterパターン
について書きます!

 

Adapterパターン

クラスのインターフェースを、
クライントの期待する別のインターフェースに変換する。

アダプタは互換性がないインターフェースのために、
そのままでは連携ができないクラス(Adaptee)を
(Clientと)連携できるようにさせます。

ラップするインターフェースを変換します。

 

 

▶︎ Adapter
Targetインターフェースを実装する。

 

▶︎ Target <<interface>>
ClientはこのTargetインターフェースを使っている感覚。
実際には、Adapterを通して、Adapteeを使っているが、
Adapteeを使っている認識は特にない。

 

▶︎ Adaptee
全てのリクエストはAdapeeに委譲される。
Adapteeの任意のサブクラスにもAdapterは適用できる
(コンポジションのため)

 

▶︎ Client
①Targetインターフェースを使って、
Adapterのメソッドを呼び出し、
Adapterにリクエストをする。
②AdapterがAdapteeインターフェースを使って
Adapteeのメソッドに変換して呼び出す
③Clientはリクエスト結果を受け取るが、
Targetインターフェースに沿っただけなので
変換をしてくれたAdapterの存在は知らない。

 

PHPで書いたみたサンプルコード

 

オブジェクトアダプタ/クラスアダプタ

実は、Adapterパターンには、
– オブジェクトアダプタ
– クラスアダプタ
の2つがある。

 

オブジェクトアダプタ

コンポジションを使うので、
Adapteeとそのサブ(具象)クラスにも適用可能、多様性あり。

 

 

クラスアダプタ

(多重)継承を使うので、
1つの特定のアダプティに特化したアダプタが実装可能。

オーバライドができる。

 

 

 

いいところ

・Adapterパターンを使わずに、互換性対応のために、
Client側のインターフェースに対する全ての実装を変更する場合、
多くのコード変更やそれによる影響調査などコストが高い

(例)TurkeyインターフェースをDockインターフェースへの互換性対応のために、
Turkeyインターフェースを変更する場合

– Turket.php

interface Turkey
{
-    public function gobble(): void;
+    public function quack(): void;
     public function fly(): void;
}

 

– WildTurkey.php(Turkeyインターフェースを実装する具象クラス)
他にも具象クラスがあれば、
全ての具象クラスのgobbleメソッドの修正が必要となる。

class WildTurkey implements Turkey
{
-    public function gobble(): void
+    public function quack(): void
     {
         echo "ゴロゴロ\n";
     }
 }

 


↓ Adapterを使うと…

1つのクラス内にすべての変更をカプセル化したクラスを提供できる。
Client/Adaptee/TargetInterfaceのコード変更は不要。

 

 

双方向アダプタを作ることも可能

実際に作ってみた!しかし、テキトーすぎて見せたくない。。。

時間あったらちゃんとさせます。

 

– DockAndTurkeyAdapter.php

class DockAndTurkeyAdapter implements DockAndTurkey
{
    /* @var Turkey $turkey */
    public $turkey;

    /* @var Dock $dock */
    public $dock;

    public function __construct($turkeyOrdock)
    {
        if($turkeyOrdock instanceof Turkey)  $this->turkey = $turkeyOrdock;
        if($turkeyOrdock instanceof Dock)  $this->dock = $turkeyOrdock;
    }

    public function quack(): void
    {
        $this->turkey->gobble();
    }

    public function gobble(): void
    {
        $this->dock->quack();
    }

    public function fly(): void
    {
        if($this->turkey !== null){
            for($i = 0; $i < 5; $i++){
                $this->turkey->fly();
            }
        } else {
            $this->dock->fly();           
        }
    }
}

– DockAndTurkey.php
DockインターフェースとTurkeyインターフェースの拡張インターフェース

use HFD\Adapter\Dock\Dock;
use HFD\Adapter\Turkey\Turkey;

interface DockAndTurkey extends Dock,Turkey
{
}

 

interfaceのextendsって初めて使いました。
こうやって使うんですね\(^^)/