PHPにおけるシングルトンについて

はじめに

PHPのコードを色々触っている時に、シングルトンなるものに出会ったのでまとめます!

今回のシングルトンというものは、デザインパターンの一つです。
そのために、まずはデザインパターンについて、軽く紹介させていただきます!

デザインパターンとは

過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。引用元(Wikipedia)

プログラミングには、この場合はこの書き方が正解。というようなものがいくつかあります。そう言ったものをデザインパターンと呼びます。
その中の一つがシングルトンパターンです。

シングルトンパターンとは

とあるクラスのインスタンスを2つ以上作成できないようにし、どのクラスからアクセスしても同じインスタンスを参照されることを保証します。

1つしか作成できないように強制するような機能というような感じです。

設計者が「1箇所以外から呼び出したくない!」と考えていても、別の箇所から呼び出し可能であれば、設計者の意図をくみ取れないビギナーズが遠慮なく複数生成することがありますよね?(私がその中の1人です…汗)

この複数生成を防ぐためにあるのが、シングルトンです。

通常であれば、複数のインスタンスを作成することができるが、インスタンス作成を1つのみにしたい場合に使用します。

Q:いくつもインスタンスを作成したくない場合ってどんな状況?

設定管理

アプリケーションの設定情報を保持するクラスは、通常は一つのインスタンスだけで十分です。
設定情報はアプリケーション全体で共有されるため、複数のインスタンスを持つと各データの一貫性が失われる可能性があります。

データベース接続

データベース接続は、リソースを多く消費して、複数の接続を開くとパフォーマンスが低下する可能性があります。
シングルトンを使うことで、アプリケーション全体で一つのデータベース接続を共有し、リソースの使用を効率化できます。

ロギング

ログを記録するクラスは、アプリケーション全体で一つだけあれば充分です。
シングルトンを使用すると、ログの記録に一貫性が保たれ、パフォーマンスの問題を避けることができます。

ハードウェアインターフェースの管理

ハードウェアリソース(プリンターなど)へのアクセスを管理する場合、シングルトンは一つのインスタンスを通じてリソースを共有し、競合することを防ぎます。

キャッシュ

データキャッシュやオブジェクトキャッシュのようなリソースは、アプリケーション内で一貫性と効率を保つために、通常は単一のインスタンスで管理されます。

複数のインスタンスが存在すると、データの整合性の問題や無駄なリソースの使用、パフォーマンスの低下などさまざまな問題が発生します。
シングルトンは、これらの問題を解決する効果的な方法です。
ですが、使用する際にはその影響範囲と制約を理解して適切に利用できるよう意識することが大事です。

【Laravelでcronを実装してみた!】

今回は、特定の処理を決まった時間に定期的に実行したいと思い。
Laravelのcron機能を実装してみたので、その備忘録も兼ねて、まとめていきたいと思います。
(Laravelのscheduleクラスを利用しております。)
https://readouble.com/laravel/10.x/ja/scheduling.html
※今回のLaravel バージョンは10です。

目次
――――――――――――――――――――――――――――
① 「cron」とは
② 基礎
③ 発展
④ まとめ
――――――――――――――――――――――――――――

①『cron』とは
cronとは、「定期的にタスクを自動実行するための機能」です。
「クーロン」と読みます。cronを利用すると時間を指定して、あらかじめ決まった時間にタスクを実行してくれます。

以下、「cron」の使用例です。
例)毎分ごとに特定のディレクトリを参照し、ファイルを読み込みたい。
例)日付が変わるタイミングで特定の処理をしたい。
例)毎月初めにステータスをリセットしたい。

②基礎
[1]まずは実行するプログラムを作成します。
・Laravelのコマンドでファイルを作成します。
php artisan make:command SendEmails

上記のコマンドにより、Commandsディレクトリにファイルが作成されます。
project
├─ app
│ ├─ Console
│ │ ├─ Commands
│ │ │ └─ SeendEmails
│ │ ├─ Kernel.php

[2]つぎにファイルを追記していきます。
・「$signature」変数にコマンド名を格納します。
例)protected $signature = ‘command:sendEmails’;

・「$description」変数に処理の内容を格納します。
例)protected $description = ‘メールを配信する。’;

・「handle()」メソッド内に処理を記載します。
例)public function handle() {

{
ここにしたい事を書く。
※今回は割愛します。
}

以上の手順にて、実行するプログラム(コマンド)を作成することができます。

③発展
基礎にてコマンドを作成したので、発展では定期的に実行できるようにタスクスケジュールをしていきましょう。

[1] Kernelファイルにスケジュールを記載する。
・ConsoleフォルダにあるKernel.phpファイルに追記します。
project
├─ app
│ ├─ Console
│ ├─ Commands
│ │ └─ SeendEmails
│ ├─ Kernel.php

・Kernal.phpの$commandsに追記する。
例)protected $commands = [
\App\Console\Commands\SendEmails::class,
];

・Kernal.phpのschedule()メソッドにてスケジュールを指定できる。
例)protected function schedule(Schedule $schedule)
{
$schedule->command(‘command:sendEmails’)->everyTenMinutes();
}

以上の設定にて、10分ごとにsendEmailsコマンドを実行するように設定することができます。

[2] スケジュールの種類

メソッド 説明
cron('* * * * *');
カスタムcronスケジュールでタスクを実行
everySecond();
毎秒タスク実行
everyTwoSeconds();
2秒毎にタスク実行
everyFiveSeconds();
5秒毎にタスク実行
everyTenSeconds();
10秒ごとにタスク実行
everyFifteenSeconds();
15秒毎にタスク実行
everyTwentySeconds();
20秒ごとにタスク実行
everyThirtySeconds();
30秒ごとにタスク実行
everyMinute();
毎分タスク実行
everyTwoMinutes();
2分毎にタスク実行
everyThreeMinutes();
3分毎にタスク実行
everyFourMinutes();
4分毎にタスク実行
everyFiveMinutes();
5分毎にタスク実行
everyTenMinutes();
10分毎にタスク実行
everyFifteenMinutes();
15分毎にタスク実行
everyThirtyMinutes();
30分毎にタスク実行
hourly();
毎時タスク実行
hourlyAt(17);
1時間ごと、毎時17分にタスク実行
everyOddHour($minutes = 0);
奇数時間ごとにタスク実行
everyTwoHours($minutes = 0);
2時間毎にタスク実行
everyThreeHours($minutes = 0);
3時間毎にタスク実行
everyFourHours($minutes = 0);
4時間毎にタスク実行
everySixHours($minutes = 0);
6時間毎にタスク実行
daily();
毎日深夜12時に実行
dailyAt('13:00');
毎日13:00に実行
twiceDaily(1, 13);
毎日1:00と13:00に実行
twiceDailyAt(1, 13, 15);
毎日1:15と13:15に実行
weekly();
毎週日曜日の00:00にタスク実行
weeklyOn(1, '8:00');
毎週月曜日の8:00に実行
monthly();
毎月1日の00:00にタスク実行
monthlyOn(4, '15:00');
毎月4日の15:00に実行
twiceMonthly(1, 16, '13:00');
毎月1日と16日の13:00にタスク実行
lastDayOfMonth('15:00');
毎月最終日の15:00に実行
quarterly();
四半期の初日の00:00にタスク実行
quarterlyOn(4, '14:00');
四半期の4日の14:00に実行
yearly();
毎年1月1日の00:00にタスク実行
yearlyOn(6, 1, '17:00');
毎年6月1日の17:00にタスク実行
timezone('America/New_York');
タスクのタイムゾーンを設定

④まとめ
このようにLaravelでは、スケジュール機能にてcron機能を実装することができます。
とても便利な機能なので、ぜひ使用してみてはいかがでしょうか。

【Laravelの機能についてまとめてみよう】~~~Model編~~~

今回は、【Laravelの機能についてまとめてみよう】第二弾です。
第二弾は….. 『Model』についてです!!

※Laravel バージョンは8をベースにしております。

目次

――――――――――――――――――――――――――――
① Modelとは
② 作成方法
③ 使用方法
④ まとめ
――――――――――――――――――――――――――――

① Model とは

Model(モデル)はデータベースとデータをやりとりするために作成します。
モデルを使うことで、LaravelのORMであるEloquentの機能を使って、データの取得や追加・更新などを行うことができます。

以下のサイトも参考にしてみてください。
↓↓※公式リファレンスを日本語訳しているサイト↓↓
https://readouble.com/laravel/8.x/ja/eloquent.html

② 作成方法

最初にModelクラスの作成からはじめましょう。

直接ファイルを作成することも出来ますが、今回はLaravelの機能『artisan』コマンドを使用して作成していきます。

project
├─ app
│ ┗Models
│   ┗Test.php (※ここにファイルが生成される)
├─ 略(config database public 等)

作成されたファイルを確認します。

【Test.php】

< ?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Test extends Model
{
use HasFactory;
}

※Artisanコンソールについての詳細は以下のサイトをご覧ください。
https://readouble.com/laravel/8.x/ja/artisan.html

③ 使用方法

次に、Laravel(Model)とDBの紐づけを行っていきましょう。

[1] Modelクラスの命名規則
・Modelクラスの名前は紐づけたいテーブルに対して単数形でかくように決められています。

今回はテーブル名がtestsを使用するので、Modelの名前をtestとします。
例)テーブル名が『users』の場合。→Modelは『user』となる。

・決められたModel名以外のテーブルを使用したい場合。
$tableプロパティを作成し、そこで定義する。
例)『samples』テーブルと紐づけたい場合

protected $table ='samples'

[2] データの登録設定
・Laravelでデータを追加するためにcreateメソッドを使用しますが、使うためにはfillableかguardedの設定が必要です。
例)登録可能にしたいカラムを全て記載します。指定されたカラム以外は登録できないようになります。

protected $fillable = [
'name',
'password',
'email',
'tell'
];

④ まとめ
このようにルールに従い、Modelを作成することでEloquentを利用してテーブルを操作することが可能です。

以上、簡単にではありますが大まかなModelについてまとめてきました。
Modelを深堀りするとまだまだまとめきれてないことが多いので
次の機会にもっと深く紹介できればと思います!

【Laravelとトロッコを連携させてデータをExcelに出力する方法】

今回は、【Laravelにてトロッコを使用したデータの出力】について実装していきましょう。

※Laravel バージョンは9をベースにしております。

目次

――――――――――――――――――――――――――――

①前提

②実装

③発展

――――――――――――――――――――――――――――

①前提に

トロッコとは?・・・trocco®は、ETL/データ転送・データマート生成・ジョブ管理・データガバナンスなどの
データエンジニアリング領域をカバーした、分析基盤構築・運用の支援SaaSです。

・「/trocco」のアクセスにて処理の実行していきます。
 ※シンプルに実装したいため、今回はControllerなどは使わずにRouteの中で実装します。

・トロッコにcsvでデータを渡し、そこからExcelにデータを渡し出力していきます。

・文字コードは「UTF-8」を使用します。※Laravelではcsvの出力はデフォルトで「UTF-8」になってます。

②実装

では、実際に処理を見ていきましょう。

Route::get('/trocco', function () {
$callback = function() {
$csv = fopen('php://output', 'w');
$users = User::get(['id','name','email']);
foreach ($users as $user) {
fputcsv($csv, [
$user->id,
$user->name,
$user->email,
]);
}
fclose($csv);
};
return response()->stream($callback, 200, [
'Content-Type' => 'text/csv',
]);
});
Route::get(‘/trocco’, function () {
┗https://○○/troccoにアクセスがあった時の処理を記載します。

$callbackの中でcsvファイルを作成していきます。
$csv = fopen(‘php://output’, ‘w’);
┗ファイルを開きます。

$users = User::get([‘id’,’name’,’email’]);
┗Modelを使用し、userテーブルのidと名前、メールアドレスを取得します。

foreach ($users as $user) {
┗件数分繰り返し処理を行います。

fputcsv($csv, [$user->id,$user->name,$user->email,]);
┗開いたファイルに第二引数の配列を書き込みます。

fclose($csv);
┗ファイルを閉じてcsvファイルの完成です。

最後にreturn response()->stream($callback, 200, [‘Content-Type’ => ‘text/csv’,]);  にてcsvのファイルを『$callback』にて作成し返します。

③発展

このまま実装したままだと、誰でもアクセスされるとユーザー情報が取れてしまいます。

セキュリティ的に問題が大ありなので、アクセスにIP制限をかけたいとおもいます。

IP制限の実装は簡単で、【Route::get(‘/trocco’, function () {】の中の一番上に以下の処理の追加で制限をかけることができます。

// IPアドレス許可リスト


$white_list = [

'○○.○○○.○○.○○○',
'○○.○○○.○○.○○○',
'○○.○○○.○○.○○○',

];

if (config('app.env') != 'local' && ! in_array(request()->ip(), $white_list)) {
abort(403);
}

 

以上でLaravelにてトロッコを使用したデータの出力機能の完成です!!
いつの日か、トロッコについてもまとめてみようと思います。

【Laravelの機能についてまとめてみよう】~~~Routing編~~~

laravel

今回は、【Laravelの機能についてまとめてみよう】第一弾です。
第一弾は….. 『Routing』についてです!!

※Laravel バージョンは8をベースにしております。

目次

――――――――――――――――――――――――――――
① Routingとは
② 基礎
③ 発展
④ まとめ
――――――――――――――――――――――――――――

① Routing とは

Routing(ルーティング)は特定のURLにアクセスされたときに「どのような処理をするのか」といったことを定義することです。
例)https://*** といったサイトがあるとします。
https://***/loginにアクセスされた場合にどう処理をするのか指定することができます。
以下のサイトも参考にしてみてください。
↓↓※公式リファレンスを日本語訳しているサイト↓↓
https://readouble.com/laravel/8.x/ja/routing.html

② 基礎

すべてのLaravelルートは、routesディレクトリにあるルートファイルで定義します。これらのファイルはApp\Providers\RouteSerciveProviderに記載されているのもを自動的に読み込んでいます。

デフォルトでは、以下の4つのファイルがあります。それぞれみていきましょう。
project
├─ app
├─ 略(config database public 等)
├─ resoures
├─ routes
│  ├─ api.php
│  ├─ channels.php
│  ├─ console.php
│  └─ web.php

【api.php】
api.phpには、APIとしてステートレスに使いたいルートを定義します。
記載例)

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

※ここで定義したルートはapiミドルグループウェアに割り当てられ、URIに /api というプレフィックスがつきます。

【channels.php】
channels.phpは、ブロードキャストのチャンネルを登録するために使います。
記載例)

Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

※ブロードキャストについての詳細は以下のサイトをご覧ください。
https://readouble.com/laravel/8.x/ja/broadcasting.html

【console.php】
console.phpは、コンソールベースのエントリポイントを定義するために使います。
記載例)

Artisan::command('inspire', function () {
    $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

※Artisanコンソールについての詳細は以下のサイトをご覧ください。
https://readouble.com/laravel/8.x/ja/artisan.html

【web.php】
web.phpには、Webからアクセスしてほしいルートを定義します。
記載例)

Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('login', [LoginController::class, 'showLoginForm');
Route::post('login', [LoginController::class, 'login');

※ここで定義したルートはwebミドルグループウェアに割り当てられ、セッション状態やCSRF保護などの機能が提供されます。例えばPOSTメソッドで定義したルートはCSRF保護され、正しいCSRFトークンを含まないリクエストは拒否されます。詳細は以下のサイトをご覧ください。
https://readouble.com/laravel/8.x/ja/routing.html

③ 発展

基礎はデフォルトで用意されているRoutingファイルをみていきました。
発展では独自のカスタムRoutingファイルの作成について調べていきましょう!

デフォルトのRouteファイルは4つあるものの、実際に処理を記載するのは、ほぼweb.phpになります。
そこで、web.phpの肥大化を防ぐために新たなRouteファイルを追加して処理を分ける必要があります。その方法をまとめていきたいと思います。
(※今回はwebと同じmiddleware群を使用します。)

[1] Route定義ファイルの追加
project
├─ 略(app config database public resources 等)
├─ routes
│  ├─ api.php
│  ├─ channels.php
│  ├─ console.php
│  ├─ web.php
│  └─ sample.php (追加ファイル)

[2] サービスプロバイダに追加ファイルの登録
Routeを管理している App\Providers\RouteServiceProvider.phpのbootメソッドにある、$this->routes();のコールバックfunctionにsampleのRouteを渡す。
例)

$this->routes(function () {
    Route::middleware('api')
        ->prefix('api')
        ->group(base_path('routes/api.php'));
                         
    Route::middleware('web')
        ->group(base_path('routes/web.php'));
                         
    Route::prefix(sample)
        ->as('sample.')
        ->middleware('web')
        ->group(base_path('routes/sample.php'));
});

※webとは違うmiddleware群を使用したい場合は、App\Http\Kernel.phpの$middlewareGroupsに新しく記載する。
例)webのmiddlewareGroups

'web' => [
    \App\Http\Middleware\EncryptCookies::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \App\Http\Middleware\VerifyCsrfToken::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

④ まとめ

このようにURLを指定したり、フォームからデータ送信する際に「処理先(ルート)」を決めるのがLaravelのRoutingとなります。

以上、簡単にではありますが大まかなRoutingについてまとめてきました。
Routeを深堀りするとまだまだまとめきれてないことが多いので
次の機会に紹介できればと思います!

『CakePHP』1から3へのVersion UP

今回はCakePHP1からCakePHP3へのバージョンUPに際して、大きく変更となっている箇所をまとめていきたいと思います。
たまたま、このような期会があったため備忘録程度に記載してます。
※記載しているのが全ての変更点ではありません。

公式のマニュアル
CakePHP1→ 【 https://book.cakephp.org/1.3/ja/index.html
CakePHP3→ 【 https://book.cakephp.org/3/ja/index.html

■フォルダ構成

CakePHP1
app
┣config
controllers
models
┣plugins
┣tmp
┣vendors
views
cake
┗VERSION.txt
etc
public_html (DocumentRoot)
vendor
composer.json

CakePHP3
bin
┗cake(migration実行ファイルあり)
config
┗設定ファイル
Logs
┗ログファイル置き場
plugins
src
┣Console
Controller
┣Form
Model
┣Shell
Template (view)
┗View(helper置き場)
tests
tmp
vendor
┗cakephp
┗cakephp
┗VERSION.txt
webroot(DocumentRoot)
composer.json

■Controller

Viewからのnameの値を調べる

CakePHP1
if(isset($this->params[‘form’][‘nameの値’])){

CakePHP3
if(isset($_POST[‘nameの値’])){

Component Ajaxかどうか調べる

CakePHP1
$this->RequestHandler->isAjax()

CakePHP3
$this->request->is(‘ajax’)

DBにデータを新規登録

CakePHP1
$this->Post->create($this->request->data);
$this->Post->save();

CakePHP3
$post = $this->Posts->newEntity($this->request->data);
$this->Posts->save($post);

Sessionの使用方法

CakePHP1
$this->Session->○○()

CakePHP3
$this->request->session()->○○()

Modelオブジェクトの取得

CakePHP1
$posts = ClassRegistry::init(‘posts’);

CakePHP3
$posts = TableRegistry::get(‘posts);

Redirectについて

CakePHP1 
$this->redirect(‘’);があるとそこで処理終了、リダイレクトされる。

CakePHP3
$this->redirect(‘’);があっても後ろの処理も(最後まで)実行された後リダイレクトされる。

■Model

CakePHP1
model sample.php

CakePHP3
Table samplesTable.php
Entity sampleEntity.php

データの取得

CakePHP1
$posts->id = $id;
$post = $posts->read();

CakePHP3
$post = $posts->get($id);

■View

ヘルパーの呼出し

CakePHP1
<?php echo $form->create(‘○○);?>

CakePHP3
<?php echo $this->Form->create(‘○○));?>>

コントローラからの関数名を判定

CakePHP1
<?php if($this->action == ‘edit’):?>

CakePHP3
<?php if($this->request-&g;taction == ‘edit’):?>

—————-
以上、Cake1とCake3での
【フォルダ構成】
【Controller】
【Model】
【View】
についてまとめてみました。

PHPのTraitの使い所


今回はPHPでTrait(トレイト)を使う⽅法について解説します。

Traitとは?

まずはじめに、Traitとはどういうものかを公式サイトをもとに説明します。

PHP は、コードを再利⽤するための「トレイト」という仕組みを
実装しています。
トレイトは、PHP のような単⼀継承⾔語でコードを再利⽤するための仕組みのひとつです。 トレイトは、単⼀継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独⽴したクラスで再利⽤できるようにします。 トレイトとクラスを組み合わせた構⽂は複雑さを軽減させてくれ、 多重継承や Mixin に関連するありがちな問題を回避することもできます。
トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのものです。 トレイト⾃⾝のインスタンスを作成することはできません。 昔ながらの継承に機能を加えて、振る舞いを⽔平⽅向で構成できるようになります。 つまり、継承しなくてもクラスのメンバーに追加できるようになります。

https://www.php.net/manual/ja/language.oop5.traits.php

機能(メソッド)を再利⽤できるようにまとめる仕組みです。

Traitの書き⽅

■定義⽅法

trait トレイト名 {
  function メソッド名1() {
    // 処理
  }
  function メソッド名2() {
    // 処理
  }
}

■利⽤⽅法

class クラス名 {
  use トレイト名;
}

■Laravelでの利⽤

Laravel内でもTraitは多く利⽤されており、会員登録やログイン周り・通知など、WEBサービスを構築する上でよく使う機能を予めまとめておくことで、コードが煩雑になるのを防ぎ、可読性が向上します。

<?php
namespace App\Http\Controllers\Auth;

use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;

class RegisterController extends Controller
{
  /*
  |--------------------------------------------------------------------------
  | Register Controller
  |--------------------------------------------------------------------------
  |
  | This controller handles the registration of new users as well as their
  | validation and creation. By default this controller uses a trait to
  | provide this functionality without requiring any additional code.
  |
  */
  use RegistersUsers;

... 省略 ...

Laravel8 + Vue + Pusherにてチャット構築

Laravel8 + Vueでのチャット機能を簡単にご紹介します。

開発環境

PHP v8.0.13
Laravel v8.73.2
npm v8.1.0

本記事ではPusherというサービスを使って構築していきます。
Laravel環境構築や、Pusherへの会員登録については他記事などを参考にしてみてください。

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

$ composer require laravel/ui

Laravel UIをインストール。

$ php artisan ui vue --auth

スカフォールドを各フレームワークから選択。(今回はVue.jsを使用)

$ npm install

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

$ npm run watch

コンパイルの自動化。

$ php artisan migrate

マイグレーション実行。

Pusher連携部分の実装

App\Providers\BroadcastServiceProvider::class,

ブロードキャストを有効にするため、 config/app.php の上記コメントアウトを外す。

$ composer require pusher/pusher-php-server

Pusher用のパッケージのインストール。

$ npm install --save laravel-echo pusher-js

Laravel Echo用のパッケージをインストール。

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    forceTLS: true
});

resources/js/bootstrap.js の上記コメントアウトを外し、Laravel Echoを有効にします。

モデルの作成

php artisan make:model Message -m
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateMessagesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->char('id', 36)->primary()->comment('ID');
            $table->char('user_id', 36);
            $table->text('message');
            $table->dateTime('deleted_at')->nullable()->comment('削除日時');
            $table->dateTime('created_at')->nullable()->comment('作成日時');
            $table->dateTime('updated_at')->nullable()->comment('更新日時');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('messages');
    }
}

作成されたマイグレーションファイルを上記のように修正。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
    use HasFactory;

    protected $primaryKey = 'id';
    protected $keyType = 'string';
    public $incrementing = false;

    protected $fillable = ['user_id', 'message'];

    protected static function boot()
    {
        parent::boot();
        static::creating(function ($model) {
            $model->{$model->getKeyName()} = (string) \Str::uuid();
        });
    }

}

作成された app/Models/Message.php を上記のように修正。

public function messages()
{
    return $this->hasMany('App\Model\Message');
}

UserモデルにhasManyを追加。

$ php artisan migrate

マイグレーション実行。

Routeを追加

use App\Http\Controllers\MessageController;
...
Route::group(['middleware' => ['auth']], function () {
    Route::get('chat', [MessageController::class, 'index']);
    Route::get('messages', [MessageController::class, 'get']);
    Route::post('messages', [MessageController::class, 'send']);
});

routes/web.php に上記を追加。

Controllerを追加

$ php artisan make:controller MessageController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Events\MessageSentEvent;
use App\Models\Message;

class MessageController extends Controller
{
    public function index()
    {
        return view('chat');
    }

    public function get()
    {
        return Message::with('user')->get();
    }

    public function send(Request $request)
    {
        $user = Auth::user();
 
        $message = $user->messages()->create([
            'message' => $request->input('message')
        ]);
 
        event(new MessageSent($user, $message));
 
        return ['status' => 'Message Success!'];
    }
}

Eventの作成

$ php artisan make:event MessageSentEvent
<?php

namespace App\Events;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\Message;

class MessageSentEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public $message;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(User $user, Message $message)
    {
        $this->user = $user;
        $this->message = $message;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('test');
    }
}
return new PrivateChannel('test');

公開チャンネルにしたい場合、上記を Channelに変更することで対応可能。
また、 ‘test’ 部分を書き換えることで複数チャンネルにも対応可能。

Broadcast::channel('chat', function ($user) {
    return Auth::check();
});

今回はプライベートチャンネルを実装したため、 routes/channels.php にて認証チェックを行う。

Viewの作成

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
 
<head>
    <meta charset="UTF-8">
    <title>テストチャット</title>
    <link href="{{ mix('css/app.css') }}" rel="stylesheet" type="text/css">
    <meta name="csrf-token" content="{{ csrf_token() }}">
</head>
 
<body>
    <div id="app">
        <chat-component></chat-component>
    </div>
 
    <script src="{{ mix('js/app.js') }}"></script>
</body>
 
</html>

resources/views/chat.blade.php

<template>
    <div>
        <ul>
            <li v-for="(message, key) in messages" :key="key">
                <strong>{{ message.user.name }}</strong>
                {{ message.message }}
            </li>
        </ul>
        <input v-model="text" />
        <button @click="postMessage" :disabled="!textExists">Submit</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            text: '',
            messages: []
        };
    },
    computed: {
        textExists() {
            return this.text.length > 0;
        }
    },
    created() {
        this.getMessages();
        Echo.private('test').listen('MessageSentEvent', e => {
            this.messages.push({
                message: e.message.message,
                user: e.user
            });
        });
    },
    methods: {
        getMessages() {
            axios.get('/messages').then(response => {
                this.messages = response.data;
            });
        },
        postMessage(message) {
            axios.post('/messages', { message: this.text }).then(response => {
                this.text = '';
            });
        }
    }
};
</script>

resources/js/components/ChatComponent.vue

Pusherの設定

Name your app : アプリ名
Select a cluster : 日本で使用する場合、そのままでOK
Front end : Vue.js
Back end : PHP

Pusherにログイン後、「Create my app」画面から各種情報を入力して登録。

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

登録が完了したら「App Keys」メニューから必要情報をコピーし、 .env ファイルに追記。

ログイン後、 /chat にアクセスすることでチャットが行えます。

Laravelの知ってると便利な機能

laravel
エイブリッジでは、Web開発の際、開発言語にPHP、フレームワークによくLaravelを使用しています。
いろいろあるPHPフレームワークの中で世界でもっとも人気であると言っても良いと思います。

そこで、知っているとちょっと便利なテクニックを紹介したいと思います。
(Laravel8をベース)

■データ取得編

日付で抽出

User::whereDate('created_at', '2021-11-11')->get();

年で抽出

User::whereYear('created_at', '2021')->get();

月で抽出

User::whereMonth('created_at', '11')->get();

日で抽出

User::whereDay('created_at', '11')->get();

NULLは最後になるようにソートする

Post::orderByRaw('published_at IS NULL ASC')
->orderBy('published_at')->get();

日付で整列

// 未指定の場合はcreated_atの降順となる。
Post::latest()->get();

// カラム名を指定するとそのカラムの降順となる。
Post::latest('published_at')->get();

Modelのタイムスタンプのみを更新

User::find(1)->touch();

JSON配列を検索する

/*
+-----------------------------------------------------------------------------------------------+
| id | tags |
+-----------------------------------------------------------------------------------------------+
| 1 | ["Android","iOS","Web"] |
+-----------------------------------------------------------------------------------------------+
*/
$tag = 'Web';
$posts = Post::whereRaw("JSON_CONTAINS(tags,'["{$tag}"]')")->get();

/*
+-----------------------------------------------------------------------------------------------+
| id | tags |
+-----------------------------------------------------------------------------------------------+
| 1 | [{"id":1,"tag":"Android"},{"id":2,"tag":"iOS"},{"id":2,"tag":"Web"}] |
+-----------------------------------------------------------------------------------------------+
*/
$tag = 'Web';
$posts = Post::whereRaw("JSON_CONTAINS(tags,'{"tag":{$tag}}')")->get();

■Eloquent編

Modelの変更された項目を確認する

$user = App\Models\User::first();
$user->name = ’taro yamada’;
$user->age = 30;

$result = $user->getChanges();
logger($result);

/* 結果:
array(
"name" => "taro yamada", "age" => 30
);
*/

Modelの元の値を確認する

$user = App\Models\User::first();
logger($user->name);

// 結果:hanako yamada

$user->name = ’taro yamada’;

$result = $user->getOriginal('name');
logger('original: '.$user->name);

// 結果:hanako yamada

属性のキャスト
モデルでキャストを設定しておくと、指定した型で返却されます。
例えば、日付項目に文字列で日付を設定しても、getするときは日付型で返却されます。

class Post extends Model
{
protected $casts = [
'category_no' => 'integer'
'published_at' => 'datetime'
];
}

インクリメント/デクリメント

$user = User::find($id);

// インクリメント(+1)
$user->increment('login_count');
// デクリメント(-1)
$user->decrement('login_count');
// インクリメント(+5)
$user->increment('login_count', 5);
// デクリメント(-5)
$user->decrement('login_count', 5);