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

Mac+MAMPの開発環境にCakePHP3を導入しようとしてハマった話

CakePHP3使ってみるぞ!ってなり、composerで入れようとした。

$ php composer.phar create-project --prefer-dist cakephp/app appname

するとこんなエラーが。

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - cakephp/cakephp 3.0.x-dev requires ext-intl * -> the requested PHP extension intl is missing from your system.
      .
      .
      .

どうも、intlが入っていないらしい。intlを入れればいいのか、と思って調べてみたが、そんな簡単に入るものではないようである。

などと言っていると、この記事に出会った。

MAMPとComposerを使ったCakePHP3.xのローカル開発環境の構築 - Qiita

この記事自体を見ても特に問題は解決しなかったのだが、本文中にこんな一文が。

nts could not be resolved to an installable set of packages.」、「the requested PHP extension intl is missing from your system.」といったエラーメッセージが表示されるかもしれません。その場合はエラーに従って必要なモジュール等をインストールして下さい(ハマりポイントです)。

どうも、mac本体に入っているphpとMAMPのphpがあるようだ。実際に調べてみると、

$ which php
/usr/bin/php

プレインストールされているphpを使ってcomposerを動かしていたようだ。

なので、MAMPのphpを用いてみる。

$ /Applications/MAMP/bin/php/php7.2.10/bin/php composer.phar create-project --prefer-dist cakephp/app appname

実際これでうまくインストールが成功した。

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

Laravelにサービス層を導入する

はじめに

LaravelでWebサービスを開発していると、最初はいいのですが、Controillerが大量のロジックで埋め尽くされ見通しが悪くなっていきます。
それを改善する為にサービス層を導入します。

1.サービスクラスの作成

今回はappの直下にServiceディレクトリを作成し、PostServiceクラスを作成します。
例としてechohogeメソッドを作成します。

PostService.php
<?php
namespace App\Service;

use Illuminate\Support\Facades\DB;

class PostService
{
    public function echoHoge()
    {
        return 'hoge';
    } 
}

2.サービスコンテナへ登録する

まずはLaravelのartisanコマンドを使用し、サービスプロバイダを作成します。

php artisan make:provider PostServiceProvider
PostserviceProvider.php
<?php

namespace App\Providers;

use App\Service\PostService;
use Illuminate\Support\ServiceProvider;

class PostServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('Post',function($app){
            return new PostService();
        });
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

3.オートロード設定を行う

app.php
'providers' => [
    /*
   省略
    */
        App\Providers\PostServiceProvider::class,
    ],

4.依存性注入を行う

最後にコントローラーのコンストラクタで注入していきます。

PostController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Service\PostService;
use App\Post;

class PostController extends Controller
{

    protected $postservice;

    //投稿系のサービス層をDIする
    public function __construct(PostService $postservice)
    {
        $this->postservice = $postservice;
    }

    //一覧表示
    public function index()
    {
        //サービス層のメソッドを使用する
        return $this->postservice->echoHoge();
    }
}

以上です!!
これでサービス層としてビジネスロジックを書ける場所ができました!

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

laravel5.8でランダム文字列を生成するStr::random()

phpstormでlaravel5.8内でstr_random()を使ったらstr_random()みたいになったので、新しいメソッドを探した。

5.8系では、str_random()は、非推奨みたい。

use Illuminate\Support\Str;

$random_str = Str::random(100);

ちなみに5.8でstr_random()を呼ぶと内部でStr::random()が呼ばれるみたい。

vendor/laravel/framework/src/Illuminate/Support/helpers.phpより
if (! function_exists('str_random')) {
    /**
     * Generate a more truly "random" alpha-numeric string.
     *
     * @param  int  $length
     * @return string
     *
     * @throws \RuntimeException
     *
     * @deprecated Str::random() should be used directly instead. Will be removed in Laravel 6.0.
     */
    function str_random($length = 16)
    {
        return Str::random($length);
    }
}

Laravel 5.8 New Features List
*) Deprecated String and Array Helpers Functions

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

PHPのデータ型を詳しく学ぶ。スカラー型/複合型/特殊型の違いとは

独習PHP第3版を学んでいるYUUKIです。
あまりデータ型について深い仕組みを知らなかったのですが、独習PHPを実践してみて初めて目にする情報も多かったのでまとめます。

PHPで扱うデータ型について

  • PHPは変数でデータ型をあまり意識しない
  • Java,C#のような言語は変数に数字をセット出来ないなどの制限がある

PHPで扱うことが出来る主なデータ型

  • スカラー型 一つの変数に一つの値だけを扱うことができる
    • 論理型
    • 整数型
    • 浮動小数点型
    • 文字列型
  • 複合型 一つの変数に複数の値をまとめて扱える
    • 配列型
    • オブジェクト型
  • 特殊型 スカラー型にも複合型にも属さない
    • リソース型
    • ヌル型

リテラルとは

変数に格納できる値そのもの、または値をスクリプトの中で表現する方法のこと。

論理リテラル(bool)

  • 真(true)が偽(false)のいずれかの状態しか持たない
  • true、falseは大文字小文字を区別しない。(TRUE,FALSEでも通る)

PHPでFalseとみなす状況
- 空文字列、または文字列の0
- 整数リテラルの0
- 浮動小数点リテラルの0.0
- 要素数が0
- null(未定義)

例として、空文字("")の戻り値はfalse

整数リテラル(int)

  • 正数、負数、ゼロを表現できる
  • 2進数、8進数、16進数も表現できるが、先頭に接頭辞(0b,0,0x)を付ける必要がある

整数リテラルの分類

  • 10進数リテラル(0,3,-12)
  • 2進数リテラル(0b1111,0b101)→15,5
  • 8進数リテラル(0777,0644,0600)→511,384,255
  • 16進数リテラル(0xFF,0xAA00BB)→255,11176140

  • 進数表記に削ぐわない(2進数で121、8進数で191など)場合はエラーとなるので注意。

浮動小数点リテラル(float)

  • 指数表現を使った浮動小数点を使うことができる
  • 指数表現→「仮数部 e 符号部 指数部」の形式で表せるリテラルのこと
  • 例→3.548E5 = 3.458 * 10の二乗。 1.1414e-3 = 1.1414 * 10の-3乗を表す

浮動小数点リテラルも整数リテラルもPHPからしたら数値として変わらない為、どちらか好きな表現を選ぶ

文字列リテラル

文字列リテラルは文字列をシングルクォート''、ダブルクォート""のどちらかで囲んで使用する

①文字列の中にシングルクォート、ダブルクォートを使いたい場合

<?php
$str = "isn't it php"; ##ダブルクォーテーションで'を囲む
$sendMail = 'Hello, I'm "YUUKI"'; ##シングルクォーテーションで"を囲む
print $str."<br>";
print $sendMail;

//結果'がきちんと表示されている
isn't it php
Hello, I'm "YUUKI"

②""と''を両方含めたい場合→クォート文字をエスケープ処理する

  • エスケープ処理とは? 特殊文字を変換し、無効化すること

例えば、以下のように、''の中に、'と"を両方含んだコードだと構文エラーとなる

$str = 'Hello, I'm "Yuuki", You're Name? ';

なぜなら、''や""で一つの文字の塊とみなす為、間の文字が文字列として認識されない為。

今回の場合は、'を特別な意味を持たないただの文字列に変換します。

$str = 'Hello, I\'m "Yuuki", You\'re Name? '; //''の中にある'をエスケープ処理する

これでOK。
PHPではエスケープ処理を行いたいリテラルの前に、(バックスラッシュ)を使います。

シングルクォートとダブルクォートの違い

PHPのダブルクォーテーション("")とシングルクォーテーション('')の違いを理解する

エスケープシーケンスについて

「\文字」のように、改行やタブなど特殊な意味を持つ文字を「エスケープシーケンス」と呼ぶ。

PHPで主に使われるエスケープシーケンスは以下

  • \r キャリッジリターン
  • \n 改行
  • \t 水平タブ
  • \v 垂直タブ
  • \f フォームフィード
  • \$ ドル記号
  • \ バックスラッシュ
  • \" ダブルクォート
  • \nnn 3桁の8進数
  • \xnn 2桁の16進数
  • \U{nnnn} 4桁の16進数(Unicode文字 7.0から採用)

ヒアドキュメント

複数行含めて文字列をリテラルを表す仕組みのこと

  • 文字列リテラルの始まりと終わりを<<<EOD 中身 EOD; と表す
  • EODで括った部分は全て文字列リテラルとみなす
  • EODは開始と終了を表すためのデリミタ(区切り文字)
  • EOD以外にも、<<<CONTENT~CONTENT;や、<<<HOGEHOGE ~ HOGEHOGE;などがある
  • 開始/終了文字列は小文字で表記してもいいが、文字列リテラルと識別できるようにしておくため、大文字で表すのが一般的

<?php
$str = 'PHP とは Hypertext Preprocessorの略だよ';
$msg = <<<EOD
${str}。サーバーサイドで動作するスクリプト言語で、<br/>
DB連携やSMTP通信もできるので、フロントエンド言語に比べて汎用性が高いです。<br/>
'Let's start, everyone!'
EOD;
print $msg;

出力結果

スクリーンショット 2019-08-11 20.25.56.png

このように、エスケープせずとも、複数行を文字列リテラルにすることで、変数展開や``を気にせず使えます。便利ですね。
なお、EOD;の行に空白やタブ文字を含めるとエラーが起きるので注意。

ヌル(null)

最後にnull型。
ある変数が値を持たないことを意味し、唯一のリテラルとしてnullをもつ。

次の条件で、変数はnullであると見なされる。

  • 変数に値が代入されていない
  • 変数に明示的にnullが代入された場合
  • unset命令で変数の内容が破棄された場合

$nullStr; //値が代入されていない
$nullStr2 = null; //nullを明示的に代入している
$nullStr3 = "nullではない"; // 文字列リテラルを代入
unset($nullStr3);  //$nullStr3をアンセット
print $nullStr3; //空なのでnull(表示されない)

次回は、phpの配列についても詳しくみていきます。

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

Goutte(グート)を使ったLaravel スクレイピング

概要

以下の4点についてまとめます。
- Goutteの導入
- Goutteの設定
- データの取得方法
- 取得データの表示

Goutteの導入

作成中のLaravelフォルダに、cdコマンドで移動後、コンポーザーをダウンロード

コマンドライン
$ composer require weidner/goutte

Goutteの設定

configフォルダのapp.phpファイルに
Weidner\Goutte\GoutteServiceProvider::class,
'Goutte' => Weidner\Goutte\GoutteFacade::class,を追加

config/app.php
'providers' => [
    //省略

        /*
         * Package Service Providers...
         */
           //ここに一行追加----------------------------------//
           Weidner\Goutte\GoutteServiceProvider::class, 

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,

    //省略
]

'aliases' => [
    //省略

        'Event' => Illuminate\Support\Facades\Event::class,
        'File' => Illuminate\Support\Facades\File::class,
        'Gate' => Illuminate\Support\Facades\Gate::class,

        //ここに一行追加-------------------------------------//
        'Goutte' => Weidner\Goutte\GoutteFacade::class,

        'Hash' => Illuminate\Support\Facades\Hash::class,
        'Lang' => Illuminate\Support\Facades\Lang::class,
        'Log' => Illuminate\Support\Facades\Log::class,

    //省略
]

データの取得方法

スクレイピングしてきた情報を表示したいページのコントローラーに、以下の内容を記述

SampleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

//ここに一行追加-------------------------------------//
use Weidner\Goutte\GoutteFacade as GoutteFacade;

class SampleController extends Controller
{
    public function index()
    {
        // ここではアマゾンカメラランキングをスクレイピング
        $goutte = GoutteFacade::request('GET', 'https://www.amazon.co.jp/gp/bestsellers/electronics/3946818051?ref_=Oct_BSellerC_3946818051_SAll&pf_rd_p=7019a35e-d4ad-5da4-8fdd-f9f5c8ef9428&pf_rd_s=merchandised-search-10&pf_rd_t=101&pf_rd_i=3946818051&pf_rd_m=AN1VRQENFRJN5&pf_rd_r=61C7KYXHQFEAY80RGM67&pf_rd_r=61C7KYXHQFEAY80RGM67&pf_rd_p=7019a35e-d4ad-5da4-8fdd-f9f5c8ef9428');

        //画像を取得するための配列を準備
        $images = array();
        //テキストを取得するための配列を準備
        $texts = array();

        //画像のsrc部分を取得
        $goutte->filter('.a-dynamic-image')->each(function ($node) use (&$images) {
            $images[] = $node->attr('src');
        });

        //テキストを取得
        $goutte->filter('.p13n-sc-truncate')->each(function ($node) use (&$texts) {
            $texts[] = $node->text();
        });

        $params = [
            'images' => $images,
            'texts' => $texts,
        ];
        return view('index', $params);
    }
}

詳しく解説すると、

use Weidner\Goutte\GoutteFacade as GoutteFacade;

を追加し、

$goutte = GoutteFacade::request('GET', 'URL');

を記述する。URLの部分は、自分が情報を取得したいページのURLを記述する。
次に、

$sample = array();

を記述し、取得した情報を入れる配列を用意。$sample(変数)の部分は自分で決めて良い。
最後に、

$goutte->filter('.sample')->each(function ($node) use (&$sample) {
            $sample[] = $node->text();
        });

を記述し、情報を取得する。
.sample(クラス名)の部分は、自分が情報を取得したいページで、検証を行い、該当箇所のクラス名を取得してくる。

&$sampleと、$sampleの部分は、先ほど用意した配列の変数名を記述する。

取得データの表示

あとは簡単です。Viewに表示の記述を追加して終了です。

sample.blade.php
@for($i = 0; $i < count($texts); $i++)
  <p>{{ $texts[$i] }}</p>
@endfor

以上です。

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

脱PHP初心者!インターフェイスを理解しよう

PHP をある程度深くやってるとインターフェイス (interface) っていうのが出てきますよね。アレって何なのかご存知でしょうか?実はすごく頼れるやつなんですよ。アプリケーションの設計をするときはもちろん、いろいろなライブラリを組み込むときにもインターフェイスを使うことのメリットを知っていると知らないとではプロダクトの質に大きな違いが出てきます。

本稿では PHP のインターフェイスの使い方とメリットを物語形式で解説します。

本稿の対象読者

  • PHP のクラスは理解しているけどインターフェイスについてはよくわからない
  • PHP のインターフェイスの仕組みは理解しているけど使い所がわからない、あるいは何のためにあるのかわからない
  • PHP のインターフェイスは実際に使っているけどそのメリットについて上手く説明できない
  • イ、インターフェイス!?もももちろん知ってるぞ、インター・フェイスだろ?いんたーふぇいすぅ…

上記いずれかの悩みをもつ PHP プログラマーの方々

※クラスがまだわからない人はそちらを先に勉強しましょう。クラスについての解説はインターネットにわかりやすい記事がたくさんあると思います。

ある町工場のインターフェイス物語

あなたはおもちゃのロボットを製造しています

factory.png

あなたは小さな町工場の従業員です。この工場では毎日子供向けのおもちゃをいくつか製造しており、その中でも マシンガンパンチロボット は年頃の男の子に大変人気のある商品で、会社の主要な収益源となっています。

このロボットには Blue 社製の (充電可能な) バッテリーが内蔵されており、ロボットの製造には Blue 社からの電池の供給が不可欠となっています。

blue-battery.png

このロボットの設計は次のように書かれています。

class Robot
{
    // バッテリー
    protected $battery;

    // 蓄電量
    protected $storage = 0;

    public function __construct(BlueBattery $battery)
    {
        $this->battery = $battery;
    }

    // バッテリーを使って充電する
    public function charge(int $minutes)
    {
        $this->storage = $this->battery->charge($minutes);
    }

    // ためたパワーを使ってパンチする
    // 終わったら蓄電量は0になる
    public function punch()
    {
        for ($i = 0; $i < $this->storage; $i++) {
            echo 'オラ';
        }

        echo 'ァ!', PHP_EOL;

        $this->storage = 0;
    }
}

Blue 社のバッテリー仕様は下記のように書かれています。 charge() によって1分あたり1だけ充電されるということです。

class BlueBattery
{
    public function charge(int $minutes): int
    {
        return 1 * $minutes;
    }
}

これを動作させるコードは下記のようになります。

$battery = new BlueBattery;
$robot = new Robot($battery);
$robot->charge(10);
$robot->punch();
// オラオラオラオラオラオラオラオラオラオラァ!

動作イメージ

競合製品登場!

x10.png

人気ロボット製造であぐらをかいていたのも束の間、なんとライバル会社がパクリ製品をリリースしました。しかもただのパクリ製品ではなく、バッテリーを10倍強化したロボットではありませんか!またたく間にあなたの会社の製品の購買層はライバル会社に流れ、売上は一気に悪化しました。

このままではまずいと判断したあなたの元請け会社の社長が「相手が10倍ならこっちは100倍だ!」と鶴の一声を上げ、100倍の蓄電性能をもつ Green 社のバッテリーを使うように急遽指示されました。

green-battery.png

やれ現場は大混乱。これまで Blue 社のバッテリーが使われることを想定してロボット設計していたので、ロボットの設計を見直さなければなりません。問題となっている箇所はここです:

public function __construct(BlueBattery $battery)
{
    $this->battery = $battery;
}

これを Green 社のバッテリーに置き換える場合は下記のようになるでしょう:

public function __construct(GreenBattery $battery)
{//                         ^^^^^^^^^^^^ ココを書き換える!
    $this->battery = $battery;
}

幸いなことに GreenBatteryBlueBattery と同名のメソッド、同じ型の引数、同じ型の返り値を持っているのでそれ以外のところで Robot を修正する必要はありません。

GreenBattery
class GreenBattery
{
    public function charge(int $minutes): int
    {
        return 100 * $minutes; // 100倍性能 ⭐️
    }
}

しかし、 GreenBattery を搭載するようにするとコンストラクタを書き換えるので、今までの Robot の設計は使えません。今後も安価な BlueBattery を搭載したロボットは販売し続けたいので、新しくロボットを設計するしかないのでしょうか…?

バッテリーの規格を決めよう

interface.png

1週間悩んだ末、あなたは画期的なアイデアをひらめきました!バッテリーを規格化するのです。その規格に沿っていればどんなバッテリーもロボットに搭載できるようになります。 BlueBattery でも GreenBattery でも YellowBattery でも!そこで早速規格の仕様書を書きました。それこそがインターフェイスです:

interface Battery
{
    public function charge(int $minutes): int;
}

これは 「 Battery というインターフェイスを実装するのであれば、それは必ず charge(int): int というメソッドを持つ」という規約です。別の言い方をすれば、

Battery インターフェイスを実装しているクラスは charge(int): int を持っていることが保証されている

ということになります。インターフェイスに書かれるのは外部から呼ばれるメソッドの定義 (そのメソッドでは何が入力され、何が出力されるか) のみです (したがって public のみ宣言可) 。内部でどんな処理をするかは、インターフェイスを実装したクラスに委ねられます。

BlueBatteryGreenBattery はどちらもこのインターフェイスに従って実装されているので、下記のように書き換えられます。

class BlueBattery implements Battery
{
    public function charge(int $minutes): int
    {
        return 1 * $minutes;
    }
}
class GreenBattery implements Battery
{
    public function charge(int $minutes): int
    {
        return 100 * $minutes;
    }
}

新たに登場した implements Battery というキーワードは、 Battery インターフェイスを実装しているよ、という意味です。

Robot のコンストラクタ引数の型には Battery を書きます。これで Battery インターフェイスを実装したクラスのオブジェクトは何でも渡すことができるようになりますね!

public function __construct(Battery $battery)
{
    $this->battery = $battery;
}

battery.png

動作確認をしてみましょう:

// Blue 社バッテリーを搭載した旧ロボット
$blue_battery = new BlueBattery;
$blue_robot = new Robot($blue_battery);
$blue_robot->charge(10);
$blue_robot->punch();
// オラオラオラオラオラオラオラオラオラオラァ!

// Green 社バッテリーを搭載した新ロボット
$green_battery = new GreenBattery;
$green_robot = new Robot($green_battery);
$green_robot->charge(10);
$green_robot->punch();
// オラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラ (略)

ここまでの流れを見て、インターフェイスって便利だと思いましたか?え?インターフェイスなんて面倒な縛りはなくして、どんな型でも受け入れられるようにすればいいんじゃないかって?つまり:

public function __construct($battery) // どんなバッテリーもオッケー?
{
    $this->battery = $battery;
}

こうしろ、ということでしょうか?本当にそれで大丈夫なのでしょうか?次の章まで読み進めて考え直してみてください。

規格外のバッテリー!

あなたのおかげで、100倍性能のバッテリーをもつロボットを無事市場に流通させ、さらに多様なバッテリーに対応させることによって売上を回復させ、バッテリー業界の中でもそれなりの存在の会社となりました。

そんなところに新たなニュースが飛び込んできました。なんと Red 社がこれまでのバッテリーの常識を覆す大容量バッテリーを発表したのです!その容量はなんと Blue 社製の1000倍! (しかも比較的安価!) なんとしてもこのバッテリーを仕入れてロボットの性能をアップさせたいところです!

red-battery.png

しかしこの新しく発表されたバッテリーには致命的な問題がひとつありました。それは、 Battery インターフェイスの仕様に沿っていない、規格外バッテリーだったのです:

class RedBattery
{
    public function supercharge(int $minutes, int $max): int
    {
        return min(1000 * $minutes, $max);
    }
}

メソッド名は charge ではなく supercharge だし、何か引数も追加されています。 $max というのは蓄電量を制御できるオプションだそうです (いらない)。こんなトンデモバッテリーにあなたは怒りを通り越して呆れてしまいます。それでも何とかして対応しないと先にライバル社の製品がこれを搭載して盛り返してくるでしょう。

cannot-implement.png

インターフェイスの縛りをなくして、 supercharge() を使うようにしましょうか?もちろん、これまでのロボットとの互換性を保たなければならないので Robot::charge() メソッドには分岐処理が必要です:

class Robot
{
    // バッテリー
    protected $battery;

    // 蓄電量
    protected $storage = 0;

    public function __construct($battery) // どんなバッテリーも受け入れる
    {
        $this->battery = $battery;
    }

    // バッテリーを使って充電する
    public function charge(int $minutes): void
    {
        // ?Red 社のバッテリーは supercharge メソッドを使う
        if ($this->battery instanceof RedBattery) {
            $this->storage = $this->battery->supercharge($minutes, 10000);
            return;
        }

        // それ以外の仕様通りのバッテリーは charge メソッドを使う
        $this->storage = $this->battery->charge($minutes);
    }

    public function punch()
    {
        // 省略
    }
}

これはもうすでにヤバいコードになりつつあります。何がヤバいかと言うと、今後も規格外のバッテリーを受け入れるたびに条件分岐が増えていくということです。分岐が増えれば当然コードは読みづらくなり、分岐ごとに正しさを検証しなければならないのでメンテナンスコストがかかり、そして最悪バグを生み出す源になることでしょう。インターフェイスは各社のバッテリーへの依存を減らし、依存による複雑さを排除する仕組みでもあったのですね。

アダプターを挟め!

バッテリー業界の大御所である Red 社に対して田舎の町工場が仕様どおりのものを作ってくれと口を挟むことは不可能です。何とかしてこちら側で対応するしかないのです。

(ロボット製造の話から逸れますが) プログラミングの世界にはたくさんのライブラリがあり、たくさんの製品が外部のライブラリに依存しています。なので自分のところが独自に開発しているアプリケーションの仕様にだけ合わせてくれとライブラリの開発者に要望を送るのはおかしな話です。

あなたが工場の仕事から帰宅し、パソコンの前で悩んでいると、パソコンに繋がれている USB Type-C と USB Type-A の変換アダプターにふと目が行きました。「これだ!」とあなたはその場でひらめいた設計をパソコンで書き出し、次の朝上司に見せました。上司も納得し、 Battery を実装した RedBatteryAdapter を搭載することになりました:

class RedBatteryAdapter implements Battery
{
    public function __construct(int $max)
    {
        $this->max = $max;
        $this->battery = new RedBattery;
    }

    public function charge(int $minutes): int
    {
        return $this->battery->supercharge($minutes, $this->max);
    }
}

このように仕様が合致しないインターフェイスとクラスの中間にアダプタークラスをかませる (実際にはアダプタークラスに対象のクラスを内包させる) ことによって間接的にインターフェイスを実装できます。このテクニックはライブラリの抽象化でよく用いられる手法なので知っておいて損はないでしょう。

wrap.png

では、動作確認をしてみましょう:

// Red 社バッテリーを搭載した最強ロボット
$red_battery = new RedBatteryAdapter(10000);
$red_robot = new Robot($red_battery);
$red_robot->charge(10);
$red_robot->punch();
// オラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラ (長過ぎるので略)

この結果あなたの会社はさらに売上を大きく伸ばし、またあらゆるバッテリーに対応した製品を出せるということで業界では大きな存在となりました。

もしインターフェイスがなかったら、もしその上たくさんの種類のバッテリー規格に対応するとしたらどんな状況になっていたでしょうか?最後にそのことについて考えてみましょう。

インターフェイスは実装を教えてくれない

ここまでの完全なコードをまず載せておきます。仕様を統一させるため、 BlueBatteryGreenBattery もアダプターを通すようにし、 Battery インターフェイスは BatteryAdapter としました。

<?php

class Robot
{
    // バッテリー
    protected $battery;

    // 蓄電量
    protected $storage = 0;

    public function __construct(BatteryAdapter $battery)
    {
        $this->battery = $battery;
    }

    // バッテリーを使って充電する
    public function charge(int $minutes): void
    {
        $this->storage = $this->battery->charge($minutes);
    }

    // ためたパワーを使ってパンチする
    // 終わったら蓄電量は0になる
    public function punch()
    {
        for ($i = 0; $i < $this->storage; $i++) {
            echo 'オラ';
        }

        echo 'ァ!', PHP_EOL;

        $this->storage = 0;
    }
}

class BlueBattery
{
    public function charge(int $minutes): int
    {
        return 1 * $minutes;
    }
}

class BlueBatteryAdapter implements BatteryAdapter
{
    public function __construct()
    {
        $this->battery = new BlueBattery;
    }

    public function charge(int $minutes): int
    {
        return $this->battery->charge($minutes);
    }
}

class GreenBattery
{
    public function charge(int $minutes): int
    {
        return 100 * $minutes;
    }
}

class GreenBatteryAdapter implements BatteryAdapter
{
    public function __construct()
    {
        $this->battery = new GreenBattery;
    }

    public function charge(int $minutes): int
    {
        return $this->battery->charge($minutes);
    }
}

class RedBattery
{
    public function supercharge(int $minutes, int $max): int
    {
        return max(1000 * $minutes, $max);
    }
}

class RedBatteryAdapter implements BatteryAdapter
{
    public function __construct(int $max)
    {
        $this->max = $max;
        $this->battery = new RedBattery;
    }

    public function charge(int $minutes): int
    {
        return $this->battery->supercharge($minutes, $this->max);
    }
}

interface BatteryAdapter
{
    public function charge(int $minutes): int;
}

// Blue 社バッテリーを搭載した旧ロボット
$blue_battery = new BlueBatteryAdapter;
$robot = new Robot($blue_battery);
$robot->charge(10);
$robot->punch();
// オラオラオラオラオラオラオラオラオラオラァ!

// Green 社バッテリーを搭載した新ロボット
$green_battery = new GreenBatteryAdapter;
$green_robot = new Robot($green_battery);
$green_robot->charge(10);
$green_robot->punch();
// オラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラ (略)

// Red 社バッテリーを搭載した最強ロボット
$red_battery = new RedBatteryAdapter(10000);
$red_robot = new Robot($red_battery);
$red_robot->charge(10);
$red_robot->punch();
// オラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラ (略)

動作チェック

注目すべきは Robot クラスの charge メソッドです。

// バッテリーを使って充電する
public function charge(int $minutes): void
{
    $this->storage = $this->battery->charge($minutes);
}

この処理はバッテリーを使うので本来はバッテリーの処理に依存します。つまりバッテリーに変更があったら、 Robot に影響が出る可能性があります。しかしこのバッテリーはインターフェイスで抽象化されているため、その心配はありません。インターフェイスに実装は書かれていないのですから。Robot クラスはインターフェイスによって保護されているのです。Robot クラスは各バッテリーの実装を知ることはできないのでどんなにバッテリーの種類があっても責任が増えることはありません。プログラミングの設計の世界では関心事が減ることは美徳なのです。「無知は罪」ではなく「知らぬが仏」です。

そうなればアプリケーションのテストも単純になります。例えば極端な話、 Robot クラスが100種類のバッテリーに直接依存していたら最低でも100種類の結合テストケースを用意しないといけません。これはアプリケーションが大きくなればなるほど負債が増えていくことを意味します。しかしインターフェイスによってバッテリーに対する入力と出力の型が保証されているのであれば、バッテリーがいくら増えても、各バッテリーにどのような変更があっても、インターフェイスが変更されない限りは Robot に対するテストを変更する必要はないのです (ただし、各バッテリーの単体テストは必要) 。

まとめ

  • インターフェイスは規格の仕様書のようなもの
  • インターフェイスから実装の中身を知ることはできないが、それが最大のメリットである
  • クラスはインターフェイスを実装できる
  • インターフェイスを実装したクラスはそのインターフェイスの型として認められる
  • インターフェイスを実装したクラスから作られたオブジェクト (インスタンス) には、インターフェイスに定義されているとおりのメソッドをもつことが保証されている
  • インターフェイスを通じて別のクラスXにアクセスできるが、その場合クラスXに直接依存しないので、クラスXが変わってもアクセス元は影響を受けない (クラスXを監視する責任を負わない)

終わりに

インターフェイスは Java や Go などでも言語レベルで備わっている仕組みで PHP だけのものではありませんし、その魅力はここでは語りきれないほどまだたくさんあります。本稿を読んでみてなんとなくわかったら、自分が実際に書いているコードでも応用できそうなところがないか探してみてください。また Laravel のようなフレームワークを使っているとインターフェイスが出てくる場面も多いかと思うので、そういった有名どころのソースコードなども参考にしながらぜひ理解を深めてみてください!

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

Composer の "scripts" 単位で Timeout の無効化が設定できるようになって便利

2019年8月3日に、Composer1.9がリリースされていて、その中で Composer\Config::disableProcessTimeout ヘルパが便利そうであった。

Composer のコマンドの実行タイムアウトは通常 300秒

Composer では、通常コマンドのタイムアウトが300秒に設定されている。
このため、scripts に、開発用サーバ起動コマンドなどを設定しようものなら、5分で強制終了されてしまう。

scripts によるスクリプトの定義は、以下などを参照されたし

composer.json
{
    "scripts": {
        "server": "php -S localhost:8080 -t public/"
    }
}
$ composer server
> php -S localhost:8080 -t public/

-> 5分後、サーバが勝手に止まります。悲しい。

タイムアウト無効化・変更する技 (1.8以前)

以下の設定を0秒にすることで、無効化できます。下に行くほど優先度が高い (設定が上書きされる) です。

  • composer 設定の、process-timeout を設定する。
    • composer config process-timeout 0 にとすることで、composer.json にタイムアウトが無効化される設定が書き出される。ただし、当該プロジェクト全体で適用されてしまうので、都合が悪い場合があるし、ドキュメント等でその方法を示す必要がある。
  • COMPOSER_PROCESS_TIMEOUT 環境変数を設定する。
    • COMPOSER_PROCESS_TIMEOUT=0 composer server で起動することで、当該コマンドに限りタイムアウト設定を無効化できる。ドキュメント等でその方法を示す必要がある。
  • run-script--timeout オプションを設定する。
    • composer run-script --timeout 0 server で起動することで、当該コマンドに限りタイムアウト設定を無効化することができる。ドキュメント等でその方法を示す必要がある。

と、すべてドキュメント等に記載しないとワークしない方法の数々です。スクリプトの共有を楽にするはずが、これではよくありません。

Composer\Config::disableProcessTimeout ヘルパによる無効化

そこで、登場したヘルパはスクリプトとして記載することで、そのスクリプトに限りタイムアウト設定を無効化することができるスグレモノです。
このヘルパは、上記の設定等中で最優先されます。

composer.json
{
    "scripts": {
        "server": [
            "Composer\\Config::disableProcessTimeout",
            "php -S localhost:8080 -t public/"
        ]
    }
}
$ composer server
> Composer\Config::disableProcessTimeout
> php -S localhost:8080 -t public/

→ タイムアウトしなーい。

もちろん、Composer1.8以前だと以下のようなエラーが出るので、このエラーが出る場合は compser self-update を忘れずに。 (1敗)

Method Composer\Config::disableProcessTimeout is not callable, can not call server script

参考資料

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

Laravel 認証で$requestは合っているのに認証されなかった理由

Laravel5.7の認証で$requestの値は合っているのにLoginができなかった理由

マルチ認証でAdminのデータをseederファイルに書いてDBに入れた。

その時に'password' => 'passwordとそのままにしていたので値は合っているのにloginができなかった。

'password' => Hash::make('password')
Hash化しないといけないようでした:dancers:
もし同じように悩んでる人の手伝いになればウレシスです:relaxed:

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;

class BrandsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('brands')->insert([
            [
                'created_at' => date('Y-m-d H:i:s'),
                'brand_name' => 'Apuweiser-riche',
                'brand_name_kana' =>'アプワイザーリッシェ' ,
                'category' => 'feminine',
                'password' => Hash::make('password'),
            ],
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自力で検索機能を実装する

はじめに

ステップ1:検索入力ページをつくる

page-form.php
    <form method="get" action="<?php echo home_url('/'); ?>">

            <input name="s" type="text" id="searchtxt" placeholder="ここに検索語句を入力" >

            <input type="submit" value="検索">

    </form>

page-result.php

        <?php
        $searchwords = array(
            'exact' => true,
            'post_type' => 'foo',
            'meta_key' => 'day',
            'meta_value' => $_GET['s'],
        );
        $check_query = new WP_Query( $searchwords );


        if ( $check_query->have_posts() ) :
            /* Start the Loop */
            while ( $check_query->have_posts() ) :
                $check_query->the_post();


            endwhile; // End of the loop.


    else:

        echo 'お探しのページは見つかりませんでした。';

    endif;

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

下層にあるindexファイルからタイトルを抜き出してリスト化する関数

はじめに

深夜テンションに身を任せた結果謎のプログラムを錬成してしまったのでアップ。
テストサイトを一々探す手間は省けるかもね。

コード

test.php
function makelinklist(){

// 現在いるディレクトリのパスを取得
$serverroot = (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . $_SERVER["HTTP_HOST"];
$fileroot = dirname(__FILE__);

    // 下層ディレクトリ名をまとめておくリストを宣言
    $dirset = null;

    // 下層ディレクトリを取得する
    if ( $dirs1 = opendir($fileroot) ){
        while ( ($file = readdir($dirs1)) !== false ){
            // ディレクトリ以外のものを排除している
            if( $file != '.' && $file != '..' && $file != '.htaccess' && $file != 'index.php' && $file != 'index.html' ){
                $dirset[] = $file;
            }
        }
        closedir($dirs1);
    }

    // リンクのリストをつくる
    foreach($dirset as $va){

        echo '<p class="names">'.$va.'</p>';
        echo '<ul>';


        // 取得した下層ディレクトリのリストを利用して、すべてのindexファイルのパスを取得
        $index_pass = glob($fileroot.'/'.$va.'/index.*');

        // indexが存在するディレクトリのみ、さらにindexファイルのタイトルを取得する処理を実行
        if(!empty($index_pass)){
            if ($dirs2 = fopen($fileroot.'/'.$va.'/'.basename($index_pass[0]), 'r')){

                // 前もってaリンクに使うURLをとってくる
                $curpass = $serverroot.'/'.$va.'/'.basename($index_pass[0]);

                // タイトルタグを検索。範囲は~</head>まで
                while (!feof($dirs2)) {
                    $ch = null;
                    $line = fgets($dirs2);
                    // タイトルタグが見つかったなら、そのタイトルを本文としてリンクを作成
                    if (preg_match("/<title>(.*?)<\/title>/i", $line, $matches)) {
                        echo '<li><a href="'. $curpass . '">' . $matches[1] . '</a></li>';
                        $ch = 'a';
                        break;
                    }
                    // タイトルタグが見つからなかった場合、URLの末尾を本文にしてリンクを生成
                    if (preg_match("/<\/head>/i", $line)) {
                        echo '<li><a href="'. $curpass . '">' . basename($index_pass[0]) . '</a></li>';
                        $ch = 'a';
                        break;
                    }
                }
                // そもそも<head>~</head>自体がindex内に存在しなかった場合、URLの末尾を本文にしてリンクを生成。<head>~</head>が存在していた場合は上記のbreakを通っている=変数$chの中身がaになっているはずなので、この判定を通過することはない
                if($ch === null){
                    echo '<li><a href="'. $curpass . '">' . basename($index_pass[0]) . '</a></li>';
                }
                $ch = null;
                fclose($dirs2);
            }
        }
        // indexが存在しない場合、すべてのファイルに対して、URLの末尾を本文にしてリンクを生成
        else{
            $elsegroup = glob($fileroot.'/'.$va.'/*.*');
            foreach($elsegroup as $va2){
                echo '<li><a href="'. $serverroot.'/'.$va.'/'.basename($va2) . '">' . basename($va2) . '</a></li>';
            }
        }
        echo '</ul>';
    }

}

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

複数の宛先へのメール送信について(PHP)

はじめに

つい最近、はじめてお問い合わせフォームを作成したのですがそこでこれどうやるんだろうと悩んだのが複数の宛先にメールを送る方法です。

結構無理やりな形で作成してしまったのですが…記事にまとめておこうかと思います。

実装内容

お問い合わせフォームから送信ボタンを押す
 ・メールを送信したことを送信者に伝えるメールを送る
 ・メールをHP作成者の元に送る

この二通りのメールを送ることを想定して作成しました。

mb_send_mail関数を複数回使う

まずはmb_send_mail関数を用いて以下のようにして送信しました。

  <?php
    // 送信ボタンが押されたら
    if (isset($_POST["submit"])) {
        // 送信ボタンが押された時に動作する処理をここに記述する

        // 日本語をメールで送る場合のおまじない
        mb_language("ja");
        mb_internal_encoding("UTF-8");

            // 件名を変数subjectに格納
            $subject = "[自動送信]お問い合わせの確認";

            // メール本文を変数bodyに格納
            $body = <<< EOM
            {$name} 

            お問い合わせありがとうございます

           ===================================================
           お名前  
           {$name}
           ふりがな  
           {$furigana}

           メール  
           {$email}
           電話番号  
           {$tel}


           お問い合わせ内容  
           {$contentbox}
           ===================================================

           内容を確認のうえ回答させて頂きます
           しばらくお待ちください
           EOM;

           $subbody = <<< EOM
           {$name} 

           お問い合わせいただきました

           ===================================================
           お名前  
           {$name}
           ふりがな  
           {$furigana}

           メール  
           {$email}
           電話番号  
           {$tel}


           お問い合わせ内容  
           {$contentbox}
           ===================================================
           EOM;

             // 送信元のメールアドレスを変数fromEmailに格納
             $fromEmail = "HP作成側のメールアドレスを記述";

            // 送信元の名前を変数fromNameに格納
            $fromName = "HP作成側の名前";

        // ヘッダ情報を変数headerに格納する
        //
        $header = "From: " .mb_encode_mimeheader($fromName) ."<{$fromEmail}>";  
        //    
        $subheader = "From: " .mb_encode_mimeheader($name) ."<{$email}>";


        // 送信者へメール送信を行う
        mb_send_mail($email, $subject, $body, $header);
        // 受信者へメール送信を行う
        mb_send_mail($fromEmail, $subject, $subbody, $subheader);  
        ?>

このような形で送信元と受信元それぞれにmb_send_mail関数でメール送信することで、同時に異なった内容のメールを送信しました。

BCCを指定する場合

同じメール内容を受信者と送信者のそれぞれに送る場合であれば、以下のようにBCCを指定することで同時に複数の宛先へ送ることが可能になります。

  <?php
    // 送信ボタンが押されたら
    if (isset($_POST["submit"])) {
        // 送信ボタンが押された時に動作する処理をここに記述する

        // 日本語をメールで送る場合のおまじない
        mb_language("ja");
        mb_internal_encoding("UTF-8");

            // 件名を変数subjectに格納
            $subject = "[自動送信]お問い合わせの確認";

            // メール本文を変数bodyに格納
            $body = <<< EOM
            {$name} 

           お問い合わせありがとうございます

           ===================================================
           お名前  
           {$name}
           ふりがな  
           {$furigana}

           メール  
           {$email}
           電話番号  
           {$tel}


           お問い合わせ内容  
           {$contentbox}
           ===================================================

           内容を確認のうえ回答させて頂きます
           しばらくお待ちください
           EOM;

             // 送信元のメールアドレスを変数fromEmailに格納
             $fromEmail = "HP作成側のメールアドレスを記述";

            // 送信元の名前を変数fromNameに格納
            $fromName = "送信元の名前(HP作成側)";

        // ヘッダ情報を変数headerに格納する
        $headers = "From: " .mb_encode_mimeheader($fromName) ."<{$fromEmail}>";
        $headers = "From: 受信者のメールアドレス";
        $headers .= "\r\n";
        $headers .= "Content-type: text/html; charset=UTF-8";  


        //メール送信を行う
        mb_send_mail($email, $subject, $body, $headers);
?>

色々やり方はあると思うのですが、今回私はこのような形で複数の宛先にメール送信を行いました。

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

php-master-changes 2019-08-10

今日は一部組み込み関数の arginfo を PHP スタブで実装するようにする修正、gen_stub.php の修正があった!

2019-08-10

nikic: Add some basic_functions stubs

nikic: Expand preprocessor support in gen_stubs

stephenreay: Fallback to curl in gen_stub if wget fails

nikic: Handle preprocessor conditions inside classes

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

php-master-changes 2019-08-09

この日はテストの修正、循環参照 GC の実装修正、組み込み関数の arginfo を PHP コードのスタブファイルから生成するようにする修正、初期化されていない DateTime をエラー扱いとする修正、openssl_random_pseudo_bytes() の引数チェックバグの修正、mbstring で無効な文字がカウントされるようにする修正があった!

2019-08-09

mbeccati: Fix test when default TZ is not UTC

mbeccati: Skip test when SIGKILL is not defined

dstogov: Added asserts to catch GC errors when refcount goes below zero.

dstogov: Fixed second part of the bug #78379 (Cast to object confuses GC, causes crash)

nikic: Generate arginfo from PHP stub files

nikic: Make uninitialized DateTime an Error

nikic: Fixed bug #78391

nikic: Don't short-circuit MBFL_OUTPUTFILTER_ILLEGAL_MODE_NONE

dstogov: Fixed handling of references in nested data of objects with destructor

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