20191127のlaravelに関する記事は8件です。

[Laravel]テキスト中のURLをリンクに自動で置換する

概要

テキスト中にあるURLを自動でリンクに置換することでフロント側でリンク化する手間が省けるのでバックエンドで処理をしてみました。

URLをリンク化する

今回はテキスト中にURLの行のみのがある場合にリンク化させます。文章中のURLは、リンク化しない正規表現にしました。

https://test  //リンク化
https://test テスト   //無視

$textのURLをリンク化

public static function replaceUrl($text)
    {
        $texts = explode(PHP_EOL, $text); //PHP_EOLは,改行コードをあらわす.改行があれば分割する
        $pattern = '/^https?:\/\/[^\s  \\\|`^"\'(){}<>\[\]]*$/'; //正規表現パターン
        $replacedTexts = array(); //空の配列を用意

        foreach ($texts as $value) {
            $replace = preg_replace_callback($pattern, function ($matches) {
            //textが1行ごとに正規表現にmatchするか確認する
                if (isset($matches[1])) {
                    return $matches[0]; //$matches[0] がマッチした全体を表す
                }
            //既にリンク化してあれば置換は必要ないので、配列に代入
                return '<a href="' . $matches[0] . '" target="_blank" rel="noopener">' . $matches[0] . '</a>';
            }, $value);
            $replacedTexts[] = $replace;
            //リンク化したコードを配列に代入
        }
        return implode(PHP_EOL, $replacedTexts);
        //配列にしたtextを文字列にする
    }

使用したメソッド

explode — 文字列を文字列により分割する
(https://www.php.net/manual/ja/function.explode.php)
preg_replace_callback — 正規表現検索を行い、コールバック関数を使用して置換を行う
(https://www.php.net/manual/ja/function.preg-replace-callback.php)
implode — 配列要素を文字列により連結する
(https://www.php.net/manual/ja/function.implode.php)

使っている正規表現について

$pattern = '/^https?:\/\/[^\s  \\\|`^"\'(){}<>\[\]]*$/' 

^ 先頭にhttpsまたはhttpにマッチする
\ 直後の正規表現記号を エスケープ
[^...]角括弧に含まれる文字以外にマッチ
ここでは、半角、全角スペースとURLで除外される記号以外とマッチさせています
* 直前の文字が 0回以上 繰り返す場合にマッチします。最長一致。
? 直前の文字が 0個か1個 の場合にマッチします。最長一致。

あとがき

文字列を改行ごとに分割して、分割したものが正規表現にマッチした場合URLをリンク化し、
それをまた文字列に変換したりとするなど色々考えたので勉強になりました。
正規表現もURLとマッチするものを考える作業が楽しかったです。

参考記事

(https://qiita.com/sukobuto/items/b6cdfa966b29823c62f0)

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

[Laravel]テキスト中のURLをリンクに置換

概要

テキスト中にあるURLを自動でリンクに置換することでフロント側でリンク化する手間が省けるのでバックエンドで処理をしてみました。

URLをリンク化する

今回はURLの行のみの場合にリンク化させます。なので文章中のURLは、リンク化しない正規表現にしました。

https://test  //リンク化
https://test テスト   //無視

$textのURLをリンク化

public static function replaceUrl($text)
    {
        $texts = explode(PHP_EOL, $text); //PHP_EOLは,改行コードをあらわす.改行があれば分割する
        $pattern = '/^https?:\/\/[^\s  \\\|`^"\'(){}<>\[\]]*$/'; //正規表現パターン
        $replacedTexts = array(); //空の配列を用意

        foreach ($texts as $value) {
            $replace = preg_replace_callback($pattern, function ($matches) {
            //textが1行ごとに正規表現にmatchするか確認する
                if (isset($matches[1])) {
                    return $matches[0]; //$matches[0] がマッチした全体を表す
                }
            //既にリンク化してあれば置換は必要ないので、配列に代入
                return '<a href="' . $matches[0] . '" target="_blank" rel="noopener">' . $matches[0] . '</a>';
            }, $value);
            $replacedTexts[] = $replace;
            //リンク化したコードを配列に代入
        }
        return implode(PHP_EOL, $replacedTexts);
        //配列にしたtextを文字列にする
    }

使用したメソッド

explode — 文字列を文字列により分割する
(https://www.php.net/manual/ja/function.explode.php)
preg_replace_callback — 正規表現検索を行い、コールバック関数を使用して置換を行う
(https://www.php.net/manual/ja/function.preg-replace-callback.php)
implode — 配列要素を文字列により連結する
(https://www.php.net/manual/ja/function.implode.php)

使っている正規表現について

$pattern = '/^https?:\/\/[^\s  \\\|`^"\'(){}<>\[\]]*$/' 

^ 先頭にhttpsまたはhttpにマッチする
\ 直後の正規表現記号を エスケープ
[^...]角括弧に含まれる文字以外にマッチ
ここでは、半角、全角スペースとURLで除外される記号以外とマッチさせています
* 直前の文字が 0回以上 繰り返す場合にマッチします。最長一致。
? 直前の文字が 0個か1個 の場合にマッチします。最長一致。

あとがき

文字列を改行ごとに分割して、分割したものが正規表現にマッチした場合URLをリンク化し、
それをまた文字列に変換したりとするなど色々考えたので勉強になりました。
正規表現もURLとマッチするものを考える作業が楽しかったです。

参考記事

(https://qiita.com/sukobuto/items/b6cdfa966b29823c62f0)

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

Laravel でむやみに Model all() を使うとパフォーマンスが悪くなる

はじめに

(サンプルのバージョンは 5.5 で書いてます)

Laravelって便利。リレーションだって簡単に貼れます。
しかし、書き方によって予期しない挙動したので備忘録として残します。

実際にやってみた

まずサンプルとして、ユーザ情報を扱うテーブルと、トレーニング情報を扱うテーブルを用意。

CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(64) NOT NULL COMMENT '名前',
  `deleted_at` datetime DEFAULT NULL COMMENT '削除日時',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='ユーザ';

CREATE TABLE `trainings` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `trained_user_id` int(10) unsigned NOT NULL COMMENT 'トレーニング者',
  `summary` varchar(255) NOT NULL COMMENT '概要',
  `deleted_at` datetime DEFAULT NULL COMMENT '削除日時',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='トレーニング';

トレーニング情報と一緒に、トレーニング者のユーザ名を取得したいとき、
Modelクラスでこのように関数を設置し、belongsToでリレーションを貼っておくと、
Training情報を取得した時にも trained_user関数 を呼び出しただけで users 情報を取得できます。

Training.php
use Illuminate\Database\Eloquent\Model;

/**
 * Class Training
 */
class Training extends Model
{
    public function trained_user()
    {
        return $this->belongsTo(User::class);
    }
}
$trainings = Training::all();
$trainings[0]->trained_user->name; // トレーニング者の名前が取れる

でも、やったね!と思ってクエリログを見ると愕然します...

select * from `trainings` where `trainings`.`deleted_at` is null
select * from `users` where `users`.`id` = '1' and `users`.`deleted_at` is null limit 1
select * from `users` where `users`.`id` = '2' and `users`.`deleted_at` is null limit 1
select * from `users` where `users`.`id` = '3' and `users`.`deleted_at` is null limit 1

みてください、・・・ユーザ分毎回SQL投げてる;;;;
めっちゃパフォーマンス悪い;;;;

反省して書き直し。

$trainings = Training::with(['trained_user'])->get(); // trained_user を設定する
$trainings[0]->trained_user->name; // 同様にトレーニング者の名前が取れる

クエリログは・・・

select * from `trainings` where `trainings`.`deleted_at` is null
select * from `users` where `users`.`id` in ('1', '2', '3') and `users`.`deleted_at` is null

期待どおりの結果になりました!
というわけで、リレーション貼ってる時は気をつけましょう〜

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

Laravel 5.8でオリジナルのイベントを定義してみた話

はじめに

こちらの記事は、社内knowledgeからの移行記事になります。

Laravelで自作イベントを定義するまでの一連の流れ。

【参考にした公式】

【参考記事】
- Laravel5.7でEventを利用してみる - qiita

【ソース】
- GitHub

環境

  • Laravel 5.8.23

本編

1.イベントとリスナをサービスプロバイダに定義

application/app/Providers/EventServiceProvider.php
protected $listen = [
    'App\Events\TaxIncreased' => [
        'App\Listeners\TaxIncreasedNotification'
    ],
];

2.Artisanコマンドでイベントとリスナを作成

# php artisan event:generate
Events and listeners generated successfully!

3.リスナにイベント処理を定義

今回は発生したイベントのオブジェクトを返すだけ。

application/app/Listeners/TaxIncreasedNotification.php
public function handle(TaxIncreased $event)
{
    return $event;
}

4.イベントを定義

application/app/Events/TaxIncreased.php
public $name;
public $price;
public $taxRate;
public $taxIncludingPrice;

/**
 * TaxIncreased constructor.
 *
 * @param string $name
 * @param int $price
 * @param bool $isReduceTax
 */
public function __construct(string $name, int $price, bool $isReduceTax){
    $this->name  = $name;
    $this->price = $price;
    $this->taxRate = ($isReduceTax) ? 0.08 : 0.10;
    // 税込み価格計算
    $this->taxIncludingPrice = (int) $this->price * ( 1 + $this->taxRate);
}

5.イベントを呼び出す

今回はコントローラのアクションからイベントを呼び出す。

application/app/Http/Controllers/HomeController.php
 public function index()
 {
     $eatIn   = event(new TaxIncreased('タピオカ',1000,false))[0];
     $takeOut = event(new TaxIncreased('タピオカ',1000,true ))[0];

     return view('home',compact(['eatIn','takeOut']));
 }

結果

軽減税率イベントが発火した。

image01.png

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

Laravel5.8でSlackに通知(Notifications)を送ってみた話

はじめに

こちらの記事は、社内knowledgeからの移行記事になります。

初めての技術投稿で探り探りな面があります。

Laravelに搭載されている通知(Notifications)機能を使って、
ログインしたときにSlackに通知を送る機能を作成してみました。

不正ログイン通知や購入処理完了時のメッセージ送信などに使えそうですね。

今回のコード

【参考にした公式】
- 公式(英語)
- ReaDouble

なお、ログインイベントのリスナー設定は省略します。
GitHubにコードはあります。

環境

  • Vagrant(Homestead)
  • Laravel 5.8.23

Slackの設定

  1. 送信したいワークスペースの管理画面(https://[ワークスペース名].slack.com/apps) から、投稿したいチャンネルを選択してIncoming Webhookを追加する。

image01.png

  1. 追加後の画面からWebhook URLを回収する。 ***

image02.png

パッケージのインストール

composer require guzzlehttp/guzzle
composer require laravel/slack-notification-channel

通知クラスの作成

下記のコマンドでSlackという通知クラスが作成されます。
viaメソッドで複数定義するとどうなるかは試してみる。

php artisan make:notification Slack

通知クラス(App\Notifications\Slack)

Notification.php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\SlackMessage;

class Slack extends Notification
{
    use Queueable;

    protected $content;
    protected $channel;
    protected $name;

    /**
     * Create a new notification instance.
     * @param string $message
     * @return void
     */
    public function __construct(string $message)
    {
        $this->channel  = env('SLACK_CHANNEL');
        $this->name     = env('SLACK_NAME');
        $this->content  = $message;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['slack'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed $notifiable
     * @return SlackMessage
     */
    public function toSlack($notifiable)
    {
        return (new SlackMessage)
            ->from($this->name, ':ghost:')
            ->to($this->channel)
            ->content($this->content);
    }

}

ログインイベントのリスナーを作成

ログイン時に通知を送信するためのクラスを作成する。

ログイン イベントリスナークラス(App\Listeners\LoginListener)

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Http\Request;

use App\Notifications\Slack;
use App\Notification\Slack\SlackRepository;
use Illuminate\Support\Facades\Notification as FacNotification;

class LoginListener
{
    protected $slackRepository;

    /**
     * Create the event listener.
     *
     * @param Request $request
     * @param SlackRepository $slackRepository
     */
    public function __construct(Request $request, SlackRepository $slackRepository)
    {
        $this->request = $request;
        $this->slackRepository = $slackRepository;
    }

    /**
     * Handle the event.
     *
     * @param  Login  $event
     * @return void
     */
    public function handle(Login $event)
    {
        //ログイン成功時の処理

        // リポジトリで通知を送信します。
        $this->slackRepository->notify(new Slack("リポジトリ:{$event->user->name}"));

        // ファサードで通知を送信します。
        FacNotification::route('slack', env('SLACK_WEBHOOK_URL'))
            ->notify(new Slack("ファサード:{$event->user->name}"));

        // モデルで通知を送信します。
        $event->user->notify(new Slack("モデル:{$event->user->name}"));
    }
}

ログインすると...

絶対通知送信するマンに煽られる。:scream::scream::scream:

image03.png

まとめ

  • Laravelで通知機能を実装
  • クラスから通知を出す場合
    1. 通知を送りたいクラスでNotifiableをtrait
    2. artisanコマンドで通知クラスを作る
    3. 通知クラスにviaメソッド,to〇〇メソッドを実装
    4. 通知を送りたいクラスにrouteNotificationFor〇〇メソッドを実装
  • Notification ファサードを使う場合
use Illuminate\Support\Facades\Notification;

Notification::route($channel,$send)
                ->notify($clsNotification));

参考

その他の参考はこちら。

  1. RitoLabo - LaravelでSlack通知を実装する~ソーシャルではなく開発者/管理者としてのSlack通知~
  2. Laravel Notifications – Easily send quick updates through Slack, SMS, Email, and more
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】Carbonで今が上期下期を判断する

Laravelを用いてますが、Carboncomposerから入れれるので同じです。

上期は基本的に4月から9月末、下期は10月から3月末のようなので、そのように定義します。

今回はweb.phpにコールバックに書いていきます

web.php
Route::get('/',function(){
    $start = today()->month(4)->firstOfMonth();
    $end = today()->month(9)->endOfMonth();
});

上期の開始と終了を定義しました。
today()はLaravelに用意されているヘルパです。
new Carbon('today')と同等になります。

web.php
Route::get('/',function(){
    $start = today()->month(4)->firstOfMonth();
    $end = today()->month(9)->endOfMonth();
    echo today()->between($start , $end ) ? '上' : '下');
});

あとは、今日が上期の範囲内か判断しているだけです。

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

【Laravel】データベースを作成しよう

こんにちは!
今回はデータベースにデータを保存できるようにすることを最終地点として説明します
大まかな流れは以下の通りです
1. 環境変数を設定する
2. データベースを作成する
3. データベースに接続する
4. データベースにデータを保存する

では、始めます!!

環境変数の編集

環境変数を編集していきます
環境変数は使用するOS上でのルールを決めている変数というイメージです。
ここで編集するファイルは二つです

.envファイルの編集

.envファイルを編集します
下記のように編集していきましょう
データベースの名前はご自身のアプリケーション名に対応させてください
DB_USERNAMErootDB_PASSWORDsecretで設定しましょう

.env(一部) 編集前
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

.env(一部) 編集後
DB_DATABASE=laravel-project
DB_USERNAME=root
DB_PASSWORD=secret

database.phpの編集

.envに合わしてdatabase.phpも編集しましょう

config/database.php
'mysql' => [
//                     :
//                     :
     'database' => env('DB_DATABASE', 'laravel-project'),
     'username' => env('DB_USERNAME', 'root'),
     'password' => env('DB_PASSWORD', 'secret'),
//                     :
//                     :
],

データベース作成

MySQLにログインして、データベースを作成しましょう!
MySQLにrootユーザーでログインします。

$ mysql -u root

MySQLにログイン完了後、laravel-projectというデータベースを作成します

mysql> CREATE DATABASE `laravel-project`;

下記のコマンドでデータベースが作成されているか確認しましょう

mysql> show databases;

データベース接続

上で作成したデータベースに接続しましょう

ターミナル
$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.3.9-1+ubuntu18.04.1+deb.sury.org+1 — cli) by Justin Hileman
>>> DB::connection();
=> Illuminate\Database\MySqlConnection {#2891}

テーブル作成

マイグレーションして、テーブルを作成します。
マイグレーションする前に少し準備をします。

文字列の最大文字数を191文字にする

マイグレーション時にエラーになるので設定しましょう

app/Providers/AppServiceProvider.php
    public function boot()
    {
        \Schema::defaultStringLength(191);
    }

では、下記のコマンドでマイグレーションを行い、テーブルを作成しましょう

ターミナル
$ php artisan migrate

テーブルの作成が完了しました。

データ作成

一度に複数のデータを作成しましょう!!
まず、下記のコマンドを実行するとUsersTableSeeder.phpが生成されます。

ターミナル
$ php artisan make:seeder UsersTableSeeder

生成されたUsersTableSeeder.phpのRun関数内に下記のように追記しましょう

database/seeds/UsersTableSeeder.php
    public function run(){
        DB::table('users')->insert([
            'name' => 'test1',
            'email' => 'test1@test.com',
            'password' => bcrypt('test1') 
        ]);
    }

次に、DatabaseSeeder.phpのRun関数内をコメントアウトしてください

database/seeds/DatabaseSeeder.php
    public function run()
    {
        $this->call(UsersTableSeeder::class);
    }

上記で編集したファイルを元にデータを作成しましょう

$ php artisan db:seed --class=UsersTableSeeder

コンソールから作成されたデータを確認しましょう

$ php artisan tinker

>>> use App\User
>>> User::all()

上記のコマンドでテーブル内のデータが表示されます
表示されれば完了です!!

疑問、気になるところがございましたら、質問、コメントよろしくお願いします!!!

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

Laravelにおける複数フィールドの最低限必須のバリデーション

TL;DR

required_without_allルールを使いましょう。

required_without_allルールについてと挙動の確認

スクリーンショット 2019-11-27 11.43.08.png

公式ドキュメントを丹念に探せば見つかるのですが、わかりづらいバリデーションとして、
複数あるフィールドの中で、最低限ひとつは入力してもらいたい場合をチェックするバリデーションがあると思います。
例えば上のキャプチャのような、いくつかのSNSアカウントを入力するフィールドがあって、その中のどれかひとつでも入力してくれればオーケーみたいな場合です。
これはrequired_without_allルールでバリデーションできます。

https://laravel.com/docs/6.x/validation#rule-required-without-all
https://readouble.com/laravel/6.x/ja/validation.html#rule-required-without-all

指定した他のフィールドがすべて存在していない場合、このフィールドが存在し、かつ空でないことをバリデートします。

よく読めばこれが求めていたルールです! と納得するのですが、流し見程度だとちょっとわかりづらい。。
そこで今回はフォームリクエストのサンプルを作り、それをテストしてrequired_without_allルールの動きを確認します。
尚、Laravelのバージョンは6です。

$ php artisan make:request SampleFormRequest
Request created successfully.
$ php artisan make:test --unit SampleFormRequestTest
Test created successfully.
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SampleFormRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'qiita'     => 'required_without_all:github,twitter,instagram',
            'github'    => '',
            'twitter'   => '',
            'instagram' => '',
        ];
    }

    public function messages()
    {
        return [
            'qiita.required_without_all' => 'Qiita / GitHub / Twitter / Instagramの中で1個以上の項目を必ず入力してください。',
        ];
    }
}

required_without_allのパラメータで他の項目を羅列します。

<?php

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Support\Facades\Validator;
use App\Http\Requests\SampleFormRequest;

class SampleFormRequestTest extends TestCase
{
    /**
     * required_without_allルールのフォームリクエストのテスト
     *
     * @dataProvider validationDataProvider
     *
     * @param array $inputs
     * @param array $expected
     * @return void
     */
    public function testValidation(array $inputs, array $expected)
    {
        $request   = new SampleFormRequest();
        $validator = Validator::make($inputs, $request->rules(), $request->messages());
        $result    = $validator->passes();
        $errors    = $validator->errors();

        $this->assertEquals($expected['response'], $result);
        $this->assertEquals($expected['errors'], $errors->toArray());
    }

    public function validationDataProvider()
    {
        return [
            'エラー' => [
                [
                    'qiita'     => '',
                    'github'    => '',
                    'twitter'   => '',
                    'instagram' => '',
                ],
                [
                    'response' => false,
                    'errors'   => [
                        'qiita' => ['Qiita / GitHub / Twitter / Instagramの中で1個以上の項目を必ず入力してください。'],
                    ]
                ]
            ],
            '正常1' => [
                [
                    'qiita'     => 'kiyc',
                    'github'    => '',
                    'twitter'   => '',
                    'instagram' => '',
                ],
                [
                    'response' => true,
                    'errors'   => []
                ]
            ],
            '正常2' => [
                [
                    'qiita'     => '',
                    'github'    => 'kiyc',
                    'twitter'   => '',
                    'instagram' => '',
                ],
                [
                    'response' => true,
                    'errors'   => []
                ]
            ],
        ];
    }
}
$ vendor/bin/phpunit tests/Unit/SampleFormRequestTest.php 
PHPUnit 8.4.1 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 396 ms, Memory: 16.00 MB

OK (3 tests, 6 assertions)

すべて未入力だとエラーになり、ルールを指定したフィールドに入力がある場合、またはルールを指定していないフィールドに入力がない場合でもエラーにならないことを確認できました:beer:
このルールを見つけられなかったばかりに、私はわざわざカスタムルールを作ってしまったので、皆さん公式ドキュメントはしっかり読みましょう:scroll:

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