タグ「Laravel」の 記事 6 件中 1 ~ 6 件を表示しています。

カレンダー UI を実装している時にその日が祝日かどうかまたはその日の祝日名が欲しいことがあると思う。 これが結構大変なのだが、私の場合はコジごみカレンダーというアプリで祝日判定に関して実装していたので、それを移植して祝日名を返すようにしたらできた。 2021 年は山の日、スポーツの日、海の日が特別ルールになっていたりといろいろと面倒だ。 あと振替休日判定が面倒。 ただ単に前の日が日曜日だったら、という判定では足りない。 尚 Laravel での実装前提なので日付を格納するインスタンスのクラスとして Carbon を使用している。 例えば素 PHP の場合は適当に $year $month $day などの int 型変数に書き換えればいいだろう。 また、今どきはこういう実装は JavaScript 側にすることが多いと思う。 そちらへの移植も難しくないだろう。

    /**
     * その日が祝日であれば祝日名を返す.
     * 祝日でなければ null を返す.
     *
     * @param Carbon $carbon 対象日付
     * @return string|null 祝日名 (祝日でない場合 null)
     */
    private function getHolidayName(Carbon $carbon): ?string
    {
        list($y, $m, $d, $w) = [$carbon->year, $carbon->month, $carbon->day, $carbon->dayOfWeek];
        if ($m === 1 && $d === 1) {
            return '元旦';
        } elseif (($y < 2000 && $m === 1 && $d === 15) || ($y > 1999 && $m === 1 && $d >= 8 && $d <= 14 && $w === Carbon::MONDAY)) {
            return '成人の日';
        } elseif ($m === 2 && $d === 11) {
            return '建国記念の日';
        } elseif (($y > 2018 && $m === 2 && $d === 23) || ($y > 1988 && $y < 2019 && $m === 12 && $d === 23)) {
            return '天皇誕生日';
        } elseif ($this->isShunbun($y, $m, $d)) {
            return '春分の日';
        } elseif ($m === 4 && $d == 29) {
            return '昭和の日';
        } elseif ($m === 5 && $d === 3) {
            return '憲法記念日';
        } elseif ($m === 5 && $d === 4) {
            return 'みどりの日';
        } elseif ($m === 5 && $d === 5) {
            return 'こどもの日';
        } elseif (($y > 1995 && $y < 2003 && $m === 7 && $d === 20) || ($y > 2002 && $y !== 2021 && $m === 7 && $d >= 15 && $d <= 21 && $w === Carbon::MONDAY) || ($y === 2021 && $m === 7 && $d === 22)) {
            return '海の日';
        } elseif (($y > 2015 && $y !== 2021 && $m === 8 && $d === 11) || ($y === 2021 && $m === 8 && $d === 8)) {
            return '山の日';
        } elseif (($y < 2003 && $m === 9 && $d === 15) || ($y > 2002 && $m == 9 && $d >= 15 && $d <= 21 && $w === Carbon::MONDAY)) {
            return '敬老の日';
        } elseif ($this->isShubun($y, $m, $d)) {
            return '秋分の日';
        } elseif (($y < 2000 && $m === 10 && $d === 10) || ($y > 1999 && $y !== 2021 && $m === 10 && $d >= 8 && $d <= 14 && $w === Carbon::MONDAY) || ($y === 2021 && $m === 7 && $d === 23)) {
            return $y > 2019 ? 'スポーツの日' : '体育の日';
        } elseif ($m === 11 && $d === 3) {
            return '文化の日';
        } elseif ($m === 11 && $d === 23) {
            return '勤労感謝の日';
        } else {
            return null;
        }
    }

    /**
     * 春分の日かどうかを判定して返す.
     *
     * @param int $y 年
     * @param int $m 月
     * @param int $d 日
     * @return bool 春分の日かどうか
     */
    private function isShunbun(int $y, int $m, int $d): bool
    {
        $arg1 = $y < 1980 ? 20.8357 : 20.8431;
        $arg2 = $y < 1980 ? 1983 : 1980;
        return $m === 3 && floatval($d) === floor($arg1 + 0.242194 * ($y - 1980) - floor(($y - $arg2) / 4.0));
    }

    /**
     * 秋分の日かどうかを判定して返す.
     *
     * @param int $y 年
     * @param int $m 月
     * @param int $d 日
     * @return bool 秋分の日かどうか
     */
    private function isShubun(int $y, int $m, int $d): bool
    {
        $arg1 = $y < 1980 ? 23.2588 : 23.2488;
        $arg2 = $y < 1980 ? 1983 : 1980;
        return $m === 9 && floatval($d) === floor($arg1 + 0.242194 * ($y - 1980) - floor(($y - $arg2) / 4.0));
    }

    /**
     * 対象日が振替休日かを判定して返す.
     *
     * @param Carbon $target 対象日
     * @return bool 振替休日かどうか
     */
    private function isExtra(Carbon $target): bool
    {
        // 前日が日曜かつ祝日であるならば振替休日
        $carbon = $target->copy();
        $carbon->subDay();
        if ($this->getHolidayName($carbon) !== null) {
            if ($carbon->dayOfWeek === Carbon::SUNDAY) {
                return true;
            }

            // 2007 年以後では日曜の祝日が発生した場合次の平日が振替休日となる: GW の振替休日判定が増える
            if ($carbon->year > 2006) {
                // 前日と後日が祝日であったならば挟まれた日も祝日
                $carbon->addDays(2);  // 対象日の後日に移動
                if ($this->getHolidayName($carbon) !== null) {
                    return true;
                }

                // 前々日から日曜の祝日を順に探索. 日曜でなく祝日だけ満たしていたらその前の日を探索し続ける. 祝日でなくなったら終了
                $carbon->subDays(3);  // 後日から前々日へシフト
                while ($this->getHolidayName($carbon) !== null) {
                    if ($carbon->dayOfWeek === Carbon::SUNDAY) {
                        return true;
                    }
                    $carbon->subDay();
                }
            }
        }
        return false;
    }

これを以下のように使用できる:

$carbon = Carbon::now();
$holidayName = $this->getHolidayName($carbon) ?? ($this->isExtra($carbon) ? '振替休日' : '');

$holidayName には祝日名もしくは振替休日という文字列、対象でない場合は null が格納される。

プロジェクトごとに Vagrant インスタンスを用意したほうが便利

2 年前に同様の記事を書いたのだが、当時は Vagrant インスタンスをローカルマシンに 1 つインストールしてそれを複数のプロジェクトで使い回す想定でいた。 いろいろ試行錯誤した結果、それよりも 1 つのプロジェクトごとに Vagrant インスタンスを別々に用意したほうがはるかに便利なことに気がついた。 前者だと複数のプロジェクトのインスタンスを同時に立ち上げる必要がある場合に 1 つだけ立ち上げれば済むのでリソース使用量が少なくて良いというのがあるが、そんなケースはほとんど発生しない。 大体がプロジェクト A に携わっている日にはプロジェクト A の仕事しかしない。 それよりは後者の方が仮想マシンが完全に分かれているので単純で安心だ。

前提

以下はインストールされているものとする:

  • PhpStorm (IntelliJ IDEA Ultimate で PHP プラグインを使用している状態でもよい)
  • Pleiades (PhpStorm 日本語化)
  • PHP 7.4 (PATH が通されており php.ini の設定も済んでいる状態)
  • Composer
  • Vagrant (Virtual Box)
  • Cygwin (PhpStorm のコンソールでも良いかも)

PhpStorm 上で Laravel プロジェクト作成

  1. PhpStorm で ファイル -> 新規プロジェクト
  2. プロジェクトの種類の中から Composer プロジェクトを選択
  3. ロケーションを適切に入力
  4. 'composer 実行可能ファイル' を選択
  5. パッケージに laravel/laravel と入力
  6. インストールするバージョンは <default> を選択。バージョン指定したい場合はそのようにする
  7. 作成を押下すると Composer の処理が走るのでしばらく待つ (2020/09/10 現在なぜか失敗する……が問題ない)

2020/09/10 現在 <default> 選択すると Laravel 8 がインストールされる。 いつからそうなったのかは分からないが、今の Laravel プロジェクトを Composer からインストールすると Laravel 側の .git ディレクトリも入ってしまう。 これだと自分の開発した Git のヒストリーと Laravel 本体のヒストリーが混じってしまう。 rm -fR .git で一旦削除した上で改めて Git 管理下に置きたい。

プロジェクトごとの Vagrant インスタンス

Laravel 公式の Homestead の説明プロジェクトごとにインストールのところをそのまま実施する。

composer require laravel/homestead --dev
vendor\bin\homestead make
vagrant up  # Box のダウンロードに時間がかかる

.gitignore

ルートの .gitignore に Homestead 関連と IntelliJ IDEA 関連のファイルを追記:

.idea/
.vagrant
after.sh
aliases
Vagrantfile
.phpstorm.meta.php
_ide_helper.php
_ide_helper_models.php

ちょっと前から Google Chrome などのブラウザで従来の HTTP 接続だとこのサイトへの接続は保護されていませんと警告表示が出るようになった。 この Blog では特にセンシティブな内容を扱ってはいるわけではないのだが、何となく気になってはいた。 しかし従来は SSL 証明書は有料が当たり前だったので自分の趣味でやっている Blog の SSL 化にお金をかけるのも躊躇うところだったが、昨今では Let's Encrypt という 90 日単位での証明書の更新作業が必要だが無料の SSL 証明書が出てきたので重い腰を上げた。

Qiita の記事で【apache】conohaのUbuntu18.04にLet's EncryptでSSL設定するまでというピッタリなものがあったので記事通り作業したら難なく対応できた。 Laravel で既に用意されている .htaccess に HTTPS へのリダイレクト処理を入れるのを試してみたが、なぜか個別記事に HTTP でアクセスされた際に URL が消失してしまい index.php へのアクセスになってしまう問題を発見して、これが解決できない。 と思ったらただ単に RewriteCond - Rule の記述の最後に入れてしまっていたので index.php へのリダイレクト後に HTTPS へのリダイレクトが走ってしまっていたからだった。 HTTPS へのリダイレクト処理を一番上に書くようにしたらうまくいった。

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews -Indexes
    </IfModule>

    RewriteEngine On

    # HTTP を HTTPS にリダイレクト
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

恥ずかしながら今まで jQuery ばかり使用していたので document.querySelector の存在を知らなかったのでメモ。 Laravel の Blade テンプレート内でデフォルトで head 要素内に以下のように meta タグで CSRF トークンが出力されている:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    ...
</head>

上記は Blade 内なので {{ csrf_token }} という Blade の記法で取得できるが、この値を Vue.js のコンポーネント内で取得するのに meta タグから取り出す必要があった。 そこで私は最初「jQuery なら一発で取れるのに」と思いながら以下のように書いてしまった:

/**
 * メタ要素から CSRF トークンを取得する.
 *
 * @returns {string} CSRF トークン
 */
csrfToken() {
    let children = document.head.children;
    for (const child of children) {
        if (child.getAttribute('name') === 'csrf-token') {
            return child.getAttribute('content');
        }
    }
    return null;
}

しかしこれを書き終えた後で「Laravel で使われている Axios でヘッダに CSRF トークンを付与しているはずだがそこはどうやっているのか」と思い bootstrap.js を参照してみたら以下のように書かれていた:

let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}

この querySelector というのを知らなかったのだが、どうも CSS の記法でセレクタを記述することで jQuery のように DOM 要素を取得できる DOM のメソッドらしい。 最近できたのかと思いきや IE8 から利用可能なようで意外とレガシーなものだった……。 最初の 1 件を取得したい場合 querySelector を使用し、複数件数取得したい場合は querySelectorAll を使用する。

これから Vue.js のようなモダンな JS フレームワークをメインとし脱 jQuery としていくにあたって必須知識だろう。 覚えておきたい。

Laravel 学習中だがモデルに関して感じたことを書く。

xxxRaw メソッド

O/R マッパーは確かに便利なのだがちょっと凝ったことをしようとすると SQL だと簡単に書けるのに O/R マッパーでどういう風に表現するのか分からずに実装できないという問題がある。 CakePHP などは顕著な問題で特にアソシエーションなどを使用すると正しいはずなのに何故か動かないというコードになりがちだ。 しかも COUNT(*)SUM(*) を書きたいだけでも $query->func()->xxx() などという訳のわからない記法を要求される。 これが覚えられない。

Laravel だと DB::raw(), selectRaw()whereRaw(), orderByRaw() などの低レベルに書けるタイプのメソッドが用意されており、これを使うと対象部分だけ SQL 直で書くことができる。 単純な機構だがとても便利だ。 例えば以下のような感じだ:

/**
 * 年月ごとの記事数を返却する.
 *
 * @return \Illuminate\Http\JsonResponse
 */
public function countYearMonth(): JsonResponse
{
    $posts = Post::query()
        ->groupBy(['year', 'month'])
        ->orderByDesc('year')
        ->orderByDesc('month')
        ->get(['year', 'month', DB::raw('COUNT(id) AS count')]); // DB:raw() で SQL 直書き
    return Response::json($posts);
}

これは覚えやすい。 「何故既存の O/R マッパーは使いにくいのか」がよく分かっている人が設計した感じがする。

何もしないと取得結果がすべて string になってしまう

クエリを投げて DB からモデルに取得した結果が INTEGER カラムであったとしてもすべて string になってしまう。 これだと毎回変換しなければならず使いにくい。 これに関しては【Laravel】DBから取得した値を$castsで型変換する【Attribute Casting】に記載がある通り、モデルに型変換したいカラムを明示的に指定する。

class Post extends Model
{
    use SoftDeletes;

    // このように定義しておけば year, month, day, count は取得時に integer にキャストされる
    protected $casts = ['year' => 'integer', 'month' => 'integer', 'day' => 'integer', 'count' => 'integer'];
}

上記の例で、例えば countposts テーブル内に存在しないカラムだったとしても問題ないし、DB::raw('COUNT(id) AS count') で取った結果がちゃんと integer で返ってくる。 この辺りは便利なのか不便なのかちょっとよく分からなかった。

私は仕事では CakePHP を使用しているので PHP 最強のフレームワークと名高い Laravel をなかなか触る機会に恵まれなかった。 Laravel は世界的には圧倒的なシェアを誇る PHP フレームワークである。 日本では依然として CakePHP が強いのだがこの 2 年の間にかなり追い上げてきたようなので、今後日本でも PHP での開発の主体が Laravel に変わっていくのではと思う。 今回個人的に勉強しようと思ったので、この記事に試行錯誤の過程を記録しておくことにする。

私が今使っているのは IntelliJ IDEA Ultimate だが PhpStorm でも同様と思われるので PhpStorm と書いておく。 事前条件として Composer はローカルにインストールしておくこと。 Composer 及び Vagrant に関してはここでは述べない (既に知っているものとする)。

Laravel インストール (PhpStorm 上から実行する場合)

これを行うにはローカルに Composer がインストールされている必要がある。 拙記事「Windows に Composer インストール」を参照。

PhpStorm で「新規プロジェクトの作成」を選択。 表示されるプロジェクトの種類から PHP -> Composer プロジェクト を選択する。 プロジェクト名などを適当に入力するがここでは test_laravel としておく。 一番下「パッケージ」のところで laravel/laravel を検索して選択。 するとインストールすることができるバージョンが一覧されるので最新のバージョンを選択する。 執筆時点だと v5.7.13 だった。 そのあと完了を押下すると composer install 相当の処理が走りすぐに開発ができるようになる。

PhpStorm を使わない場合やローカルに Composer などを入れたくない場合は以下に示すとおり Homestead 上から行う。

Laravel インストール (Homestead 上から実行する場合)

手順前後して恐縮だが、後述の Homestead インストールが済んでいるものとする。 Homestead には PHP 環境や Composer が最初からインストールされているので非常に簡単。 vagrant ssh で Homestead に接続する。 Homestead.yaml に書いてある folders: to: /home/vagrant/code がそのままの場合は以下で /home/vagrant/code に新規 Laravel プロジェクトを作成する:

composer create-project --prefer-dist laravel/laravel code

インストール完了後 http://192.168.10.10/ にアクセスし正しく Laravel のスタートページが表示されることを確認する。

Homestead インストール

Homestead は見た感じちょっと便利な Vagrant の Box という感じだった。 インストールは公式の手順に従い忠実に行えば苦もなく完了した。 そしてインストール後は Vagrant を使ったことがあれば何ら違和感なく使用することができた。 ちょっと違うのは Homestead.yaml という YAML で共有フォルダや nginx サイト設定などを簡単に行えるというところだった。 同じフォルダにある Vagrantfile 側に Homestead.yaml をインクルードするようなコードが書いてあったので、より簡単に設定が書けるようにという配慮なのだと思われる。

Vagrant の時はデフォルトで Vagrantfile があるフォルダがそのまま共有フォルダとしてマウントされるようになっているので Vagrantfile とプロジェクトのソースコードを同一フォルダに置くような運用をしたりするのだが Homestead の場合は最初から Homestead インストールディレクトリとプロジェクトのソースコードが切り離せる構造になっている上、複数プロジェクト・複数ホスト名が考慮された作りになっている。 ということで、

  • Homestead は C:\Users\hoge\Homestead にインストール
  • プロジェクトのソースは C:\Users\hoge\PhpStormProjects\test_laravel に置く
  • (今後増えた場合) プロジェクト 2 のソースを C:\Users\hoge\PhpStormProjects\fuga に置く

といったことが苦もなくできる。 つまり Homestead.yaml などの Homestead 関連のファイルは個人の環境で書き換えさせるものとして Git 管理下にしないほうが良さそうだ。 例えば上記の例の場合 (今後増えた場合は含まない) は Homestead.yaml を以下のように設定する:

ip: "192.168.10.10"

...

folders:  # これは複数定義できる
    - map: C:\Users\hoge\PhpStormProjects\test_laravel
      to: /home/vagrant/code

sites:  # これも複数定義できる
    - map: homestead.test
      to: /home/vagrant/code/public

本当は hosts ファイルに上記 homestead.test を定義すれば http://homestead.test で確認できるが、今回は 1 サイトしかないし面倒なので http://192.168.10.10 でアクセスする (2 サイト以上ある場合は名前をつけないと区別ができなくなる)。 そうすると Laravel のデフォルトページが表示されるのが確認できる。

注釈するが、結局のところ中身はただの Vagrant (Box の中身は Ubuntu Server だった) なので vagrant up してからアクセスすること。 Homestead.yaml を書き換えた場合は vagrant reload (再起動)。

まとめるとローカルに PHP をインストールして試行錯誤するより遥かに楽なので使わない手はない。