GitHubのgit://形式のURLは暗号化されていないのでサポートされなくなったらしい。

 

何かの手違いでローカルPCでbrew servicesが使えなくなった。(それ以外のbrewコマンドは使える状態)

原因は、git clone https://... が失敗するためだったぽい。https://でなくgit@github.com: <user-name>/<repository>.gitをURLに指定するとできた🎉🙌

そもそもgit clone https://できないのがよくなさそう😅

error文にもある通りhttps://github.blog/2021-09-01-improving-git-protocol-security-github/をみると、git://は暗号化されていないのでサポートされなくなったらしい。

$ brew services --help
==> Tapping homebrew/services
Cloning into '/usr/local/Homebrew/Library/Taps/homebrew/homebrew-services'...
fatal: remote error:
The unauthenticated git protocol on port 9418 is no longer supported.
Please see https://github.blog/2021-09-01-improving-git-protocol-security-github/ for more information.
Error: Failure while executing; `git clone https://github.com/Homebrew/homebrew-services /usr/local/Homebrew/Library/Taps/homebrew/homebrew-services --origin=origin --template=` exited with 128.
Error: Failure while executing; `/usr/local/bin/brew tap homebrew/services` exited with 1.


# homebrew-services がない!!!
$ ll /usr/local/Homebrew/Library/Taps/homebrew
total 0
drwxr-xr-x 5 kin29 admin 160 4 29 17:25 .
drwxr-xr-x 5 kin29 admin 160 4 22 16:56 ..
drwxr-xr-x 17 kin29 admin 544 2 16 12:43 homebrew-cask
drwxr-xr-x 17 kin29 admin 544 2 16 12:43 homebrew-core
 


# git clone https://..だと失敗したので git@github.comを使うようにした。
$ git clone git@github.com:Homebrew/homebrew-services.git /usr/local/Homebrew/Library/Taps/homebrew/homebrew-services --origin=origin --template=
Cloning into '/usr/local/Homebrew/Library/Taps/homebrew/homebrew-services'...
remote: Enumerating objects: 1985, done.
remote: Counting objects: 100% (494/494), done.
remote: Compressing objects: 100% (162/162), done.
remote: Total 1985 (delta 338), reused 434 (delta 324), pack-reused 1491
Receiving objects: 100% (1985/1985), 545.80 KiB | 751.00 KiB/s, done.
Resolving deltas: 100% (893/893), done.
 

$ ll /usr/local/Homebrew/Library/Taps/homebrew
total 0
drwxr-xr-x 5 kin29 admin 160 4 29 17:25 .
drwxr-xr-x 5 kin29 admin 160 4 22 16:56 ..
drwxr-xr-x 17 kin29 admin 544 2 16 12:43 homebrew-cask
drwxr-xr-x 17 kin29 admin 544 2 16 12:43 homebrew-core
drwxr-xr-x 13 kin29 admin 416 4 29 17:25 homebrew-services //入った!

# 使えるようになったーーー!!!
$ brew services --help
Usage: brew services [subcommand]

Manage background services with macOS' launchctl(1) daemon manager.

If sudo is passed, operate on /Library/LaunchDaemons (started at boot).
Otherwise, operate on ~/Library/LaunchAgents (started at login).

Symfony4をHerokuにデプロイしてみる

 

Symfony Advent Calendar 2019の14日目です。(間に合わすつもりでしたが、過ぎてしまいました…)

 

ほとんど、Herokuのドキュメント通りですが、 Apache設定に関して忘れがちなので同じ経験をしている方の助けになればと思います!

Githubにコミットしてるので、参考にどうぞ!
kin29/symfony4-heroku

 

使うもの

– Symfony4.4
– Heroku (Apacheを想定)

 

確認事項

Symfony4.4はPHP7.1.3以上が必要です。

$ php -v  //ローカルのPHPのバージョンを確認
PHP 7.1.30 (cli) (built: Jul 4 2019 21:55:42) ( NTS )

 

※ 今のHerokuのデフォルトPHPバージョンは7.3.12でした!

-----> PHP app detected
-----> Bootstrapping...
-----> Installing platform packages...
       - php (7.3.12)
       - apache (2.4.41)
       - nginx (1.16.1)

 

手順

1.composer経由でプロジェクトの作成

$ composer create-project symfony/website-skeleton symfony4-heroku/

ビルドインサーバーを立ててローカルで確認してみる。

$ cd symfony4-heroku/
$ php -S 127.0.0.1:8888 -t public/

welcomeページ見れましたー!

 

2.Herokuにデプロイしてみる

Githubにpushして、masterをdeployしてみます。

なんかエラーになって、deployできない。

...
     Script cache:clear returned with error code 255
       !!  Symfony\Component\ErrorHandler\Error\ClassNotFoundError {#29
       !!    #message: """
       !!      Attempted to load class "WebProfilerBundle" from namespace "Symfony\Bundle\WebProfilerBundle".\n
       !!      Did you forget a "use" statement for another namespace?
       !!      """
       !!    #code: 0
       !!    #file: "./src/Kernel.php"
       !!    #line: 23
       !!    trace: {
       !!      ./src/Kernel.php:23 {
       !!        App\Kernel->registerBundles(): iterable
       !!        › if ($envs[$this->environment] ?? $envs['all'] ?? false) {
       !!        ›     yield new $class();
       !!        › }
       !!      }
       !!      ./vendor/symfony/http-kernel/Kernel.php:449 { …}
       !!      ./vendor/symfony/http-kernel/Kernel.php:133 { …}
       !!      ./vendor/symfony/framework-bundle/Console/Application.php:169 { …}
       !!      ./vendor/symfony/framework-bundle/Console/Application.php:75 { …}
       !!      ./vendor/symfony/console/Application.php:148 { …}
       !!      ./bin/console:42 { …}
       !!    }
       !!  }
       !!  2019-12-14T12:26:54+00:00 [critical] Uncaught Error: Class 'Symfony\Bundle\WebProfilerBundle\WebProfilerBundle' not found
       !!  
       Script @auto-scripts was called via post-install-cmd
 !     WARNING: There was a class not found error in your code
 !     ERROR: Dependency installation failed!
 !     
 !     The 'composer install' process failed with an error. The cause
 !     may be the download or installation of packages, or a pre- or
 !     post-install hook (e.g. a 'post-install-cmd' item in 'scripts')
 !     in your 'composer.json'.
 !     
 !     Typical error cases are out-of-date or missing parts of code,
 !     timeouts when making external connections, or memory limits.
 !     
 !     Check the above error output closely to determine the cause of
 !     the problem, ensure the code you're pushing is functioning
 !     properly, and that all local changes are committed correctly.
 !     
 !     For more information on builds for PHP on Heroku, refer to
 !     https://devcenter.heroku.com/articles/php-support
 !     
 !     REMINDER: the following warnings were emitted during the build;
 !     check the details above, as they may be related to this error:
 !     - There was a class not found error in your code
 !     Push rejected, failed to compile PHP app.
 !     Push failed

 

herokuのdeployではcomposer require-devのほうは、インストールしてくれないそうなので、symfony/profiler-packがないよーって言っています。

横着ですが、
require-devのパッケージを、requireの方にも入れます。

       !!  Symfony\Component\ErrorHandler\Error\ClassNotFoundError {#29
       !!    #message: """
       !!      Attempted to load class "WebProfilerBundle" from namespace "Symfony\Bundle\WebProfilerBundle".\n
       !!      Did you forget a "use" statement for another namespace?
       !!      """

 

こっちも、composer.jsonのscript部分の消しちゃって逃げます💦

-    "scripts": {
-        "auto-scripts": {
-            "cache:clear": "symfony-cmd",
-            "assets:install %PUBLIC_DIR%": "symfony-cmd"
-        },
-        "post-install-cmd": [
-            "@auto-scripts"
-        ],
-        "post-update-cmd": [
-            "@auto-scripts"
-        ]
-    },

で、どうにかHerokuにデプロイできるようになりました😎

 

3.env=prodにする

heroku config:set APP_ENV=prod --app [herokuのapp名]

4.Apacheの設定 ←ここ大事💡

以下コマンドでProcfileを作成します。

$ echo 'web: heroku-php-apache2 public/' > Procfile

さらに、composer require apache-packを実行し、質問にはyesで答えます。
そうすると、.htaccessが追加されます。

git pushしてherokuにdelpoyされたら、ブラウザで確認してみます。
env=prodでは、welcomeページはないので404になります。

 

5.コントローラー作成

下記コマンドで、コントローラを作成することができます。

$ php bin/console make:controller HogeController

 created: src/Controller/HogeController.php
 created: templates/hoge/index.html.twig

           
  Success! 
           

 Next: Open your new controller class and add some pages!

deploy後にheroku bashで、routerを一応確認してみます。良さそう😏

~ $  bin/console debug:router
 ------ -------- -------- ------ -------
  Name   Method   Scheme   Host   Path
 ------ -------- -------- ------ -------
  hoge   ANY      ANY      ANY    /hoge
 ------ -------- -------- ------ -------

6.ブラウザチェック

作成した/hogeにアクセスしてみると、、、見れたー!!!!!🎉

つまづきポイント

deployできたはずなのに、herokuでsymfonyコマンド使ってみると、 最初のエラーが再び….
Attempted to load class “WebProfilerBundle” from namespace “Symfony\Bundle\WebProfilerBundle”.\n
Did you forget a “use” statement for another namespace?
heroku run bashして、サーバに入ってrm -rf vendor/からのcomposer installでどうにかsymfonyコマンドは動作するようになりました。

 

参考

https://devcenter.heroku.com/articles/deploying-symfony4
https://github.com/heroku/heroku-buildpack-php/issues/278#

 



npxで電子名刺つくってみた!

 

先週、Nagoya.php#17でLTをしていた方がnpxを使った電子名刺で自己紹介をしていて、

めちゃくちゃカッコよくて真似して作ってみました!

 

 

ターミナルで

$ npx kin29

を打つと以下スクショのように情報がでるようにしました!

追記(2022/09/10):使う機会がないので非公開&削除しました😅

business-card

 

 

仕組み

npx <user-name>を実行すると、

該当のnpmパッケージを落としてきて、package.jsonにある"main": "/bin/card.js"を実行する(テキストファイルをconsole.logする)とのこと。

package.json

{
...
  "main": "/bin/card.js",
  "bin": {
    "kin29": "./bin/card.js"
  },
...
  "scripts": {
    "prepublish": "npm run build",
    "build": "node build.js",
    "dev": "npm run build && node ./bin/card.js",
    "lint": "standard",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
...

"scripts"にあるように、build.jsを自分用に修正をして、
npm run buildすると、出力ファイルである./bin/outputができます。
npm run dev で、 npx * のシュミレーションができます。

bin/card.js

#!/usr/bin/env node
// 👆 Used to tell Node.js that this is a CLI tool

'use strict'

const fs = require('fs')
const path = require('path')
const output = fs.readFileSync(path.join(__dirname, 'output'), 'utf8')
console.log(output)  // bin/output に出力テキストがある

 

そもそも、npxとは?

公式サイト:https://www.npmjs.com/package/npx

npm5.2.0以上が導入されていれば、npxコマンドも使えるそうです。

指定されたnpmパッケージがローカルに存在しない場合には、インストールをしてコマンド実行するものだそうです。

npmと違うのは、たとえローカルにインストールされていなくてインストールすることになっても、一時的にしかインストールせず実行後は削除されるらしいです。

ローカルを汚さずにすみますね😄

 

npmとnpxを比較してみた💡

● npm

:~/test-npm$ ls //まだローカルには何もない

:~/test-npm$ npm install eslint
...
+ eslint@6.3.0
added 119 packages from 75 contributors and audited 176 packages in 5.246s
found 0 vulnerabilities

:~/test-npm$ ls   //ローカルにインストールされた!
node_modules package-lock.json

:~/test-npm$ node_modules/.bin/eslint --version
v6.3.0

●npx

:~/test-npx$ ls

:~/test-npx$ npx eslint --version
npx: 119個のパッケージを4.678秒でインストールしました。
v6.3.0

:~/test-npx$ ls //ローカルにインストールされてないよ^^

 

npmに公開する

npxを使った電子名刺を公開するためには、npmで公開する必要があります。

やりかた

  1. https://www.npmjs.com/ にアカウントを登録する。
  2. $ npm adduser でログイン(1の登録情報を使う)する。
  3. GitHubリポジトリを作成し、masterにpushする。
  4. $ npm publish でnpmに公開をする。
    // タグをつけてpushする。
    $ git tag -a v1.0.0
    $ git push origin tags/v1.0.0
    
    // npmに公開する。
    $ npm publish ./
  5. npx <user-name> を試して、確認する。

 

さいごに

composerにもnpxみたいな機能ないんかな?って思って、

「composer npx」でググったら、https://github.com/composer/composer/issues/7272

↑でなんか、あるよー的な雰囲気なことを書いてると私の英訳力では感じたのですが、本当にできるのかよくわからんです 😅

 

 

 

参考:

[Qiita]npm 5.2.0の新機能!「npx」でローカルパッケージを手軽に実行しよう

[Qiita]npxでnodeモジュールを実行する

[Qiita]初めてのnpm パッケージ公開

 

複数チケットサービス検索用ライブラリを作って、はじめての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行ってきます!

 

 

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って初めて使いました。
こうやって使うんですね\(^^)/

 

 

 

 

Commandパターン

 

どうも。
最近、蒙古タンメンにハマりすぎて困っています。
麻婆麺が辛すぎて病みつきになります。

 

 

Commandパターン
を見ていきます。

Commandパターンの定義

リクエスト(命令)をオブジェクトとしてカプセル化し、
その結果、他のオブジェクトを異なるリクエスト、キュー
またはログリクエストなどでパラメータ化でき、
アンドゥ可能な操作もサポートする。
= リクエストをオブジェクトでカプセル化する。
= クライアントはいろんなリクエストを持つことができる。(→使える)

 

クラス図

縦長。。。

 

・Command
…全てのコマンド(オブジェクト)のためのインターフェースを定義する。

 

・ConcreteCommand
…Receiverとaction()の結びつきを(execute()に)定義する。
Invokerがexecute()することで
ConcreteCommandへリクエストを行い、
ConcreteCommandがReceiverの
1つ以上のaction()を呼び出すことでそれを実行する。

 

・Invoker
…コマンド(オブジェクト)を保持し、
ある時点でのコマンドのexecute()を呼び出すことで
コマンド(オブジェクト)にリクエストを実行するよう依頼する。
様々なコマンド(オブジェクト)をパラメタ化して保持できるが、
具体的なコマンド(オブジェクト)の内容は知らない。
(Commandインターフェースを実装している何かだと知っている程度)

 

・Receiver
…制御されるもの(制御対象)

・Client

 

具体的に…

この章でのは
「複数の電化製品のon/offを制御できるリモコン」
でした。

 

PHPで書いたみたコード

 

・Command
…制御したい命令を定義するインターフェース

・LightOnCommand / LightOffCommand [ConcreteCommand]
…具体的に制御対象であるライトの
制御(点けたり消したりする)方法がここにある

・RemoteControl [Invoker]
…制御対象(Receiver)をsetCommandで設定したり、
実際に制御するコントローラー
複数の制御対象をセットできる。

・Light [Receiver]
…制御対象となるライト

・RemoteLoader [Client]

 

いいところ

Invoker(RemoteControl)自体が
制御するReceiver(Light)を知っている場合、
Receiverが増えるたびに、if文で追記する形になってしまう。

RemoteControl.php

  if($receiver == 'Light') {
   $receiver.on();

  } else if($receiver == 'Door') {

   $receiver.open();

- }
+ //制御対象「Sprinkler」の追加
+ } else if($receiver == 'Sprinkler') {
+
+  $receiver.waterOn();
+
+ }

 


↓ Commandパターンを使うと…

 

Invoker(RemoteControl)は
具体的なコマンドオブジェクトの内容を知らずして
(インターフェスは知ってる)、
コマンドオブジェクトに具体的な処理(制御)を依頼できるので、
Invoker(RemoteControl)とReceiver(Light)を分離することができる。
= 各々の役割分担ができる。

RemoteControl.php
※Receiver(LightやDoorなど)について全く出てこない。

use HFD\Command\Command\Command;
use HFD\Command\Command\NoCommand;

class RemoteControl
{
    /**
     * @var Command[]
     */
    public $onCommands;

    /**
     * @var Command[]
     */
    public $offCommands;


    public function __construct(int $commandCnt)
    {
        for ($i = 0; $i < $commandCnt; $i++) { $this->onCommands[] = new NoCommand();
            $this->offCommands[] = new NoCommand();
        }
    }

    public function setCommand(int $slotNum, Command $onCommand, Command $offCommand): void
    {
        $this->onCommands[$slotNum] = $onCommand;
        $this->offCommands[$slotNum] = $offCommand;
    }

    public function onButtonWasPushed(int $slotNum): void
    {
        $this->onCommands[$slotNum]->execute();
        $this->undoCommand = $this->onCommands[$slotNum];
    }

    public function offButtonWasPushed(int $slotNum): void
    {
        $this->offCommands[$slotNum]->execute();
        $this->undoCommand = $this->offCommands[$slotNum];
    }
}

 
制御対象が増える可能性がある時の
拡張性を考えた時に良さそうなパターンですね!
 

 

 

 

 

Factoryパターン〜AbstractFactory〜

Head FirstのFactoryパターンの
例え話が「ピザ屋」のお話だったので、
すんごいピザが食べたくなり、ピザホール一人で食べました。
やっぱりマルゲリータですよね。
んで、追いオリーブオイル。

 

Factoryパターン一族

– Simple Factory[正確にはFactoryパターンではない]
– Factory Method
– Abstract Factory

今回は最後のAbstract Factoryについてです!

 Abstract Factory

オブジェクトを作成をカプセル化し、依存度を下げる。(Factory Methodと同じ)
FactoryMethodと異なり、オブジェクトコンポジションを使います。

具象クラスを指定することなく、
一連の関連オブジェクトや依存オブジェクトを作成するための
インターフェースを提供する。

→ 使い手は、抽象インターフェースを使って、
実際に作成される具体的な製品を知ることなく、
一連の製品を作成できる。

いいところ:
使い手は、具体的な製品の詳細から完全に分離されます。
→ 役割分担できて、個々のクラスの責任が小さくなる

 

クラス図はこんな感じ

 

 

Head Firstでは、
– 工場:ピザの食材を作る食材工場
– 製品:ピザの食材
を例としていました。

こんな感じ(PHPStormのクラス図作れるの教えてもらったので、早速)

PizzaIngredientFactoryクラスで、
関連する一連の食材(Dough, Sauce, Cheese, …)オブジェクトを
まとめてグループ化して作れるのがいいところです。

 

AbstractFactoryのサンプルコード

 

▶︎ PizzaIngredientFactory  ←抽象的な食材工場

interface PizzaIngredientFactory
{
    //実装クラスにピザの食材(製品)の作成方法は任す。
    public function createDough(): DoughInterface;
    public function createSauce(): SauceInterface;
    public function createCheese(): CheeseInterface;
       ...
}

 

▶︎ NYPizzaIngredientFactory (PizzaIngredientFactoryの実装クラス)

↑具象な食材工場

class NYPizzaIngredientFactory implements PizzaIngredientFactory
{
    //具体的なピザの食材(オブジェクト)を作成する。
    public function createDough(): DoughInterface
    {
        return new ThinCrustDough();
    }

    public function createSauce(): SauceInterface
    {
        return new MarinaraSauce();
    }
...

 

▶︎ DoughInterface ←抽象的な食材

interface DoughInterface
{
    public function getName(): string;
}

 

▶︎ ThinCrustDough (DoughInterfaceの実装クラス)

↑具象な食材

class ThinCrustDough implements DoughInterface
{
    private $name;

    public function __construct()
    {
        $this->name = "うっすい生地";
    }

    public function getName(): string
    {
        return $this->name;
    }
}

 

 

ただし、食材が追加された時
Product(MeetInterface, Bacon)の追加だけでなく、
PizzaIngredientFactoryと
PizzaIngredientFactoryを実装する全てのクラスを変更する必要があります。
→影響範囲が大きめです。

例: 肉(食材)を追加する場合

– PizzaIngredientFactory

interface PizzaIngredientFactory
{
    //実装クラスにピザの食材(製品)の作成方法は任す。
    public function createDough(): DoughInterface;
    public function createSauce(): SauceInterface;
    public function createCheese(): CheeseInterface;
   ...
+   public function createMeet(): CheeseInterface;
}

– NYPizzaIngredientFactory

class NYPizzaIngredientFactory implements PizzaIngredientFactory
{
    ...
+   public function createMeet()
+   {
+       //MeetInterfaceの実装クラスをインスタンス化する
+       return new Bacon();
+   }
}

 

 

 

ある程度、固定された一連のオブジェクトがあって、
それらを作成する必要がある時に使えそうですね。