20190526のPHPに関する記事は11件です。

デザインパターンを学習しよう【1. Iteratorパターン-2】

株式会社オズビジョンのユッコ (@terra_yucco) です。
デザインパターンの 2 記事目の続きになります。実際のデザインパターンの説明を見ていきます。

教材

変わらずです。

Iterator パターンの前回はこちらの記事で。

1. Iteratorパターン

1.1 Itaratorパターンとは

1.2 サンプルケース

1.2a (実習課題1)

「Teacher」抽象クラスを継承する MyTeacher クラスと、「StudentList」クラスを拡張した MyStudentList クラスを設計しクラス図を作成しなさい。また、これらのクラスを実装し、 下記のMain クラスを実行しなさい。この2つのクラス以外のクラスやインタフェースを自由に追加しても問題ありません。 MyTeacher クラスの callStudents メソッドでは、あなたのクラスの生徒を順番に標準出力に出力するようにしなさい。

(略)

追加課題

このメソッドを利用して、 毎朝出席を取ることにも慣れてきました。ところが学校側から「名簿が古くなったので、 新しい形式のものに変えようと思います。新しい名簿は、java.util.Vector クラスを利用したものです。」 との通達があり、新しい名簿が渡されました。新しい名簿は、以下のように与えられます。

おっと java.util...。読み替えはできるのだろうか。

import java.util.Vector;

public class NewStudentList{
    protected Vector<Student> students;
    public void add(Student student){
        students.add(student);
    }
    public Student getStudentAd(int index){
        return students.get(index);
    }
}

Vector と思ったら、PHP にも実はあるらしいです。
PHP マニュアル > 関数リファレンス > その他の基本モジュール > Data Structures > The Vector class
ただし PECL なので今回は見送ります。

PHP で実装するならこんな感じでしょうか。
StudentList とあまり変わらない :expressionless: (今度 Vector 使ってみよう)
と、とりあえず、大事なのは、元と違うクラスになっている、ということです。

NewStudentList.php
class NewStudentList
{
    protected $students;

    /**
     * @param Student $student
     */
    public function add($student)
    {
        $this->students[] = $student;
    }

    /**
     * @param int $index
     * @return Student
     */
    public function getStudentAt($index)
    {
        return $this->students[$index];
    }
}

こういう風に、リストのクラスがどんどん変わってしまう場合に使うべきなのが Iterator パターン。

クラス図

クラス図は描けないので、教材からお借りしています。

image.png

Interfaces
Aggregate.php
<?php
interface Aggregate {
    public Iterator iterator();
}

PHP には組み込みで Iterator が存在していました。

※教材通りに進めるとそれと名前が重複してしまうため、クラス図の Iterator にあたる部分は MyIterator としています。

MyIterator.php
<?php
interface MyIterator {
  public function hasNext();
  public function next();
}
今回のケースに基づいて具体的な構成を検討

このクラス図も教材に載っていたものです。

image.png

上記の Interface を実装して、今回のケースに必要な ConcreteAggregate と ConcreteIterator を実装する必要がある。

  • ConcreteAggregate は StudentList (を拡張した MyStudentList) が該当
  • ConcreteIterator は今までの中に登場しないが、対応させたクラスを作る必要がある

1.2b 実習課題 2

前ページの最後のクラス図にあわせて、 MyStudentList クラスと、MyStudentListIterator クラスを実装しなさい。 ただし、MyStudentList クラスは、以前の古い名簿 StudentList を拡張したものにすること。

1.2c 実習課題 3

java.util.Vector クラスを利用した新しい名簿に変更後に必要となる、NewVeteranStudentList クラスと NewVeteranStudentListIterator クラスを実装し、クラス図を作成しなさい。また、これに伴い VeteranTeacher クラスを変更しなさい。

ああでもないこうでもないとやっていたら、新しく作った Interface 類を使って修正したコードで MyTeacher が仕事をできるようになったので、実習課題の 2 と 3 をまとめてやった形になりました。
課題と言いつつ途中まではヘルプも入るので、どこまでが課題かわかりにくい。

とりあえず、最終的なコードは以下のようになっています。

Main.php
<?php
require_once './StudentList.php';
require_once './Aggregate.php';
require_once './MyStudentIterator.php';
class MyStudentList extends StudentList implements Aggregate
{
    public function __construct()
    {
        parent::__construct();
    }

    public function iterator($list)
    {
        return new MyStudentIterator($list);
    }
}
[root@localhost ~]# cat Main.php
<?php
require_once './MyTeacher.php';

class Main
{
    public function work1()
    {
        $you = new MyTeacher();
        $you->createStudentList();
        $you->callStudents();
    }
}

$main = new Main();
$main->work1();
MyTeacher.php
<?php
require_once './Teacher.php';
require_once './Student.php';
require_once './MyStudentList.php';

class MyTeacher extends Teacher
{
    public function createStudentList()
    {
        $this->studentList = new MyStudentList();
        $this->studentList->addStudent(new Student('赤井亮太', '男'));
        $this->studentList->addStudent(new Student('赤羽里美', '女'));
        $this->studentList->addStudent(new Student('岡田美央', '女'));
        $this->studentList->addStudent(new Student('西森俊介', '男'));
        $this->studentList->addStudent(new Student('中ノ森玲菜', '女'));
    }

    public function callStudents()
    {
        $itr = $this->studentList->iterator($this->studentList);
        while ($itr->hasNext()) {
            $student = $itr->next();
            echo $student->getName() . $this->getTitle($student->getSex()) . PHP_EOL;
        }
    }

    private function getTitle($sex)
    {
        return $sex === '男' ? 'くん' : 'さん';
    }
}
Teacher.php
<?php
abstract class Teacher
{
    protected $studentList;

    abstract public function createStudentList();
    abstract public function callStudents();
}
MyIterator.php
<?php
interface MyIterator
{
  public function hasNext();
  public function next();
}
MyStudentIterator.php
{
    private $myStudentList;

    private $index;

    public function __construct($list)
    {
        $this->myStudentList = $list;
        $this->index = 0;
    }

    public function hasNext()
    {
        return ($this->myStudentList->getLastNum() > $this->index);
    }

    public function next()
    {
        $student = $this->myStudentList->getStudentAt($this->index);
        $this->index++;
        return $student;
    }
}
Aggregate.php
<?php
interface Aggregate {
    public function iterator($list);
}
MyStudentList.php
<?php
require_once './StudentList.php';
require_once './Aggregate.php';
require_once './MyStudentIterator.php';
class MyStudentList extends StudentList implements Aggregate
{
    public function __construct()
    {
        parent::__construct();
    }

    public function iterator($list)
    {
        return new MyStudentIterator($list);
    }
}
StudentList.php
<?php
class StudentList
{
    /**
     * @var array
     */
    protected $students;

    /**
     * @var int
     */
    private $last = 0;

    /**
     * @param int
     */
    public function __construct() {
        $this->students = array();
    }

    /**
     * @param Student $student
     */
    public function addStudent($student)
    {
        $this->students[$this->last] = $student;
        $this->last++;
    }

    /**
     * @param int $index
     * @return Student
     */
    public function getStudentAt($index)
    {
        return $this->students[$index];
    }

    /**
     * @return int
     */
    public function getLastNum()
    {
        return $this->last;
    }
}
Student.php
<?php
class Student
{
    /**
     * @var string
     */
    private $name;

    /**
     * @var int
     */
    private $sex; // 0:男, 1:女

    /**
     * @param string $name
     * @param int    $sex
     */
    public function __construct($name, $sex)
    {
        $this->name = $name;
        $this->sex = $sex;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return int
     */
    public function getSex()
    {
        return $this->sex;
    }
}

Conclusion

サイトの内容自体をまだ理解できていないところもあり、本当にこれで Iterator になっているのはまだ自信がありません。次回、このコードを元に、集約体が変わっても大丈夫かを確認するような取り組みをして、自分の Iterator パターンの学習を一度終わろうと思います。

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

「null」ってなんだ?

はじめに

私は、大学4回生です。
現在、未来電子テクノロジーでプログラミング開発コースでもうプログラミングを勉強しています。
今回は、データ構造について勉強していた時に調べたことを、初心者目線でお伝えします。

プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。

「null」とは

「null」は、簡単に言うと、「何も示さない」や「何も登録されていない」という意味を持ちます。
勘違いしやすい意味は、「何も入っていない」です。
正確に言うと、データ構造の中で、何も登録されていない項目の初期値に「null」が入っています。
感覚として理解し難いですが、「null」は、「必要ならば、値を入れてね」と行っているような気がします。

「null」の問題点

何も示さないことを言う「null」は、便利な気がしますが、現代では問題視されています。
それは、「null」を使っているのか、「null」がミスで残ってしまい、エラーが起きるのかわからないという問題です。
また、「null」によって、ソースコードを破壊してしまう可能性もあるそうです。

まとめ

今回は、詳しい内容まではお話ししませんでしたが、「null」の使用説明書の前書きほどの説明を行いました。
これから「null」を使う頻度を増やし、理解を深めていきます。

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

個人的に一番良いと思うHTTPエラーページ

WEBページにアクセスするとHTTPステータスコードが返されます。

100番台 200番台 300番台 400番台 500番台
情報 成功 リダイレクション クライアントエラー サーバーエラー

基本的には400番台,500番台が返された時にエラーページは表示されます。
それらのページをそれぞれの意味をもとに個人的に最も良いデザインを考えました。
コピぺで実装できるので是非お試しください。

1, .htaccess

まずはこれがないと始まりません。
今回はルートディレクトリにあるerrorフォルダの中にファイルを入れていきます。
ルートディレクトリに.htaccessという名前のファイルを作成して中に次のように書いてください。

ErrorDocument 401 /error/401.php
ErrorDocument 403 /error/403.php
ErrorDocument 404 /error/404.php
ErrorDocument 500 /error/500.php
ErrorDocument 503 /error/503.php

2, エラーページ本体

次にerrorフォルダを作成しその中に401.php,403.php,404.php,500.php,503.phpの5つのファイルを作成してください。
それぞれに以下のファイルをコピペしてください。でも一箇所だけそれぞれの最後の方にある「ここにメールアドレス」はきちんと自分のメールアドレスを描いてください。

401.php

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<title>HTTPエラー 401 / HTTP ERROR 401</title>
</head>
<body>
<span style="font-size:1.5em;">HTTPエラー 401<br>HTTP ERROR 401</span><br>認証に失敗しました<br>Unauthorized<br><?php
echo "アクセスしようとしたURL : ";
echo (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . 
$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
echo "Requested URL : ";
echo (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . 
$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
?><br>管理者にご連絡頂ければ幸いです。メールアドレス:<a href="mailto:ここにメールアドレス">ここにメールアドレス</a><br>Please contact the administrator. Mail address : <a href="mailto:ここにメールアドレス">ここにメールアドレス</a>
</body>
</html>

403.php

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<title>HTTPエラー 403 / HTTP ERROR 403</title>
</head>
<body>
<span style="font-size:1.5em;">HTTPエラー 403<br>HTTP ERROR 403</span><br>アクセスが禁止されています<br>Forbidden<br><?php
echo "アクセスしようとしたURL : ";
echo (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . 
$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
echo "Requested URL : ";
echo (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . 
$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
?><br>管理者にご連絡頂ければ幸いです。メールアドレス:<a href="mailto:ここにメールアドレス">ここにメールアドレス</a><br>Please contact the administrator. Mail address : <a href="mailto:ここにメールアドレス">ここにメールアドレス</a>
</body>
</html>

404.php

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<title>HTTPエラー 404 / HTTP ERROR 404</title>
</head>
<body>
<span style="font-size:1.5em;">HTTPエラー 404<br>HTTP ERROR 404</span><br>ページが存在しません<br>Not Found<br><?php
echo "アクセスしようとしたURL : ";
echo (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . 
$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
echo "Requested URL : ";
echo (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . 
$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
?><br>管理者にご連絡頂ければ幸いです。メールアドレス:<a href="mailto:ここにメールアドレス">ここにメールアドレス</a><br>Please contact the administrator. Mail address : <a href="mailto:ここにメールアドレス">ここにメールアドレス</a>
</body>
</html>

500.php

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<title>HTTPエラー 500 / HTTP ERROR 500</title>
</head>
<body>
<span style="font-size:1.5em;">HTTPエラー 500<br>HTTP ERROR 500</span><br>サーバー内部でエラーが発生しました<br>Internal Server Error<br><?php
echo "アクセスしようとしたURL : ";
echo (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . 
$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
echo "Requested URL : ";
echo (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . 
$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
?><br>管理者にご連絡頂ければ幸いです。メールアドレス:<a href="mailto:ここにメールアドレス">ここにメールアドレス</a><br>Please contact the administrator. Mail address : <a href="mailto:ここにメールアドレス">ここにメールアドレス</a>
</body>
</html>

503.php

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<title>HTTPエラー 503 / HTTP ERROR 503</title>
</head>
<body>
<span style="font-size:1.5em;">HTTPエラー 503<br>HTTP ERROR 503</span><br>アクセスの集中によりサービスが利用できません<br>Service Unavailable<br><?php
echo "アクセスしようとしたURL : ";
echo (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . 
$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
echo "Requested URL : ";
echo (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . 
$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
?><br>管理者にご連絡頂ければ幸いです。メールアドレス:<a href="mailto:ここにメールアドレス">ここにメールアドレス</a><br>Please contact the administrator. Mail address : <a href="mailto:ここにメールアドレス">ここにメールアドレス</a>
</body>
</html>

3,まとめ

要は「管理者に連絡が取れる手段を何かしら用意しよう」「現在どこのページにアクセスしているのかわかるようにしよう」ってことです。
間違ってたりうまくできなかったらコメントまでご連絡ください。

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

Laravel で Service 層を取り入れるときに検討したいこと

この記事について

普段何気なく使っている Service クラス(Service 層)について、書籍を中心にその役割や目的について書かれた資料を読みながら、Laravel 製のウェブアプリケーションに Service 層を取り入れる際の判断材料となるような情報や観点を、提供できればと思います。

以前にも調べたり考えたりしたんですが、そのときは明確な役割や使い方の提案ができなかったので、それの補遺的な位置づけになります。

Laravelでウェブアプリケーションをつくるときのベストプラクティスを探る (9) Service編 - Qiita

Service レイヤーとは

これ

出典: Martin Fowler's Bliki
https://martinfowler.com/eaaCatalog/serviceLayer.html

どんなときに Service 層が必要になるか

Despite their different purposes, these interfaces often need common interactions with the application to access and manipulate its data and invoke its business logic.

そういった異なる目的(訳者注: 上図の Data Loaders、User Interfaces、Integration Gateway の目的)にかかわらず、これらのインタフェースには、データを操作したりビジネスロジックを実行し対するために、アプリケーションとの共通のやりとりが必要になるときがある。

ようするに、UI からだけでなく、CUIのプログラムとか他システムとの連携部分とかで、共通のロジックを呼ぶ必要があるときに、Service レイヤー越しにドメインロジックを処理すると、共通のインタフェースで操作できるようになるよね、ということと理解できます。

たとえば、

// via Web User Interface
class SomeController extends Controller {
    public function someAction(Request $request) {
        $this->someService->doSomething($request->all());
    }
}

// via Command
class SomeCommand extends Command {
    public function handle() {
        $this->someService->doSomething($this->options());
    }
}

// via Another Service
class AnotherService {
    public function doSomething() {
        $this->someService->doSomething($params);
    }
}

このように処理をラップすることで、上記3つのクライアントがそれぞれ、引数の構築だけを担って、実際の処理を Service クラスに移譲することができるようになります。

Service クラスとは

Service には2種類ある

  • Application Service: Application 層にある Service(何の説明にもなってないですが、後述します)
  • Domain Service: Domain 層にある Service(何の(ry

Domain Service の定義

Domain といえば「ドメイン駆動設計」ですね。

「ドメイン駆動設計」では、巻末で以下のように定義されています:

SERVICE An operation offered as an interface that stands alone in the model, with no encapsulated state.

Evans, Eric. Domain-Driven Design

「サービス
モデルの中で独立したインタフェースとして提供される操作、カプセル化された状態を持たない。」

ちょっとこれだけだとよく分からないので、もう少し詳細な説明を探してみます。

When a significant process or transformation in the domain is not a natural responsibility of an ENTITY or VALUE OBJECT, add an operation to the model as a standalone interface declared as a SERVICE.

「ドメインにおける重要な処理や変換が、エンティティや値オブジェクトの自然な責務ではない場合、サービスとして宣言された独立したインタフェースとしての操作をモデルに追加してください。」

さらに、以下のような記述もあります。

Unlike ENTITIES and VALUE OBJECTS, it is defined purely in terms of what it can do for a client.

「エンティティや値オブジェクトとは異なり、純粋に、クライアントに対してなにができるか、という観点でのみ定義されます。」

ドメインとはなにか、というのは下記の記事も参照してください。

ドメインモデル、ドメインロジックとは何かをコードを交えて考えてみる - Qiita

Application Service の定義

It can be harder to distinguish application SERVICES from domain SERVICES. The application layer is responsible for ordering the notification.

Evans, Eric. Domain-Driven Design

「Application Service と Domain Service を区別するのはもう少し難しいかもしれない。Application 層の責務は通知を命令することである。」

この文の前段で、銀行のシステムで口座の残高がある閾値を超えた場合にメールを送信する、というような例が紹介されていて、閾値のチェックは Domain 層、メールの送信を命令するのは Application 層、メールを送信するのは Infrastructure 層、という切り分けであると述べています。

また他の箇所では、

layer. For example, if the banking application can convert and export our transactions into a spreadsheet file for us to analyze, that export is an application SERVICE.

Evans, Eric. Domain-Driven Design

Spreadsheet へのエクスポート機能も Application Service と言っています。

明確な定義はないものの、Infrastructure 層(メールなどの通知系、ファイルやデータベースの操作、など)へ命令を下すのが Application Service である、と言えそうです。

よい Service の特徴

  1. The operation relates to a domain concept that is not a natural part of an ENTITY or VALUE OBJECT.
  2. The interface is defined in terms of other elements of the domain model.
  3. The operation is stateless.

Evans, Eric. Domain-Driven Design

  1. その操作が、エンティティや値オブジェクトの自然な一部でないようなドメインの概念に関連したものである
  2. そのインタフェースが、ドメインモデルの他の要素の観点から定義されている
  3. その操作が、状態を持たない

Laravel における Service

Service Providers のセクションに出てきます。

Your own application, as well as all of Laravel's core services are bootstrapped via service providers.

https://laravel.com/docs/5.8/providers

あまり深くは見ませんが、config/app.php で providers に登録されている様々な Service Provider の中身を見てみると、命名だったり、初期化の仕方だったり、を参考にできるかもしれません(ただし、後述しますが、アプリケーションで Service をつくるときは、必ずしもサービスコンテナに登録する必要はないと考えています)。

Facade は一種の Service である(と言えるのではないか)

そうして見てみると、いくつかの Facade が Service Provider で登録されていることが分かりました。

そうでない Facade についても、たとえば Log の場合、

  1. ウェブアプリケーションフレームワークというドメインにおいて、ログ操作というのは、どのモデルにも属さない独立した操作である
  2. とはいえ、デバッグ、エラー、といった、アプリケーションの状態に関わるインタフェースがある
  3. 状態を持たない

といった特徴があり、よい Service クラスの特徴に合致しています。

「Facade は一種の Service である」と考えると、どういう機能を Service 化するのがいいのか、のヒントになるような気がします。

Application 層と Domain 層の境界

  • Application 層
    • UI
    • Request(アプリケーション外部からの入力パラメータ)
    • Job/Event(アプリケーションの外部に対する通知やコマンド実行)
    • Response(アプリケーション外部への出力パラメータ)
    • Command(php artisan CLI で実行されるコマンド)
    • app/Http, app/Console, Exceptions, Events, Listeners, Jobs, Policies, Providers
  • Domain 層
    • Laravel に依存しない部分、DB に依存しない部分
    • 厳密には上記の定義だが、Eloquent Model (Active Record) はドメインロジックを書く一方で、中で DB に依存した処理が含まれるものもあり、境界はあいまい
    • Laravel ではどこに配置するかは決まっていない
    • app 以下じゃなくてもいい
  • Service 層
    • Application 層と Domain 層の間に位置し、Application Service はアプリケーション層に、Domain Service は Domain 層に配置する
  • Infrastructure 層
    • DB
    • Mail
    • Notification (mail, slack, etc.)
    • Queue (database, redis, etc.)

Application Service の例

例1: Excel への Export

なんらかのデータを整形して Excel 形式のファイルに出力してダウンロードするというのは、Service 化がもっとも適した例だと思います。

$activeUsers = User::active()->get();
// 出力形式の他にテンプレートファイルを渡すのもいいかもしれません
$exporter = Factory::factory(ExportType::Excel);
// ファイルに保存するなら
$exporter->toFile($activeUsers, $dir, $name);
// Response として返すなら
$exporter->toStream($activeUsers);

例2: 複数のエンティティを一度に更新するようなケース

たとえば UI の都合で、複数のエンティティを一度に更新するといったケースでは、Controller から直接操作するよりも、Service を挟んだほうがいい気がします(UI が頻繁に変わる場合でも、Controller に影響が及ばないようにできます)。

エンティティを個別に操作するパターン

class SomeController {
    public function update(UpdateRequest $request) {
        $someModel->update($request->someAttributes());
        $otherModel->update($request->otherAttributes());
    }
}

Serivce を介して更新するパターン

class SomeController {
    public function update(UpdateRequest $request) {
        $this->updateSomeServce->update($request->validated());
    }
}

Domain Service の例

例1: ECサイトにおける Recommendation

Recommend されるのは「商品」だが、「商品」に「レコメンド」の責務を持たせるのは不自然かな、と思ったら、Service クラスに切り出すことができるでしょう。

「おすすめ」インタフェースに対して複数の実装を持たせ、内部のアルゴリズムをインタフェース越しに差し替えることもできます。

class RecommendController {
    public function __invoke() {
        $user = Auth::user();
        return $this->recommender->recommendTo($user); // Userに合わせたレコメンド
    }
}

例2: ホテルの予約サービスにおける Reservation

「予約」というエンティティがあるにはあるが、「予約」という振る舞いに対して、内部で複数のエンティティを連動して操作しなければいけないときに、Service クラスに切り出すことができるでしょう。

ロジックが巨大で複雑になるようなら、さらに「空き状況確認サービス」などといった処理ごとに別の Service に分解してもいいかもしれません。

class Reservation {
    public function invoke(Accommodation $accommodation) {
        if ($this->vacancyFinder->vacant($accommodation)) {
            throw new AlreadyOccupiedException('…');
        }
        // ...
    }
}

Service 層を取り入れる際に検討したいこと

振る舞いを Entity に持たせるか Service に持たせるか

Service を使ったバージョン

class ReservationService {
    public function cancel(Reservation $reservation) {
        $reservation->is_cancelled = true;
        $reservation->save();
    }
}
// Client
$this->reservationService->cancel($reservation);

エンティティにメソッドを持たせず、プロパティを直接変更しています。

Entity を使ったバージョン

class Reservation {
    public function cancel() {
        $this->is_cancelled = true;
        $this->save();
    }
}
// Client
$reservation->cancel();

こちらは、プロパティの変更はエンティティ内で行い、クライアントはエンティティのメソッドを呼び出しています。

「キャンセル」という操作と、実際に更新されるデータが分離されているので、仮にデータ構造が変わり、更新する対象データが変わっても、クライアントに影響が及ぶことがありません( is_cancelled の代わりに cancelled_at にキャンセル日時を入れるようにした場合、とか)

委譲するかしないか

Service を使ったバージョン

class SomeService {
    public function create(SomeModel $model) {
        return $this->repository->create($model);
    }
    public function update(SomeModel $model) {
        return $this->repository->update($model);
    }
}

いわゆる Repository パターンを使った、Controller -> Service -> Repository という流れでのデータ操作の方法です。

Entity を使ったバージョン

$someModel->create($attributes);
$someModel->update($attributes);

単純に委譲しているだけなら直接 Model を書き換えることを検討してもいいと思います(無理に Service 層と Repository を経由する必要はないと思います)。

関連する操作をまとめるか分けるか

決済関連のメソッドをまとめるパターン

class PaymentService {
    public function doPayment() {}
    public function doRefund() {}
}

Service には状態がないので、いくつ公開メソッドをつくっても基本的には問題にはならないです。

1クラス1メソッドのパターン

class PaymentService {
    public function invoke() {}
}

class RefundService {
    public function invoke() {}
}

しかし、個々のメソッドの行数が長くなって見通しが悪くなってきた場合、クラスを分けてしまったほうがいいと思います。

まとめ

Service とは何かというところから、実際に Laravel にどう組み込んで使うかというところまで、ざっと見てみましたが、どのようなケースで Service 化するといいか、というのが少し見えてきた気がします。

他にもこういう処理は Service 化するといい、などの提案や、文中の定義や例の部分で改善点みたいなのがありましたら、ぜひコメント欄にてご指摘ください :bow:

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

php-master-changes 2019-05-25

今日は libffi の最低要求バージョン指定の追加、valgrind 検出の pkg-config への移行と with-valgrind と with-pcre-valgrind の統合、バンドルしている PCRE2 を 10.33 へ更新する修正があった!

2019-05-25

petk: Define minimum required libffi version

hughmcmaster: Use PKG_CHECK_MODULES to detect valgrind, and share build config with pcre

weltling: Upgrade bundled PCRE2 to 10.33

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

.envが使えないレガシー環境で.env的なファイルを使いたい

背景

composerが使えないなどのレガシー環境でも環境変数を使いたい。

結論

putenv getenv
を使いましょう。

使い方

// Env.php
<?php
putenv('DBNAME=hoge');
putenv('HOST=fuga');
putenv('PORT=3306');
putenv('USER=root');
putenv('PASSWORD=password');
// Config.php
<?php
require_once 'Env.php';

define('DBNAME', getenv('DBNAME'));
define('HOST', getenv('HOST'));
define('PORT', getenv('PORT'));
define('USER', getenv('USER'));
define('PASSWORD', getenv('PASSWORD'));

参考

公式ドキュメント(getenv)

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

paiza cloudのHTTPSの制限でハマった

概要

PaizaCloudを使うと、簡単に開発環境を構築することができる。有料プランなら、常時公開もできるようになる。
https://paiza.cloud/ja/

Laravel勉強用にPaizaCloudで開発環境を構築していたところ、
HTTPS周りのことでつまずいて、初心者はきっとハマると思うので誰か困った時に参考にしてもらえるように、メモ。

詳しく

php artisan make:auth  // ログイン認証画面を作成
php artisan migrate    // DBに反映
php artisan serve --host=0.0.0.0

これで、ログイン画面ができたので、xxx.paiza-user.cloud:8000 にアクセス。
スクリーンショット 2019-05-26 11.29.19.png

しかし、右上のLOGINを押しても、

Please use HTTPS(SSL) instead of HTTP to access the URL.

と表示され、ログイン画面が表示されない…
ローカル環境ならちゃんと表示されるのに、なぜだ...

なぜなのか

  • ログイン画面へのリンクが、httpで張られていた
  • トップページがhttpsで表示されているので、当然httpsだと思ってリンクがhttpなのに気が付かなかった・・・

  • PaizaCloudで非SSLなアクセスがあった際、

解決策

[コメントを受け、追記]
ここでhttpsとならないのは、X_FORWARDED_PROTO を使った判定をしていないため。

app/Http/Middleware/TrustProxies.php
// 略
class TrustProxies extends Middleware
{
    /**
     * The trusted proxies for this application.
     *
     * @var array|string
     */
    protected $proxies = "*"; // ここに"*"を指定する

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

こちらの記事で詳しく解説されている。
https://qiita.com/yamatox/items/8f3f481d88e807793ad5

以下は、forceSchemeを使う方法。この方法では、扱うURLをhttpsに強制する。

AppServiceProvider bootメソッドでUrlGeneratorクラスforceSchemeメソッドを呼び出して、
アプリ内で扱われるURLを全てhttpsにする。
こうすることで、リンクがhttpsで出力されるようになる。

app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Routing\UrlGenerator;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot(UrlGenerator $url)
    {
        // force to use HTTPS
        $url->forceScheme('https');
    }
}

参考

[Laravel]常時SSLなアプリケーションでのURL生成のベストプラクティスを考える
https://qiita.com/hisash/items/4b3bb1ea47c38b0d8c86

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

【PHP】オブジェクト指向を学ぶ[その]3

オーバーライド

継承した親クラスのメソッドやプロパティを書き換えると言うもの。

とりあえずコード。

index.php
<?php

//クラス定義
class Self_Introduction{

    // プロパティー
    // 変数定義
    private $name;
    private $age;


    // インスタンスが生成されると同時に処理が走る
    public function __construct($name, $age){
        $this->setName($name);
        $this->setAge($age);
    }

    private function setName($name){
        $this->name = $name;
    }

    private function setAge($age){
        $this->age = $age;
    }

    function getName(){
        return "私の名前は{$this->name}です。<br>";
    }

    public function getAge(){
        return "年齢は{$this->age}才です。<br>";
    }

    public function getIntroduction(){
        return "私の名前は{$this->name}です<br>年齢は{$this->age}才です。<br>";
    }
}

class Sub_Introduction extends Self_Introduction{

    private $blood;

    public function __construct($name, $age, $blood){
        parent::__construct($name, $age);
        $this->setBlood($blood);
    }

    private function setBlood($blood){
        $this->blood = $blood;

    }

    public function getBlood(){
        return "血液型は{$this->blood}です。<br>";
    }

    public function getIntroduction(){
        return "{$this->getName()}{$this->getAge()}{$this->getBlood()}";
    }

}

// インスタンス生成
$USER = new Self_Introduction('太郎', 35);
$user = new Sub_Introduction('太郎', 35, 'A型');

echo $USER->getIntroduction();
//私の名前は太郎です
//年齢は35才です。


echo $user->getIntroduction();
//私の名前は太郎です。
//年齢は35才です。
//血液型はA型です。

getIntroduction();に注目してください。
getIntroduction();は親classでも子classでも使用されています。

ですが、子classでgetIntroduction();を呼び出し出力結果を見るとちゃんと上書きされているのが分かります。

このように継承した親クラスのメソッドやプロパティを書き換えることをオーバーライドと言います。

静的メンバ

次は静的メンバです。
静的メンバはプロパティやメソッドをひっくるめた言い方

プロパティやメソッドを静的メンバにするとクラスの外からプロパティやメソッドを使えるようにすること。

使用方法
publicをつける
staticをつける
です。

コードを見ていきます。

index.php
class Foo{

    public static $age = 20;
    public $name = '花子';

    public static function HOGE(){
        return '静的メンバです';
    }

}

echo Foo::$age; //20
echo Foo::$name //出力されず
echo Foo::HOGE(); //静的メンバです

//インスタンス生成
$foo = new Foo();
echo $foo->age; //NULL
echo $foo->name; //花子
echo $foo->HOGE(); //静的メンバです



結果はこのような感じになります。

まず上から見ていきます。
echo Foo::$age・・・インスタンスを作成していなくても呼び出せていますね。これが静的メンバです。
echo Foo::$name・・・では$nameはと言うと呼び出せていません。このプロパティはインスタンスを生成して初めて使えるものです。
echo Foo::HOGE();・・・次はメソッド。これもちゃんと呼び出せています。ですが、メソッドに限りstaticにしていなくても呼び出せてしまうので注意が必要です。

次はインスタンスを生成して呼び出してみます。
echo $foo->age;・・・これは呼び出せません。と言うのも静的メンバはclassに属しているものであってインスタンスに属しているものじゃないからです。
echo $foo->name;・・・これは当たり前ですが呼び出せます。
echo $foo->HOGE();・・・これも呼び出せてしまうので注意が必要です。
インスタンス生成しても静的メンバはそのインスタンスの中には入っていない。

こう見るとインスタンスを生成しなくても呼び出せる静的メンバは関数みたいですね。

以上。

※「ここ間違っている」や「こんなことも覚えておいた方がいい」と言うものがあれば是非教えてください

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

【コピペ用】Laravel環境構築【MAMP】

はじめに

こまけぇことはいいんだよ! ってことで、コピペで
MAMPを使用してのLaravelのローカル開発環境を構築します。

前提条件

MacにMAMPをインスール済みでPHPの環境構築が済んでいること
あらかじめ、phpMyAdminで新しくデータベースを作っておく。
今回は「myapp」というDB名で作成

手順1 Laravelをインストールするまで

ターミナルを起動して以下のように順番にコマンドを打っていきます。

// homebrewをインストール
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

// composerをインストール
$ brew install composer

// composerのバージョンを確認
$ composer -V

// 以下のような感じに表示されたらインストールおっけー!
Composer version 1.8.5 2019-04-09 17:46:47

// MAMPのhtdocsフォルダに移動する
$ cd /Applications/MAMP/htdocs

// composerを使ってLaravelをインストール(バージョンは5.5LTS版を使用)
// 「myapp」の部分はプロジェクト名になるのでお好きな名前を
$ composer create-project laravel/laravel=5.5.* myapp --prefer-dist

// プロジェクトフォルダに移動する
$ cd myapp

// Laravelのバージョンを確認
$ php artisan -V

//以下のように表示されたらインストールおっけー!
Laravel Framework 5.5.45

手順2 .envファイルの修正する

作成したプロジェクトフォルダ内に「.env」という名前のファイルがあるのでエディタで開いて修正する。
デフォルト設定なら以下の部分を書き換える。

.envファイル一部
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889        //DBのポート番号 デフォルトは8889
DB_DATABASE=myapp //phpMyadminで新規作成したDB名
DB_USERNAME=root  //DBのユーザー名 デフォルトは root
DB_PASSWORD=root //DBのログオンパスワード デフォルトは root

手順3 DBに接続されてるかどうかを確認する

マイグレーションを実行して、DBと接続できているか確認します。

//マイグレーション実行
$ php artisan migration

//以下のような感じに表示されたら接続おっけ〜!
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table

きちんとテーブルも作成されていますね。
スクリーンショット 2019-05-26 13.00.21.png

タイムゾーンの変更

ついでにデフォルトのタイムゾーンを変更しておきましょう
「cofig」フォルダの中の「app.php」をエディタで開いてtimezoneの項目を以下のように変更する。
元は 'timezone' => 'UTC', になっています。

app.php
    /*
    |--------------------------------------------------------------------------
    | Application Timezone
    |--------------------------------------------------------------------------
    |
    | Here you may specify the default timezone for your application, which
    | will be used by the PHP date and date-time functions. We have gone
    | ahead and set this to a sensible default for you out of the box.
    |
    */

    'timezone' => 'Asia/Tokyo',

終わりに

毎回環境構築を調べなおすのが面倒なので自分用にまとめてみました。
「ここが間違ってるよ〜!」とか「ここはもっとこうしたらいいんじゃない?」って
などがありましたら、コメントください。お待ちしております。

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

php 基本

学習したいこと

php基本メソッド

実行タグ
<?php
?>
echo
 → puts
;
閉じたぐ必要

:php -a
irb

基本メソッド
. = + 文字連結


var_dump() = boolean判定
var_dump(1 < 20);

変数 = $変数名 = 格納する値;

定数 = $変数名 = 格納する値;


式展開 = {$hello}

fgets(STDIN); = gets(ユーザーの入力まち)

配列
$data = [キー1 => 値1, キー2 => 値2, ...];

$data = ['title' => "ワンパンマン"];
echo $data['title'];

例)

$posts = ["aa" => "ワンパン", "bb" => "ばか"];

echo $posts["aa"];

→ ワンパン
if
if (条件式) {
  // 条件式が真(true)のときに実行する処理
}
関数
function say_hello() {
  echo "Hello World";
}

// 関数の実行
say_hello();
while
$sum = 0;
$number = 1;
while ($number <= 10) {
  $sum += $number;
  echo $sum;
  $number += 1;
}
class
class クラス名 {
  // プロパティやメソッドの定義
}

$post = new Post();

クラスメソッド
class Post {
  public static function get_post_count() {
    return 0;
  }
}

$post = new Post();  // Reviewクラスのインスタンスを生成
var_dump($post);

echo Post::get_review_count(), PHP_EOL;


コンストラクタもあるみたい

ララベル環境設定
phpbrew
PHP
Composer(PHPのパッケージ管理システム) bundler

composer作成
composer create-project laravel/laravel アプリケーション名 バージョン

rails new
コントローラー
php artisan make:controller コントローラ名
モデル作成
php artisan make:model モデル名

```:サーバー起動(rails s)

php artisan serve
```

マイグレート
php artisan migrate
基本ディレクトリ構成
web.php(Routing)

Route::get('tweets', 'TweetsController@index');

layout.blade.php(application.html.erb)

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

PHP7.2をHomebrewでインストールする方法

環境

  • macOS Mojave 10.14

インストール

$ brew install php@7.2

PATHを通す

$ echo 'export PATH="/usr/local/opt/php@7.2/bin:$PATH"' >> ~/.bash_profile
$ echo 'export PATH="/usr/local/opt/php@7.2/sbin:$PATH"' >> ~/.bash_profile

ターミナルを再起動後、バージョンを確認

$ php -v
PHP 7.2.18 (cli) (built: May 21 2019 20:18:44) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.18, Copyright (c) 1999-2018, by Zend Technologies
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む