20200121のlaravelに関する記事は6件です。

【Laravel - AWS】composer install が mbstring のエラーで失敗するとき

結論だけ知りたい

$ sudo yum install php{$ver}-mbstring.x86_64

$ver はインストールしたPHPのバージョンに合わせる。

PHPバージョン7.2系をインストールしている場合は

$ sudo yum install php72-mbstring.x86_64

とすればOK。心配な人はPHPのバージョンを確認しましょう。

前提

Amazon Linuxの公式ガイドに沿ってLAMP環境を構築。

基本は記事に忠実に。ただ途中、phpのバージョンを70→72に指定した。

バージョン情報
Laravel : 6.9
PHP : 7.2.24
AWS : EC2 - AmazonLinux

▼上記の記事に書かれているコマンド▼

[ec2-user ~]$ sudo yum install -y httpd24 php70 mysql56-server php70-mysqlnd

▼実際に打ち込んだコマンド▼

[ec2-user ~]$ sudo yum install -y httpd24 php72 mysql56-server php72-mysqlnd

起きたこと

ローカルのLaravelを本番でgit経由でpullし、 composer install しようとしたタイミングでエラー。

エラー内容

[ec2-user@ip-XXX src]$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for erusev/parsedown 1.7.3 -> satisfiable by erusev/parsedown[1.7.3].
    - erusev/parsedown 1.7.3 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system.
  Problem 2
    - Installation request for laravel/framework v6.9.0 -> satisfiable by laravel/framework[v6.9.0].
    - laravel/framework v6.9.0 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system.
  Problem 3
    - Installation request for facade/ignition 1.13.0 -> satisfiable by facade/ignition[1.13.0].
    - facade/ignition 1.13.0 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system.
  Problem 4
    - Installation request for phpunit/phpunit 8.5.1 -> satisfiable by phpunit/phpunit[8.5.1].
    - phpunit/phpunit 8.5.1 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system.
  Problem 5
    - Installation request for scrivo/highlight.php v9.17.1.0 -> satisfiable by scrivo/highlight.php[v9.17.1.0].
    - scrivo/highlight.php v9.17.1.0 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system.
  Problem 6
    - laravel/framework v6.9.0 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system.
    - facade/flare-client-php 1.3.1 requires illuminate/pipeline ~5.5|~5.6|~5.7|~5.8|^6.0 -> satisfiable by laravel/framework[v6.9.0].
    - Installation request for facade/flare-client-php 1.3.1 -> satisfiable by facade/flare-client-php[1.3.1].

何やら長々とエラーが出ているが、全部のエラーで php-extensionの mbstring が無いということなのでそれをインストールすれば良さそう。

調べてみると yum install --enablerepo=remi-php72 php-mbstring でインストールできるとのこと。

しかしコマンドを打ち込んだらまた怒られる。

[ec2-user@ip-XXX src]$ sudo yum install --enablerepo=remi-php72 php-mbstring
読み込んだプラグイン:priorities, update-motd, upgrade-helper


Error getting repository data for remi-php72, repository not found

リポジトリが見つからないということなのでリポジトリのコマンドを外す。

[ec2-user@ip-XXX src]$ sudo yum install php-mbstring
読み込んだプラグイン:priorities, update-motd, upgrade-helper
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ php-mbstring.x86_64 0:5.3.29-1.8.amzn1 を インストール
--> 依存性の処理をしています: php-common(x86-64) = 5.3.29-1.8.amzn1 のパッケージ: php-mbstring-5.3.29-1.8.amzn1.x86_64
--> トランザクションの確認を実行しています。
---> パッケージ php-common.x86_64 0:5.3.29-1.8.amzn1 を インストール
--> 衝突を処理しています: php72-common-7.2.24-1.18.amzn1.x86_64 は php-common < 5.5.22-1.98 と衝突しています
--> 依存性解決を終了しました。
エラー: php72-common conflicts with php-common-5.3.29-1.8.amzn1.x86_64
 問題を回避するために --skip-broken を用いることができます。
 これらを試行できます: rpm -Va --nofiles --nodigest

バージョンがコンフリクトしたらしい。どうやらインストールしているパッケージ自体がおかしい気がする。

よくよく調べると、AmazonLinuxはデフォルトで自前のリポジトリを最優先で探すことがわかった。

これを回避する方法は

  1. 対応するパッケージをAmazonLinux公式のリポジトリから落とす。
  2. リポジトリ優先度の設定をする。

の2つと考えられる。

今回は mbstring がインストールできれば良いので1の方法で行くことにした。

2の方法についてはこちらの記事がわかりやすい。
Amazon LinuxでYUMを使う時に気をつけるポイント | Developers.IO

対応パッケージを探す

Amazon公式のリポジトリは
php72-cli.x86_64
のように、バージョンとパッケージ名を同時に指定する方式になっている。

今回は72に対応するパッケージが欲しいのでgrepを使って探す

[ec2-user@ip-XXX src]$ yum list | grep php72
php72.x86_64                          7.2.24-1.18.amzn1             @amzn-updates
php72-cli.x86_64                      7.2.24-1.18.amzn1             @amzn-updates
php72-common.x86_64                   7.2.24-1.18.amzn1             @amzn-updates
php72-json.x86_64                     7.2.24-1.18.amzn1             @amzn-updates
php72-mbstring.x86_64                 7.2.24-1.18.amzn1             @amzn-updates
php72-mysqlnd.x86_64                  7.2.24-1.18.amzn1             @amzn-updates

...

mbstringも発見したので、これをインストールしてみる。

[ec2-user@ip-XXX src]$ sudo yum install php72-mbstring.x86_64
読み込んだプラグイン:priorities, update-motd, upgrade-helper
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ php72-mbstring.x86_64 0:7.2.24-1.18.amzn1 を インストール
--> 依存性解決を終了しました。

依存性を解決しました

=================================================================================================================
 Package                     アーキテクチャー    バージョン                      リポジトリー               容量
=================================================================================================================
インストール中:
 php72-mbstring              x86_64              7.2.24-1.18.amzn1               amzn-updates              1.4 M

トランザクションの要約
=================================================================================================================
インストール  1 パッケージ

総ダウンロード容量: 1.4 M
インストール容量: 3.2 M
Is this ok [y/d/N]: y
Downloading packages:
php72-mbstring-7.2.24-1.18.amzn1.x86_64.rpm                                               | 1.4 MB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  インストール中          : php72-mbstring-7.2.24-1.18.amzn1.x86_64                                          1/1 
  検証中                  : php72-mbstring-7.2.24-1.18.amzn1.x86_64                                          1/1 

インストール:
  php72-mbstring.x86_64 0:7.2.24-1.18.amzn1                                                                      

完了しました!

無事インストールできたので、改めて composer install してみる

[ec2-user@ip-XXX src]$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Package operations: 85 installs, 0 updates, 0 removals

...

Package manifest generated successfully.

無事通った。

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

Laravelではメール送信やSlack通知のテストが簡単だって知ってましたか?

メールやSlack通知のテストしてますか?
通知のテストはし難いのですが、Laravelではモックが充実しているので簡単にテストすることができます。

参考: https://readouble.com/laravel/6.x/ja/mocking.html

環境

  • PHP v7.2
  • Laravel v6.x
  • PHPUnit v7.5

テストする方法

以下のようにpostのテストを実行する前に Mail Facade で fake を差し込むようにしておく。
postテスト実行した後に、assertSent() でMailableクラスの処理が走っているかテストできます。

use Illuminate\Support\Facades\Mail;

class IndexControllerTest extends TestCase
{
    public function testIndex()
    {
        Mail::fake();

        $this->post('/path/to/something/post', ['text' => 'ダミーテキスト'])->assertRedirect();

        // MessageCreatedクラスは、Mailableをextendsしたものです。実行されているべきMailableをフルパスで指定します。
        Mail::assertSent(MessageCreated::class);
    }
}

おまけ

notificationもテストできます。

Slack通知など。
ドキュメントの Notification Fake のところを参照してください。
「テストをかけるよ」ということを知識として知っていないとなかなかかけないのでここで紹介だけしておきます。

ハマりどころ

最初、以下のように書いて動かずにハマったので共有しておきます。

    public function testIndex()
    {

        $this->post('/path/to/something/post', ['text' => 'ダミーテキスト'])->assertRedirect();

        // postのテストが終わった後にfake差し込み
        Mail::fake();
        Mail::assertSent(MessageCreated::class);
    }

fakeを差し込むタイミングが終わった後なので意味がありません。

面倒なので毎回 fake を書きたく無い場合

setUp()のなかで処理を追加しておきましょう。
毎回クラスに書いてもいいのですが、これを差し込んだ共通親クラスを作っておくともっと楽チンです。

    protected function setUp(): void
    {
        parent::setUp();

        Mail::fake();
    }

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

Laravel データベースの論理削除

はじめに

  • Laravel で データベースの論理削除を行いたい時は、ソフトデリート の機能が使用できる。
  • 論理削除とは、本当にデータベースから削除するのではなく、フラグなどを立ててシステム上から見えなくすること
  • Laravelでは Eloquent の機能でソフトデリートが使用できる。

前提

Laravel のバージョン

$ php artisan -V
> Laravel Framework 5.8.16

Mysql のバージョン

  • mysql を使用する
mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.28    |
+-----------+

使いかた

①テーブルに『deleted_at』カラムを追加する

  • ここでは users テーブルにソフトデリートを実装する
mysql> describe users;
+-------------------+---------------------+------+-----+---------+----------------+
| Field             | Type                | Null | Key | Default | Extra          |
+-------------------+---------------------+------+-----+---------+----------------+
| id                | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| username          | varchar(20)         | NO   | UNI | NULL    |                |
| password          | varchar(128)        | NO   |     | NULL    |                |
| created_at        | timestamp           | YES  |     | NULL    |                |
| updated_at        | timestamp           | YES  |     | NULL    |                |
| deleted_at        | timestamp           | YES  |     | NULL    |                |
+-------------------+---------------------+------+-----+---------+----------------+

②ソフトデリートを使用できるようにモデルで宣言

  • モデルで宣言するだけで、delete()処理を実行するとdeleted_atカラムに現在の時刻がセットされる。
  • deleted_atカラムに現在の時刻がセットされていると、削除済み隣、クエリ結果に含まれない。
app/Model/User.php
<?php

namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model
{
    use SoftDeletes;
省略

まとめ

  • とても簡単にソフトデリートを有効にできた。
  • ソフトデリート済みのデータのみ取得や、物理削除の方法もあるがあとで、別途調べるよお。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelで大規模プロジェクトと戦うにはRepository層を導入するといいかも

Laravelをなんとなく書いていると起こる問題点

  • コントローラ膨らみすぎ問題
  • Model膨らみすぎ問題
  • MdoelにORMマッパーにベタベタに依存したビジネスロジックを書いてしまってテスト書くのがしんどくなる問題

設計方針

  1. 重要度が高いビジネスロジックはDBやLaravelの機能に依存させない
    LaravelのModelにビジネスロジックを書いてしまうとEloquent依存のコードを簡単にかけてしまいます。
    とりあえず動かすにはこれが一番早いので選択肢としてはありかと思いますが、後に機能が複雑になってくるとテスト結果がDBの状態になったりテストがしにくくなるのでやめましょう
    とはいえEloquentは便利なのでRepository内で使いましょう

  2. できるところはLaravelに依存する
    ログイン、ページネーション、バリデーションなどはLaravelに依存しましょう。

  3. できる限りテストを簡単に
    テストを簡単にかけるような設計でないとテスト書くのが苦痛になり、テストが雑になりがちです。

解決策

DDDや、クリーンアーキテクチャはそのまま導入しようとしましたが、現状に対してあまりに壮大過ぎる&チームでやるには学習コストが高すぎるので以下を導入することにしました。

Modelからビジネスロジックを分離させる

Entitiesディレクトリを新規に作成し、ここに重要度が高いビジネスロジックをModelに依存させない形で作成します。
LaravelのFacadeも使ってはいけません。CakePHPやWordPressにそのままコピペできるようにしましょう。

<?php


namespace App\Entities;


class Video
{
    protected int $id;
    protected string $title;
    protected string $description;
    protected string $publish_at;
    protected User $author;

    /**
     * @return int
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getTitle(): string
    {
        return $this->title;
    }

    /**
     * @return string
     */
    public function getDescription(): string
    {
        return $this->description;
    }

    /**
     * @return User
     */
    public function getAuthor(): User
    {
        return $this->author;
    }

    /**
     * @return User|string
     */
    public function getPublishAt()
    {
        return $this->publish_at;
    }

    /**
     * Video constructor.
     * @param int $id
     * @param string $title
     * @param string $description
     * @param string $publish_at
     * @param User $author
     */
    public function __construct(int $id, string $title, string $description, string $publish_at, User $author)
    {
        $this->id = $id;
        $this->title = $title;
        $this->description = $description;
        $this->publish_at = $publish_at;
        $this->author = $author;
    }

    public function check_publish(){
        // いろいろなロジックを書いていく
    }
}

Repository層の導入

Repositoriesディレクトリを作成し、ここでEloquentを使いながら上のEntityを作成します。
まずはapp/Repositoriesにインターフェイスを作成します。インターフェイスを作成することでデータベースへの依存を防げます。

<?php


namespace App\Repositories;


use App\Entities\Video;

interface VideoRepository
{
    public function getById(int $id): ?Video;
}

app/Repositories/implに実際にEloquentを使った組み込みを書いていきます。

<?php


namespace App\Repositories\impl;


use App\Entities\Video;
use App\Repositories\VideoRepository;

class VideoDBRepository implements VideoRepository
{
    function getById(int $id): ?Video
    {
        $video = \App\Model\Video::find($id);
        if(empty($video)){
            return null;
        }
        return new Video($video->id, $video->title, $video->discription, $video->author}
}

サービスプロバイダーを追加し、VideoRepositoryとVideoDBRepositoryをバインドします。
これでVideoRepository参照するとVideoDBRepositoryが読み込まれます。
VideoRepositoryに依存することにより入れ替えで簡単にテストができます。

<?php

namespace App\Providers;

use App\Repositories\impl\VideoDBRepository;
use App\Repositories\VideoRepository;
use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
    }

    /**
     * Bootstrap services.
     */
    public function boot(): void
    {
        $this->app->bind(VideoRepository::class, VideoDBRepository::class);
    }
}

実際にリポジトリを使用する時はコンストラクタインジェクションを使います。
サービス・プロバイダーでbindしているのでInterfaceからアクセス可能です。

<?php

namespace App\Http\Controllers;

use App\Entities\Video;
use App\Repositories\VideoRepository;


class VideoController extends Controller
{
    protected VideoRepository $videoRepository;

    /**
     * VideoController constructor.
     * @param VideoRepository $videoRepository
     */
    public function __construct(VideoRepository $videoRepository)
    {
        $this->videoRepository = $videoRepository;
    }

    public function video(VideoRequest $request){
        /** @var Video $video */
        $video = $this->videoRepository->getById($request->get('id'));
        return view('video', [
            'video' => $video
        ]);
    }
}

まとめ

ControllerはRepositoryのIntarfaceに依存しているので、ControllerはDBを意識しないでよくなりました。
テスト用のMockRepositoryでも、DBではなくAPIで永続化をするApiRepositoryでもimpl組み込みを追加しbindし直すだけで対応が可能になりました。
そしてEntityではRepositoryもControllerも意識する必要がありません。ロジックのテストにDBの依存が入らないのでテストがやりやすくなります。

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

ReflectionException Class does not exist 原因まとめ

ReflectionException Class does not existが出現するときの原因

違う原因で何度か遭遇したのでまとめます。

  1. useし忘れ -> ちゃんとuseする。
  2. autoloaderが壊れる -> composer dump-aoutoload
  3. sintax error -> 直す

3はPHPのバージョンに寄ったりするようで、ローカルでは問題ないけど、本番環境ではエラーが発生するという事象で少し厄介でした。

参考

https://qiita.com/yoshinyan/items/5116bd30f2d1f2b1d865
https://qiita.com/K-Shuuun/items/c48292cd4186a2c1c8fd

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

【Laravel】条件付きで必須チェックの組み合わせを実現する。

「hoge 且つ fuga()」 が真の時に、hogeの必須チェックを有効にする

    /**
     * 条件付き必須チェックの組み合わせを実現。
     * @param Validator $validator
     */
    public function withValidator(Validator $validator)
    {
        // 「hoge 且つ fuga」の時には必須チェックを有効にする
        $validator->sometimes('hoge', ['nullable', $this->fuga()], function ($o) {
            return $o->hoge == true;
        });
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む