20200922のPHPに関する記事は10件です。

【ローカル環境】Laravelアプリ作ってローカルDB(MAMP)にテーブル作るまで

Laravelでいろいろなハンズオン形式の練習をしていくにあたり、
アプリ作成からテーブル作成まで、毎回ちょっと調べるのが面倒になってきたのでまとめます。

前提環境/どんな方向け?

・mac OS 10.15(10.14や10.13でも大きな違いはなし)
・MAMPをmacにインストール済みであること。
・ターミナル操作がわからないこともないこと。
・Laravelはmacにインストール済みであること。
・composerも入れていること。

という感じで、しょっぱなからの説明ではなく、過去にlaravelをmampで構築されており、ローカル環境で新しくアプリを作る方向け(主に自分の備忘録)です。

手順

1.Laravelアプリを作る

まずはアプリ作成。「todoLife」というアプリを作る場合は下記の手順。
ターミナルで、

composer create-project "laravel/laravel=5.8.*" todoLife

※バージョン指定は任意です。不要な場合は「laravel/laravel」でokです。
ここでは5.8の中の最新版を指定してみます。

また、このコマンドを行うことで、「今いる場所」にフォルダ/プロジェクトを作ります。
例えばデスクトップに「todoLife」というフォルダを作り、その中にcdで移動して上記のコマンドを実行すると
desuktop/todoLife/todoLife (← laravelのアプリ)
という感じになっちゃいます。
ので、desktopに作りたい場合はデスクトップに移動して実行しましょう。
(この後すぐmamp内に移動しますが)

2.MAMP内に移動

作ったララベルプロジェクトをmampの中のhtdocsに移動しましょう。
具体的には下記のような配置となります。

Macintosh ハードディスク/Applications/MAMP/htdocs/プロジェクタフォルダ

phpデータをローカルのmampで表示、動作させるにはこの「htdocs」の中に保管している必要があります。

3.DBを作る

MAMPで予めDBを作っておきます。
開発環境ではあるので大体でもいいかもですが、アプリ名と同じにしているケースが多いです。

MAMPの中でnewから作成しましょう。
※照合順序は「utf8_general_ci」でokです。
01.png

4.テーブルを作る

手動でMAMP内で作ってもいいですが、ここではlarabelのマイグレーション機能で作ってみます。今回作るのはシンプルに「todo」のテーブル「todos」です。

※なお、laravelのプロジェクト > database > migrations の中にデフォルトで存在している

2014_10_12_000000_create_users_table.php
2014_10_12_100000_create_password_resets_table.php

は、ユーザーテーブルやパスワードリセットが不要ならば削除しても問題ないです。

マイグレーションとは?

マイグレーションを行うことにより、このmigrationsファイルの中の「up()」の処理を実施します。
この後マイグレーションファイルの中身を見ますが、これらのファイルのupでは、テーブルを作るという処理が書かれています。
そのため、デフォルトの状態でマイグレーションをすると、前述の既存の二つのファイルにより、「ユーザーテーブル」と、「パスワードリセットのテーブル」が作られる、ということになります。

今回はそのままにしておき、マイグレーションファイルを別途作ります。
作る方法は、ターミナルでプロジェクトフォルダのトップにいるときに

php artisan make:migration create_todos_table --create=todos

と実施します。
ここで中盤と最後にある「todos」が、作成するファイル名、テーブル名になります。
テーブル名は通常複数形として作成する必要があります。postなら「posts」などです。

次にmigrationsの中にできた記述を見てみましょう。こんな関数ができているはずです。

2020_xx_xx_123630_create_todos_table.php
class CreateTodosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('todos', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->timestamps();
        });
    }

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


up()が、migration実行時に作られるテーブルの情報、そして
down()がやっぱその処理をやめるときに巻き戻す(ロールバック時に)実行される処理です。

今回、各todosには題名と本文をつけたいので、こんな感じでtitle、bodyテーブルを作る記述を追加します。

    public function up()
    {
        Schema::create('todos', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title',50);//追加
            $table->text('body');//追加
            $table->timestamps();
        });
    }

それでは、下記の手順でマイグレートを実行します。

php artisan migrate

む。エラーとなりました。
スクリーンショット 2020-09-22 22.03.30.png

5.環境設定

DBとの接続設定をまだ行っていませんでした。
その辺の環境設定は.envファイルで行います。
.envはプロジェクトのトップディレクトリ直下に存在しています。
02.png
いじるのはこの辺。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=todoLife //アプリ名に変更
DB_USERNAME=root
DB_PASSWORD=root //パスワードを設定。mamp側でDBにログインするためのユーザーごとのid、パスワードを設定します。


config/app.php
config/database.php
というファイルも編集します!

//app.php
//タイムゾーンと言語設定を変更しておきましょう。
    'timezone' => 'Asia/Tokyo',
    'locale' => 'ja',
//database.php
//database名、ユーザーネーム、パスワード、unix_socketを修正します。

'mysql' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'todoLife'),//修正
            'username' => env('DB_USERNAME', 'root'),//修正
            'password' => env('DB_PASSWORD', 'root'),//修正
            'unix_socket' => env('DB_UNIX_SOCKET', '/Applications/MAMP/tmp/mysql/mysql.sock'),//修正


6.もう一度マイグレーション

今度は成功しました!
03.png

mampを見てもちゃんとtodos、users、passwordresetテーブルがつくられています。
スクリーンショット 2020-09-22 22.23.54.png

終わり!

おまけ

マイグレーションしてなくても見れますが、プロジェクトフォルダでphp artisan serveして、
mampを起動しておけばlaravelの起動も確認できます!
スクリーンショット 2020-09-22 22.25.51.png

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

適当な文字列を 0 から 1 までの実数に振り分けるロジック (PHP/TypeScript)

PHP-7.2 TypeScript-3.7

最近 A/B テストのように確率的な条件分岐を実現する文脈で、適当な入力文字列から区間 $ [0, 1] $ に含まれる数値を出力するようなハッシュ関数を実装する機会がありました。単に定められた範囲の数値を出力するだけでなく、出力される数値に偏りが出ないように(関数の出力値の分布が連続一様分布とみなせる 1 ように)するという要件もありました。

イメージとしては以下のコードのようになります。

// 同じ文字列を入力に取ると同じ数値が出力される
strToNumberBetweenZeroAndOne('abc');        // output: 0.7283949105904
strToNumberBetweenZeroAndOne('abc');        // output: 0.7283949105904

// 別の文字列を入力に取ると別の数値が出力される
strToNumberBetweenZeroAndOne('Hello');      // output: 0.095208030923864
strToNumberBetweenZeroAndOne('World!');     // output: 0.31755707966684
strToNumberBetweenZeroAndOne('0123456789'); // output: 0.51892998626938

// 色々な文字列に対して出力値をプロットしていくと、数値の確率分布が区間 [0, 1] の一様分布に近づく。

このような関数を PHP と TypeScript で実装したので紹介します。

実装

PHP

PHP の場合は以下のように実装できます。

function strToNumberBetweenZeroAndOne(string $message): float
{
    // 1
    $hashHex = hash('sha256', $message);

    // 2
    $maxHex = str_repeat("f", 64);
    return hexdec($hashHex) / hexdec($maxHex);
}

1 の部分では hash('sha256', $string) で入力文字列を SHA256 2 で64桁の固定長16進数に変換し、 hexdec() でその10進数表現を得ています。文字列から16進数への変換は bin2hex で行うこともできますが、文字列のサイズが大きすぎると桁あふれを起こしてしまうので使用を避けています。

2 の部分では 1 で得られた10進数を64桁の16進数の最大値 str_repeat("f", 64) の10進数表現で割ることで数値を算出しています。

TypeScript

import CryptoJS from 'crypto-js'

const strToNumberBetweenZeroAndOne = (message: string): number => {
  const hashHex = CryptoJS.SHA256(message).toString()

  const maxHex = 'f'.repeat(64)
  return parseInt(hashHex, 16) / parseInt(maxHex, 16)
}

内容は PHP と特に変わりませんが、 SHA256 のハッシュ化を行うために crypto-js というライブラリを利用しています。

検証

実際にランダムな文字列を入力にメソッドの出力値 3 を集計してヒストグラムを作成してみると、おおよそ一様分布になっていることが分かります。

メソッドの出力値を集計して作成したヒストグラム


  1. 統計学に関しては門外漢なので、数学的に厳密な連続一様分布がどう表されるべきかは特に考慮しておらず、 A/B テストのような用途で実用性を損なわない精度の出力が得られることを目指しています。また統計学の用語の使い方が誤っている等あれば編集リクエストをいただけると助かります! 

  2. 単に強度や処理速度などの要件に応じて他のハッシュ関数を用いても構いません。 

  3. ここではヒストグラムの作成用に出力値を100倍して小数点を切り捨てています。 

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

Docker x Laravel phpMyAdmin のコンテナを構築する

以前の記事(最強のLaravel開発環境をDockerを使って構築する【新編集版】)のコメントにてphpMyAdminを使用したいという要望がありましたので補足の記事を作成させていただきます。

手順

https://github.com/ucan-lab/docker-laravel

docker-compose.yml に phpMyAdmin のサービスを追加

docker-compose.yml
volumes:
  # 追記
  pma-session-store:

services:
  # 追記
  pma:
    image: phpmyadmin/phpmyadmin:5.0.2
    environment:
      - PMA_HOST=db
      - PMA_USER=root
      - PMA_PASSWORD=secret
    ports:
      - 8080:80
    volumes:
      - pma-session-store:/sessions

Dockerコンテナの構築

$ docker-compose up -d

phpMyAdmin へアクセス

http://127.0.0.1:8080

上記のURLへアクセスし、phpMyAdminの画面が表示されればokです。

ScreenShot 2020-09-22 19.11.42.png

ScreenShot 2020-09-22 19.11.46.png

補足

MySQL新認証プラグイン caching_sha2_password

私のdbコンテナの設定でMySQL8.0系で認証方式を新しいcaching_sha2_passwordに設定しています。phpMyAdminは5.0.1以降でないとcaching_sha2_passwordに対応していないのでタグで新しいバージョンを明示的に指定してます。latestが5系を指すようになったら外してもいいかもです。

蛇足

(MySQLのCLIやSequel Ace等のクライアントツールで特に不便がないのでがあるので個人的にはphpMyAdminは入れなくてもいいかなと思ってます)

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

PHPでconfigファイルをオートロードで呼び出す方法

Laravelをやっているとconfigから値を呼ぶことがあります。
同様に素のPHPでも使いたいなと思ったのでやり方を書いておきます

Config.php
<?php
class Config {
    protected static $directory;
    public static function setConfigDirectory($directory) {
        self::$directory = $directory;
    }

    public static function getConfigDirectory() {
        return rtrim(self::$directory, '/\\');
    }

    public static function get($s) {
        $values = preg_split('/\./', $s, -1, PREG_SPLIT_NO_EMPTY);
        $key = array_pop($values);
        $file = 'common.php';
        $path = (!empty($values)) ? implode(DIRECTORY_SEPARATOR, $values) .
            DIRECTORY_SEPARATOR : '';
        $base_dir = self::getConfigDirectory() . DIRECTORY_SEPARATOR;
        $config = include($base_dir . $path . $file);
        return $config[$key];
    }
}

これの他にconfig/common.phpを作ってください

common.php
<?php
return [
    'STRIPEKEY' => 'abc',
    'ZOOMKEY' => '123',
];

読み込みたいファイルで

example.php
<?php
require_once 'Config.php';
Config::setConfigDirectory(__DIR__ . '/config');

これを追記してください

蛇足ですが、require, includeの違いは

ファイルが読み込めなかった場合
require文の場合はFatalエラーで処理が終了するが
include文の場合はWarnigエラーで処理が継続されることです

require_once, include_onceの違いは

同じファイルを読み込んだ場合に
_once文は一度しかファイルを読み込まないが
require, include文は何度でも読み込むことです

つまりほとんどの場合はrequire_onceでいいですね

gitにpushするときは

.gitignore
config/
Config.php #任意

を追記すればいいでしょう

example.php
<?php
require_once 'Config.php';
Config::setConfigDirectory(__DIR__ . '/config');
echo Config::get('STRIPEKEY'); //abc
echo Config::get('ZOOMKEY'); //123

読み込むのがめんどくさいときはオートロードしましょう
オートロードはクラスを利用する際に自動的にそのクラスが定義されているファイルを読み込む機能です
処理上のオーバーヘッドを防ぐために使うといいですね!
spl_autoload_register()関数を使う
オートロードの処理をコールバック関数として任意のタイミングで登録できて
__autoload()関数と違って複数のオートロード関数を登録しておくこともできる

lib/autoload.php
<?php
spl_autoload_register(
    function($class_name)
    {
        $filepath = __DIR__ . '/' . $class_name . '.php';
        if (is_readable($filepath)) require $filepath;
        if ($class_name == 'Config') Config::setConfigDirectory(__DIR__ . '/config');
    }
);
#複数作ると必要なクラスの定義が見つかるまで登録した順にコールバック関数が実行される

それを読み込もう

example.php
<?php
require 'lib/autoload.php';
echo Config::get('ZOOMKEY'); //123

これでおっけい!

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

入力欄が別の時の期間カスタムバリデーションLaravel5.5

入力欄が別の期間のバリデーションをかけようと思うと
2015-3~2017-4ではバリデーションがうまくかかるが、
2018-8~2018-6だとエラーを吐かなかったので
バリデーションをかける前に連結させました。
誰かの参考になればと思い、残しておきます

laravel5.5でFormRequestを使う場合コントローラの処理を実行する前にバリデーションがかかってしまうので、
先に文字列を連結させる必要がありました。
なので文字列を連結させてそれをフォームから送信させているようにしてバリデーションをかけるようにしました。

もちろん入力欄を分けなければafterafter_or_equalbeforebefore_or_equalなどを使えばいいのですが。。
送信前にリクエストを加工するには6.xではprepareForValidation()メソッドを使えばいいみたいなので参考にはならないと思います

まずはこれを追加してください

use Illuminate\Validation\Validator;

その後にメインロジックを追加していきます
見ての通り、連結させて$requestに追加しています
それに伴って

    protected function validationData()
    {
        $this->addStartOfRegisterDate();
        $this->addEndOfRegisterDate();
        $this->addStartOfEnrollmentDate();
        $this->addEndOfEnrollmentDate();
        return parent::validationData();
    }

    private function addStartOfRegisterDate()
    {
        $start_of_register_year = (string)$this->request->get('start_period_of_register_year');
        if ($start_of_register_year === '') return;
        $start_of_register_month = (string)$this->request->get('start_period_of_register_month');
        if ($start_of_register_month === '') return;
        $start_of_register_date = $start_of_register_year . '-' . $start_of_register_month;
        $this->request->add(['start_of_register_date' => $start_of_register_date]);
    }

    private function addEndOfRegisterDate()
    {
        $end_of_register_year = (string)$this->request->get('end_period_of_register_year');
        if ($end_of_register_year == '') return;
        $end_of_register_month = (string)$this->request->get('end_period_of_register_month');
        if ($end_of_register_month == '') return;
        $end_of_register_date = $end_of_register_year . '-' . $end_of_register_month;
        $this->request->add(['end_of_register_date' => $end_of_register_date]);
    }

    private function addStartOfEnrollmentDate()
    {
        $start_of_enrollment_year = (string)$this->request->get('start_period_of_enrollment_year');
        if ($start_of_enrollment_year === '') return;
        $start_of_enrollment_month = (string)$this->request->get('start_period_of_enrollment_month');
        if ($start_of_enrollment_month === '') return;
        $start_of_enrollment_date = $start_of_enrollment_year . '-' . $start_of_enrollment_month;
        $this->request->add(['start_of_enrollment_date' => $start_of_enrollment_date]);
    }

    private function addEndOfEnrollmentDate()
    {
        $end_of_enrollment_year = (string)$this->request->get('end_period_of_enrollment_year');
        if ($end_of_enrollment_year == '') return;
        $end_of_enrollment_month = (string)$this->request->get('end_period_of_enrollment_month');
        if ($end_of_enrollment_month == '') return;
        $end_of_enrollment_date = $end_of_enrollment_year . '-' . $end_of_enrollment_month;
        $this->request->add(['end_of_enrollment_date' => $end_of_enrollment_date]);
    }

これによって連結された文字列が$requestに入るので、そこにバリデーションをかけるようにしましょう

'start_of_register_date' => 'nullable|before_or_equal:end_of_register_date',
'end_of_register_date' => 'nullable|after_or_equal:start_of_register_date',
'start_of_enrollment_date' => 'nullable|before_or_equal:end_of_enrollment_date',
'end_of_enrollment_date' => 'nullable|after_or_equal:start_of_enrollment_date',

これをrules()returnに追加しましょう

全体を見るとこんな感じですー

MypageUpdateRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;

class MypageUpdateRequest 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 [
            'birth_year' => 'nullable|integer|min:' . config('common.validate_min.birth_year') . '|max:' . config('common.validate_max.birth_year'),
            'birth_month' => 'nullable|integer|min:' . config('common.validate_min.birth_month') . '|max:' . config('common.validate_max.birth_month'),
            'birth_day' => 'nullable|integer|min:' . config('common.validate_min.birth_day') . '|max:' . config('common.validate_max.birth_day'),

            'start_period_of_register_year' => 'nullable|string|required_with:company_name,start_period_of_register_month,end_period_of_register_month|integer|min:1969|max:date(Y)',
            'start_period_of_register_month' => 'nullable|string|required_with:company_name,start_period_of_register_year,end_period_of_register_year|integer|min:1|max:12',
            'start_of_register_date' => 'nullable|before_or_equal:end_of_register_date',
            'end_period_of_register_year' => 'nullable|string|required_with:company_name,start_period_of_register_month,end_period_of_register_month|integer|min:1969|max:date(Y)',
            'end_period_of_register_month' => 'nullable|string|required_with:company_name,end_period_of_register_year,start_period_of_register_year|integer|min:1|max:12',
            'end_of_register_date' => 'nullable|after_or_equal:start_of_register_date',
            'start_period_of_enrollment_year' => 'nullable|string|required_with:school_name,start_period_of_enrollment_month,end_period_of_enrollment_month|integer|min:1969|max:date(Y)',
            'start_period_of_enrollment_month' => 'nullable|string|required_with:school_name,start_period_of_enrollment_year,end_period_of_enrollment_year|integer|min:1|max:12',
            'start_of_enrollment_date' => 'nullable|before_or_equal:end_of_enrollment_date',
            'end_period_of_enrollment_year' => 'nullable|string|required_with:school_name,start_period_of_enrollment_month,end_period_of_enrollment_month|integer|min:1969|max:date(Y)',
            'end_period_of_enrollment_month' => 'nullable|string|required_with:school_name,start_period_of_enrollment_year,end_period_of_enrollment_year|integer|min:1|max:12',
            'end_of_enrollment_date' => 'nullable|after_or_equal:start_of_enrollment_date',
        ];
    }

    protected function validationData()
    {
        $this->addStartOfRegisterDate();
        $this->addEndOfRegisterDate();
        $this->addStartOfEnrollmentDate();
        $this->addEndOfEnrollmentDate();
        return parent::validationData();
    }

    private function addStartOfRegisterDate()
    {
        $start_of_register_year = (string)$this->request->get('start_period_of_register_year');
        if ($start_of_register_year === '') return;
        $start_of_register_month = (string)$this->request->get('start_period_of_register_month');
        if ($start_of_register_month === '') return;
        $start_of_register_date = $start_of_register_year . '-' . $start_of_register_month;
        $this->request->add(['start_of_register_date' => $start_of_register_date]);
    }

    private function addEndOfRegisterDate()
    {
        $end_of_register_year = (string)$this->request->get('end_period_of_register_year');
        if ($end_of_register_year == '') return;
        $end_of_register_month = (string)$this->request->get('end_period_of_register_month');
        if ($end_of_register_month == '') return;
        $end_of_register_date = $end_of_register_year . '-' . $end_of_register_month;
        $this->request->add(['end_of_register_date' => $end_of_register_date]);
    }

    private function addStartOfEnrollmentDate()
    {
        $start_of_enrollment_year = (string)$this->request->get('start_period_of_enrollment_year');
        if ($start_of_enrollment_year === '') return;
        $start_of_enrollment_month = (string)$this->request->get('start_period_of_enrollment_month');
        if ($start_of_enrollment_month === '') return;
        $start_of_enrollment_date = $start_of_enrollment_year . '-' . $start_of_enrollment_month;
        $this->request->add(['start_of_enrollment_date' => $start_of_enrollment_date]);
    }

    private function addEndOfEnrollmentDate()
    {
        $end_of_enrollment_year = (string)$this->request->get('end_period_of_enrollment_year');
        if ($end_of_enrollment_year == '') return;
        $end_of_enrollment_month = (string)$this->request->get('end_period_of_enrollment_month');
        if ($end_of_enrollment_month == '') return;
        $end_of_enrollment_date = $end_of_enrollment_year . '-' . $end_of_enrollment_month;
        $this->request->add(['end_of_enrollment_date' => $end_of_enrollment_date]);
    }

    public function messages()
    {
        return [
            'birth_year.integer' => '正しい年を選択してください',
            'birth_year.max' => '正しい年を選択してください',
            'birth_year.min' => '正しい年を選択してください',
            'birth_year.required_with' => '月と日も選択してください',
            'birth_month.integer' => '正しい月を選択してください',
            'birth_month.max' => '正しい月を選択してください',
            'birth_month.min' => '正しい月を選択してください',
            'birth_month.required_with' => '年と日も選択してください',
            'birth_day.integer' => '正しい日を選択してください',
            'birth_day.max' => '正しい日を選択してください',
            'birth_day.min' => '正しい日を選択してください',
            'birth_day.required_with' => '月と日も選択してください',

            'start_period_of_register_year.integer' => '在籍期間を正しく選択してください',
            'start_period_of_register_year.max' => '在籍期間を正しく選択してください',
            'start_period_of_register_year.min' => '在籍期間を正しく選択してください',
            'start_period_of_register_year.required_with' => '年も選択してください',
            'start_period_of_register_month.integer' => '在籍期間を正しく選択してください',
            'start_period_of_register_month.max' => '在籍期間を正しく選択してください',
            'start_period_of_register_month.min' => '在籍期間を正しく選択してください',
            'start_period_of_register_month.required_with' => '月も選択してください',
            'start_of_register_date.before_or_equal' => '在籍開始期間を正しく選択してください',
            'end_period_of_register_year.integer' => '在籍期間を正しく選択してください',
            'end_period_of_register_year.max' => '在籍期間を正しく選択してください',
            'end_period_of_register_year.min' => '在籍期間を正しく選択してください',
            'end_period_of_register_year.required_with' => '年も選択してください',
            'end_period_of_register_month.integer' => '在籍期間を正しく選択してください',
            'end_period_of_register_month.max' => '在籍期間を正しく選択してください',
            'end_period_of_register_month.min' => '在籍期間を正しく選択してください',
            'end_period_of_register_month.required_with' => '月も選択してください',
            'end_of_register_date.after_or_equal' => '在籍終了期間を正しく選択してください',
            'start_period_of_enrollment_year.integer' => '在籍期間を正しく選択してください',
            'start_period_of_enrollment_year.max' => '在籍期間を正しく選択してください',
            'start_period_of_enrollment_year.min' => '在籍期間を正しく選択してください',
            'start_period_of_enrollment_year.required_with' => '年も選択してください',
            'start_period_of_enrollment_month.integer' => '在籍期間を正しく選択してください',
            'start_period_of_enrollment_month.max' => '在籍期間を正しく選択してください',
            'start_period_of_enrollment_month.min' => '在籍期間を正しく選択してください',
            'start_period_of_enrollment_month.required_with' => '月も選択してください',
            'start_of_enrollment_date.before_or_equal' => '在籍開始期間を正しく選択してください',
            'end_period_of_enrollment_year.integer' => '在籍期間を正しく選択してください',
            'end_period_of_enrollment_year.max' => '在籍期間を正しく選択してください',
            'end_period_of_enrollment_year.min' => '在籍期間を正しく選択してください',
            'end_period_of_enrollment_year.required_with' => '年も選択してください',
            'end_period_of_enrollment_month.integer' => '在籍期間を正しく選択してください',
            'end_period_of_enrollment_month.max' => '在籍期間を正しく選択してください',
            'end_period_of_enrollment_month.min' => '在籍期間を正しく選択してください',
            'end_period_of_enrollment_month.required_with' => '月も選択してください',
            'end_of_enrollment_date.after_or_equal' => '在籍終了期間を正しく選択してください',

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

phpで切り捨て処理を行う際、floor関数とintキャスト処理のどちらを使うのが適切か

やりたいこと

浮動小数点数の切り捨て処理を行い、float 型を int 型にしたい。

  • floor() 関数
  • intのキャスト処理

のどちらを利用するのが適切か。それとも、どちらでも差異はないのか。

結論

  • floor() 関数は返り値として float型を返すため、int型でキャストする必要がある。
  • マイナス値を扱う場合において、 floor()(int) の結果が異なってくる。

そのため、

  • -1.1を切り捨てした際、-1になるのが自然と感じるのであれば、(int)$numとする
  • -1.1を切り捨てした際、-2になるのが自然と感じるのであれば、(int)floor($num)とする

という選択肢の、どちらかを選ぶのがよいと考えられる。

マイナス値を与えたときの処理の違いについて

floor() 関数の場合

value をこえない最大の整数の値を返します。 floor() の返り値は float 型のままとなります。
https://www.php.net/manual/ja/function.floor.php

マイナス値 (例: -1.1) を与えると、-2を返す

<?php

$float_vals = array(1.1, 1.8, -1.1, -1.8, 0.05, -0.05);

foreach ($float_vals as $v) {
    var_dump(floor($v));
}

https://3v4l.org/UMDvF

float(1)
float(1)
float(-2) *
float(-2) *
float(0)
float(-1) *

(int) キャストの場合

float から整数に変換する場合、その数はゼロのほうに丸められます。
https://www.php.net/manual/ja/language.types.integer.php

マイナス値 (例:-1.1) を与えると、-1を返す

<?php

$float_vals = array(1.1, 1.8, -1.1, -1.8, 0.05, -0.05);

foreach ($float_vals as $v) {
    var_dump((int)$v);
}
int(1)
int(1)
int(-1) *
int(-1) *
int(0)
int(0) *

https://3v4l.org/oD9Cm

マイナス値の切り上げ・切り捨て処理には、4種類あるみたい

  • 0への丸め
  • 無限大への丸め
  • 負の無限大への丸め
  • 正の無限大への丸め

wikipedia - 端数処理
※ wikipediaまでしか確認していないため、本当にこのような用語が定義されているかについてはわかりません。
参考程度にお願いします。

floor() 関数はなぜ値をfloat型で返すのか

表すことのできる数値の範囲を大きく取るためです。

以下記事のコメントにて、説明がありました。
https://qiita.com/takepan/items/ff8bba234fdc93a32791#comment-50d3e6e3db22ffdd53da

floorは返り値として float を返すため、取り得る値の範囲が広くなります。
floatであれば格納可能な値 9223372036854775808 をintにキャストすると、オーバーフローを引き起こします。

実際にコメントのサンプルコードを実際に実行すると、以下のようになります。

<?php
$large_number = PHP_INT_MAX + 100.1;
var_dump(sprintf('%.30f', floor($large_number)));
var_dump(sprintf('%.30f', intval(floor($large_number))));
?>
string(50) "9223372036854775808.000000000000000000000000000000"
string(51) "-9223372036854775808.000000000000000000000000000000"

https://3v4l.org/fjNMs

オーバーフローしました。

おわりに

思いの外深い話題でした。
もっと掘り下げられそうなのですが、下書きが10件溜まってしまったため一度記事にします1


  1. この記事を書くのに疲れて、結局今日書こうと思っていた記事は書けそうにない。。。あとおなかすいた 

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

PHPにおける文字列置き換え大全

コピペして使うように作りましたー
間違ってたら教えてください!!

目次

よく使う正規表現のパターンマッチ
携帯番号の例
郵便番号の例
全角カタカナの例
なんちゃってメールアドレスの例
クレジットカード全社の例
IPアドレスの例
文字列を繰り返す方法
入力値を統一する方法
小文字から大文字に変換
大文字から小文字に変換
全角を半角に変換
半角を全角に変換
カナ文字の変換
文字エンコードの変換
文字列の一部を切り抜く方法
文字列の一部が一致しているか確認する
文字列の置き換え

よく使う正規表現のパターンマッチ

正規表現の基本的な構文

構文 説明
. 改行文字以外の任意の一文字
? 0回か1回だけのマッチ
* 0回以上の繰り返し
+ 1回以上の繰り返し
{n} n回の繰り返し
{n,m} n回以上m回以上の繰り返し
^ 文字列及び行の始まり
$ 文字列及び行の終わり
[] 文字クラス[a-z]ならaからzまでの一文字
\A 文字列の始まり
\z 文字列の終わり
\w 単語構成文字[a-zA-Z0-9_]とは異なる可能性がある
\d 数字
\s スペースタブ改行などの空白文字
\S 空白以外の全ての文字
() パターンのグループ化

パターン修飾子はデリミタ(\)の後に書く

パターン修飾子 説明
i 大文字と小文字を区別しない
m 行単位でマッチ
s 「.」を改行文字にもマッチさせる
u パターン文字列をUTF-8エンコードとして扱う
x エスケープするか文字クラスの内部を除いてパターン中の空白文字を無視

\をマッチさせるには
\\\\とする必要がある
正規表現でもPHPでもエスケープするため

携帯番号の例

example.php
//080-1234-5678, 09012345678
$tel_pattern = '/\A(090|080|070)-?\d{4}-?\d{4}\z/';

郵便番号の例

example.php
//111-1112, 1111112
$postal_code_pattern = '/\A\d{3}-?\d{4}\z';

全角カタカナの例

example.php
$em_kana_pattern = '/\A[\x{30A1}-\x{30FC}().-]+\x/u';

半角カタカナの例

example.php
$en_kana_pattern = '/\A[\x{FF66}-\x{FF9F}]+\x/u';

なんちゃってメールアドレスの例

example.php
$mail_pattern = '/\A[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+)*@(?:[_\p{L}0-9][-_\p{L}0-9]*\.)*(?:[\p{L}0-9][-\p{L}0-9]{0,62})\.(?:(?:[a-z]{2}\.)?[a-z]{2,})\z/ui';

クレジットカード全社の例

example.php
$cards_pattern = '/\A(4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|^(?:2131|1800|35\d{3})\d{11}$)\z/';
$visa_pattern = '/\A4\\d{12}(\\d{3})?\z/';
$master_pattern = '/\A5[1-5]\\d{14}\z/';
$jcb_pattern = '/\A(3\\d{4}|2100|1800)\\d{11}\z/';
$amex_pattern = '/\A3[4|7]\\d{13}\z/';
$diners_pattern = '/\A(?:3(0[0-5]|[68]\\d)\\d{11})|(?:5[1-5]\\d{14})\z/';
$discovery_pattern = '/\A(?:6011|650\\d)\\d{12}\z/';

IPアドレスの例

example.php
$ipv4_pattern = '\A(?:(?:25[0-5]|2[0-4][0-9]|(?:(?:1[0-9])?|[1-9]?)[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|(?:(?:1[0-9])?|[1-9]?)[0-9])\z';

$ipv6_pattern = '\A((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}(:|((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(:[0-9A-Fa-f]{1,4})))|(([0-9A-Fa-f]{1,4}:){5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){0,1}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4}){0,4}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))(%.+)?\z';

参考
https://qiita.com/ryounagaoka/items/dc9fe731d0fa6f8f9daa

文字列を繰り返す方法

str_repeatを使う

example.php
str_repeat('*', 10);

入力値を統一する方法

小文字から大文字に変換

strtoupper()関数を使う
mb_strtoupper()関数を使えば全角のアルファベットも変換する
ucfirst()関数は最初の文字だけを大文字に変換し
ucwords()関数は最初の単語だけを大文字に変換します

大文字から小文字に変換

strtolower()関数を使う
mb_strtolower()関数を使えば、全角のアルファベットも変換する

全角を半角に変換

mb_conver_kana()関数を使う

オプション 説明
r 全角英字を半角に変換
n 全角数字を半角に変換
a 全角英数字を半角に変換
s 全角スペースを半角に変換
example.php
mb_convert_kana($text, 'r');

半角を全角に変換

同じくmb_convert_kana()関数の第二引数を使う

オプション 説明
R 半角英字を全角に変換
N 半角数字を全角に変換
A 半角英数字を全角に変換
S 半角スペースを全角に変換

カナ文字の変換

同じくmb_convert_kana()関数の第二引数を使う

オプション 説明
k 全角カタカナを半角カタカナに変換
K 半角カタカナを全角カタカナに変換
h 全角平仮名を半角平仮名に変換
H 半角カタカナを全角平仮名に変換
c 全角カタカナを全角平仮名に変換
C 全角平仮名を全角カタカナに変換
V 濁点付きの文字を一文字に変換、KやHと共に使う
s 全角スペースを半角に変換
S 半角スペースを全角に変換
example.php
mb_conver_kana($text, 'HV');

文字エンコードの変換

mb_convert_encoding()関数を使う

example.php
mb_convert_encoding(mb_convert_encoding($text, 'SJIS'), 'UTF-8', 'SJIS');
mb_convert_encoding(mb_convert_encoding($text, 'EUC-JP'), 'UTF-8', 'EUC-JP');

文字列の一部を切り抜く方法

mb_substr('文字列', '開始位置'[, '文字数'])を使う
文字数を省略した場合、開始位置から末尾までの文字列の全てを返す
また先頭は0から始まる
マイナスを指定することもできる

example.php
$text = 'asdfghjkl';
echo mb_substr($text, 0, 5); //asdfg
echo mb_substr($text, 2); //dfghjkl
echo mb_substr($text, -5); //lkjhg

文字列の一部が一致しているか確認する

mb_strpos()関数を使う
最初に見つかった位置を数値で返す

example.php
if (mb_strpos($target, $keyword) === false) echo $keyword . 'が見つかりませんでした';

演算子は'==='を使う必要があります
検索する文字が先頭に見つかってしまった場合に0を返すからです。

文字列の置き換え

str_replace()関数を使う

example.php
echo str_replace('before', 'after', $target);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel8以降でルーティングがうまくいかない件

Laravel8.Xからrouteの記述方法が変わって面倒になったかもしれません。(自分でも検証済み)
Laravel7.X以前→Laravel8.X以降はエラーになったと思ったが、解決した件。

route/web.php

Route::get('/', 'TestController@index');

laravel8.X以降はこのようにしないとうまく行かなかった。。
route/web.php

use App\Http\Controllers\TestController;
Route::get('/',[TestController::class, 'index']);

しかし、Laravel8でも、app/Providers/RouteServiceProvider.php の bootメソッドでnamespaceを指定すればこれまで通りの動作をすることがわかった。
(土日にも関わらず、疑問に対応いただいた私の上司に感謝。。)

app/Providers/RouteServiceProvider.php

public function boot()
    {
        $this->configureRateLimiting();
        $this->routes(function () {
            Route::middleware('web')
                ->namespace('App\Http\Controllers')   ← 【これを足す】
                ->group(base_path('routes/web.php'));
            Route::prefix('api')
                ->middleware('api')
                ->group(base_path('routes/api.php'));
        });
    }

これで今まで通り(Laravel7以前のようにルーティングがうまくいく)
routes/web.php

Route::get('/test', 'TestController@index');

以上

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

Laravelでapi.phpに送れない

何をしているか

https://www.hypertextcandy.com/vue-laravel-tutorial-authentication-part-4/
上記サイトを参考にLaravel × Vue のアプリ開発の勉強をしています

ただ、Laravelの更新により、現在のバージョンではコピペでエラーが出てしまうことがありました。

躓いた箇所

上記サイトのところで、api.phpにgetを渡したのにも関わらず、web.phpの
Route::get('/{any?}', fn() => view('index'))->where('any', '.+');
が参照されてしまいました。

解決策

app\Providers\RouteServiceProvider.php の箇所で

public function boot()
    {
        $this->configureRateLimiting();

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

            Route::prefix('api')
                ->middleware('web')
                ->group(base_path('routes/api.php'));
        });
    }

となっている箇所を

public function boot()
    {
        $this->configureRateLimiting();

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

に変更しました。
要するにwebとapi の読み込む順番を逆にするということです。

同じところで躓いた人は試してみてください。

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

Laravel_CRUD処理を使った投稿アプリを作成する その10 バリデーションエラーを日本語に変える

目的

  • アプリを作成する上で基本となるCRUD処理を有したLaravelアプリをチュートリアル的に作成する方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提条件

前提情報

  • ソースコードはこちら→https://github.com/miriwo0104/laravel_crud/tree/master
  • DockerやAWSなどは使用せずにMacのローカルに実施環境と同じLaravel開発環境を構築して実施する。
  • チュートリアルで実際に筆者が作成したソースコードをGitHubにて公開予定である。
  • CRUD処理の作成完了を最短目標にしてバリデーションなどは後々設定することとする。
  • 実施環境と同じ環境がDockerやAWSで用意できるなら都度読み替えていただければ実施が可能だと思う。
  • 公式ドキュメントと一冊の技術書を元に本記事を記載する。
  • DBへの意図しないデータの流入を防ぐためバリデーションを実装する。
  • 問い合わせの内容は「入力がされていること」のバリデーションルールを、新規投稿と投稿編集は「入力されており、かつ140文字以内であること」定義してバリデートする。
  • バリデーションで弾かれた時に出力されるエラーメッセージを自分好みの物に変える方法を記載する
  • バリデーションルールは下記の公式ドキュメントに数多く記載されているので実装できたら遊んでみるのもいいかもしれない。

この記事の読後感

  • バリデーションエラーをユーザ任意の物に変更することができる。

概要

  1. Requestファイルの修正
  2. 確認

詳細

  1. Requestファイルの修正

    1. laravel_crudディレクトリで下記コマンドを実行してリクエストファイルを開く。

      $ vi app/Http/Requests/ContentRequest.php
      
    2. 開いたコントローラファイルを下記のように修正する。

      laravel_crud/app/Http/Requests/ContentRequest.php
      <?php
      
      namespace App\Http\Requests;
      
      use Illuminate\Foundation\Http\FormRequest;
      
      class ContentRequest 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 [
                  'content' => ['required', 'max: 140'],
              ];
          }
      
          // 下記を追記する
          public function messages()
          {
              return [
                  'content.required' => '内容を記入してください。',
                  'content.max' => '投稿内容は140文字以下にしてください。',
              ];
          }
          // 上記までを追記する
      }
      
    3. laravel_crudディレクトリで下記コマンドを実行してリクエストファイルを開く。

      $ vi app/Http/Requests/InquiryRequest.php
      
    4. 開いたコントローラファイルを下記のように修正する。

      laravel_crud/app/Http/Requests/InquiryRequest.php
      <?php
      
      namespace App\Http\Requests;
      
      use Illuminate\Foundation\Http\FormRequest;
      
      class InquiryRequest 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 [
                  'content' => ['required'],
                  'name' => ['required'],
              ];
          }
      
          // 下記を追記する
          public function messages()
          {
              return [
                  'content.required' => '内容を記入してください。',
                  'name.required' => '内容を記入してください。',
              ];
          }
          // 上記までを追記する
      }
      
  2. 確認

    1. laravel_crudディレクトリで下記コマンドを実行しローカルサーバを起動する。

      $ php artisan serve
      
    2. 下記にユーザ認証後、下記にアクセスする。

    3. 何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。

      127_0_0_1_8000_input.png

    4. 下記にユーザ認証後、下記にアクセスする。(コンテンツのidは1以外でも構わない)

    5. 何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。

      127_0_0_1_8000_edit_5.png

    6. 下記にユーザ認証後、下記にアクセスする。(コンテンツのidは1以外でも構わない)

    7. 何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。

      127_0_0_1_8000_inquiry_input.png

参考文献

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