20200323のPHPに関する記事は18件です。

【PHP8.0】gettypeとget_classの悪魔合体

ワレハget_debug_type、コンゴトモヨロシク…

PHPにはプリミティブ型名を取得するgettypeと、オブジェクトのクラス名を返すget_classという関数が存在します。
_があったりなかったりと命名の不統一も気になりますが、それよりgettypeはオブジェクトに使うとobjectしか返さず、get_classをプリミティブ型に使うとE_WARNINGが発生します。
いや、プリミティブ型であればintとかの型が欲しいし、オブジェクトならPDOとかの型が欲しいんだ、という問題に対する答えはありませんでした。

というわけで両者を合体させたget_debug_typeというRFCが提出されました。

PHP RFC: get_debug_type

proposal

このRFCは、指定された変数の型を返す新しい関数、get_debug_typeを追加する提案です。

これは、配列で来る値など、変数の型に基づいた既存のチェック方法では対応できないパターンを置き換えるためのものです。

$bar = $arr['key'];

if (!($bar instanceof Foo)) {
  // もっとも単純な例。しかしgettypeは"integer"を返すので、正しい型にしたいなら"int"に変換するなどが必要。
  throw new TypeError('Expected ' . Foo::class . ' got ' . (is_object($bar) ? get_class($bar) : gettype($bar)));
}

// 今後はこう書ける
if (!($bar instanceof Foo)) { 
  throw new TypeError('Expected ' . Foo::class . ' got ' . get_debug_type($bar));
}

$bar->someFooMethod();

この関数は、正しい型名を返すという点でgettypeと異なります。
"integer"ではなく"int"を返し、クラスもクラス名に変換します。
次の表は、いくつかの値に対してgettypeget_debug_typeが返す値を比較したものです。

get_debug_type() gettype()
0 int integer
0.1 float double
true bool boolean
false bool boolean
"hello" string
[] array
null null NULL
Foo\Bar Foo\Bar object
無名クラス class@anonymous object
リソース resource (xxx) resource
閉じたリソース resource (closed)

Backward Incompatible Changes

なし。

Proposed PHP Version(s)

PHP8.0

Implementation

https://github.com/php/php-src/pull/5143

投票

投票は2020/03/26まで、2/3の賛成で受理されます。
このRFCは賛成42、反対3で受理されました。

感想

Mark Randallは最初はgettypeがクラス名も返すようにしようとしたものの、Nikitaから「新しい関数にしてくれ」と言われてget_debug_typeを作ったようです。
まあ、これまでobjectとしか言わなかったgettypeがいきなり色々な型を喋り出したら困るところも出そうですからね。

ということで、今後は型の取得はget_debug_typeに一本化できそうです。

手間を省くための定型処理を言語機能に取り込むことは、他の言語でも多々起きていることです。
たとえばJavaScriptのasync/awaitPromiseの糖衣にすぎず、async/awaitができることは全てPromiseでもできるので、究極的にはasync/awaitは不要です。
しかし非同期処理を楽に書けるようにするために言語仕様に取り込まれました。
糖衣構文の取り込み自体は、このようにさほど珍しいことでもありません。

しかし、str_containsとかis_countableとか、他言語であれば「自分で書け」と言われそうな極端に簡単な構文まで言語仕様に取り込んでしまう言語は、PHP以外にはそうそう無いのいではないかと思います。

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

Laravelのライブラリ「laravel-dompdf」を使ってビューの表示をPDFとして表示しよう

目的

  • ライブラリを使用して現在のビューとして表示しているコードをそのままPDFで表示する方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.3)
ハードウェア MacBook Air (11-inch ,2012)
プロセッサ 1.7 GHz デュアルコアIntel Core i5
メモリ 8 GB 1600 MHz DDR3
グラフィックス Intel HD Graphics 4000 1536 MB
  • ソフトウェア環境
項目 情報
PHP バージョン 7.4.3
Laravel バージョン 7.0.8

実施条件

実施方法概要

  1. コントローラの確認
  2. アクションの記載変更
  3. 確認

実施方法詳細

  1. コントローラの確認

    1. PDFとして表示したいビューファイルが呼び出されているコントローラファイルを表示する。

      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      
      
      
      
    2. use Illuminate\Http\Request;の下に下記の内容を記載する。

      use PDF;
      
    3. コントローラファイルの設定部分が下記の様になっていることを確認する。

      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      
      use PDF;
      
      
      
      
  2. アクションの記載変更

    1. 先に確認したコントローラファイルのビューを表示しているアクションを確認する。

      return view('ビューファイルディレクトリ名.ビューファイル名');
      
    2. 先に確認したreturn部分を下記の様に記載する。

      $pdf = PDF::loadView('ビューファイルディレクトリ名.ビューファイル名');
      return $pdf->stream();
      
  3. 確認

    1. 下記コマンドを実行してローカルサーバを起動する。

      $ cd アプリ名ディレクトリ
      $ php artisan serve
      
    2. 修正したアクションとリンクするURLにアクセスする。

    3. PDFで表示されればOKである。(日本語は文字化け表示されるが、現在の設定だと正常である。)

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

【WordPress】カスタムブロックの作り方を書いてみた

はじめに

WordPress 5.0からGutenbergと呼ばれるブロックエディターがデフォルトのエディターとして採用されました。
そのため、今後はこのブロックエディターによる開発が増えてくると思われます。

個人的には、今までのWysiWygエディターに不満を感じていたわけではありませんが、ブロックエディターという選択肢が増えたことで、できる事の幅が増えてくる と思います。
本記事では、このブロックエディターの新規ブロック(以降、カスタムブロック)の作り方を記載して行きます。

ブロックエディターとは?

ブロックエディターは、名前の通り、HTMLをブロックように積み上げてHTMLを作成していくエディターです。

詳細は、以下を参照ください。

https://ja.wordpress.org/gutenberg/

ブロックエディターの特徴の1つとして、上記リンク先のページにも記載がありますが、 実際のサイトと同様に表示されるエディター。 であることです。
これから記載するカスタムブロックも、実際のサイトと同様に表示されるよう作成して行きます。

また、WordPressのブロックエディターは、大きく分けて以下の2種類のカスタムブロックを作成できます。

  • 動的ブロック ・・・ 最新の投稿を表示するブロック等の動的にブロックの内容が変化するブロック
  • 静的ブロック ・・・ 動的とは反対で、画像アップロードやテキストフィールド等のブロックの内容が変化しないブロック

これから記載するカスタムブロックは静的ブロックを作成して行きます。(動的ブロックは別途記載予定)

作成するカスタムブロック

以下のようなテキストが書けて、背景色をサイドナビゲーションから選択できるカスタムブロックを作成して行きます。
サンプルブロックは、エディターと画面表示(以降、フロント)にCSSが適応されるブロックと、エディターのみCSSが適応されるブロックの2種類作成します。

sample-block-2.png

環境準備

以下がインストールされている事が前提です。

  • PHP: 7.1以上
  • Node: v10.x
  • Yarn: v1.9.4

WordPressのブロックエディターはReactで作成されています。
これから作成するカスタムブロックも、ReactのComponentを使って開発して行きます。
しかし、通常のReactを使用するのではなく、WordPress側で用意されているReactを使用します。そのため、Reactをインストールする必要はありません。

以降、ライブラリのインストールです。

[必須]package.jsonを用意する

以下のようなpackage.jsonを作成します。

{
  "name": "sample-block",
  "version": "1.0.0",
  "description": "Sample Block",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Kodak",
  "license": "GPL-2.0"
}

[任意]webpackを用意する

こちらは任意です。
JavaScriptをwebpackを通してビルドしたい方は、以下をインストールしてください。
今回は、後述する@wordpress/scriptsを使ってビルドをするので、インストールしません。

yarn add -D webpack webpack-cli

[必須]BABELを用意する

以下をインストールします。
カスタムブロックはJSXを使いたいので@babel/preset-reactもインストールします。

yarn add -D @babel/core babel-loader @babel/preset-env @babel/preset-react
  • @babel/core ・・・ BABEL本体
  • babel-loader ・・・ webpackからBABELを通すためのライブラリ
  • @babel/preset-env ・・・ 特定の環境(ブラウザ)に合わせた変換をしてくれるライブラリ
  • @babel/preset-react ・・・ JSXのコンパイル可能にするライブラリ

[任意]SASSに対応させるためのライブラリを用意する

こちらは任意です。
今回はSASSに対応させたいため、以下をインストールします。

yarn add -D node-sass css-loader sass-loader
  • node-sass ・・・ SASS(SCSS)をCSSに変換するライブラリ
  • sass-loader ・・・ webpackからSASS(SCSS)をCSSに変換するライブラリ
  • css-loader ・・・ CSSをJavaScriptにバンドルするライブラリ

[任意]CSSを拡張するためのライブラリを用意する

こちらは任意ですが、インストールしておいたほうが良いです。
ベンダープレフィックスやCSSを別ファイルとして出力してくれます。

yarn add -D postcss-loader autoprefixer mini-css-extract-plugin optimize-css-assets-webpack-plugin terser-webpack-plugin
  • postcss-loader ・・・ ベンダープレフィックスをCSSに追加するため&ネストしたCSSを解析してくれるライブラリ
  • autoprefixer ・・・ ベンダープレフィックスをCSSに追加(postcss-loaderのプラグイン)
  • mini-css-extract-plugin ・・・ CSSを別ファイルとして出力するためのライブラリ
  • optimize-css-assets-webpack-plugin ・・・ 作成するCSSを圧縮するためのライブラリ
  • terser-webpack-plugin ・・・ CSSを圧縮する際、さらにCSSを縮小してくれる(邪魔なコメントやscourceMapを削除してくれる)ライブラリ

[必須]ビルドフォルダーにゴミファイルを残さないためのライブラリを用意する

ビルドフォルダーにゴミファイルを残さないようにするため、これはインストールしておきましょう。

yarn add -D clean-webpack-plugin
  • clean-webpack-plugin ・・・ ビルドする度に、ビルドフォルダーを削除するライブラリ

[必須]カスタムブロックを作るライブラリを用意する

カスタムブロックを作成するためのライブラリです。

yarn add -D @wordpress/browserslist-config @wordpress/scripts @wordpress/blocks @wordpress/dependency-extraction-webpack-plugin
  • @wordpress/browserslist-config ・・・ WordPressのブロックエディターが動くブラウザリストのライブラリ
  • @wordpress/scripts ・・・ WordPressのブロックエディター用のwebpackが詰め込まれたライブラリ。これを使用しない場合は、webpackを別途インストールする必要がある。
  • @wordpress/blocks ・・・ WordPressでカスタムブロックを作るためのライブラリ。
  • @wordpress/dependency-extraction-webpack-plugin ・・・ ライブラリの依存関係を自動で解決するassets.phpファイルを生成してくれるライブラリ

[任意]作成するカスタムブロックに合わせてライブラリを用意する

作りたいカスタムブロックに合わせて、ライブラリをインストールします。
今回は、@wordpress/block-editor@wordpress/componentsを使用します。

yarn add -D @wordpress/block-editor @wordpress/components

各ライブラリの詳細は、公式サイトを参照してください。

https://developer.wordpress.org/block-editor/packages/

事前準備

package.json(折りたたんでいます)
{
  "name": "sample-block",
  "version": "1.0.0",
  "description": "Sample Block",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "wp-scripts build --mode=development --config webpack.config.js --watch",
    "build": "wp-scripts build --mode=production --config webpack.config.js"
  },
  "author": "Kodak",
  "license": "GPL-2.0",
  "devDependencies": {
    "@babel/core": "^7.8.7",
    "@babel/preset-env": "^7.8.7",
    "@babel/preset-react": "^7.8.3",
    "@wordpress/block-editor": "^3.7.5",
    "@wordpress/blocks": "^6.12.1",
    "@wordpress/browserslist-config": "^2.6.0",
    "@wordpress/components": "^9.2.4",
    "@wordpress/dependency-extraction-webpack-plugin": "^2.4.0",
    "@wordpress/scripts": "^7.1.3",
    "autoprefixer": "^9.7.4",
    "babel-loader": "^8.0.6",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.4.2",
    "mini-css-extract-plugin": "0.6.0",
    "node-sass": "^4.13.1",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^8.0.2",
    "terser-webpack-plugin": "^2.3.5"
  },
  "browserslist": [
    "extends @wordpress/browserslist-config"
  ]
}

WordPress用のwebpack(wp-scripts)を使用して、ビルドをするため、以下のように定義します。

  "scripts": {
    "start": "wp-scripts build --mode=development --config webpack.config.js --watch",
    "build": "wp-scripts build --mode=production --config webpack.config.js"
  },

ブラウザリストはextendsを使用して読み込みます。

  "browserslist": [
    "extends @wordpress/browserslist-config"
  ]

注)mini-css-extract-pluginは、chunkFilenameを使用しているとエラーが出たのでバージョンを0.6.0にしています。

webpack.config.js(折りたたんでいます)
const autoprefixer                      = require('autoprefixer');
const MiniCSSExtractPlugin              = require('mini-css-extract-plugin');
const OptimizeCSSAssetsWebpackPlugin    = require('optimize-css-assets-webpack-plugin');
const TerserWebpackPlugin               = require('terser-webpack-plugin');
const { CleanWebpackPlugin }            = require('clean-webpack-plugin');
const DependencyExtractionWebpackPlugin = require('@wordpress/dependency-extraction-webpack-plugin');

module.exports = (env, argv) => {
                // mode('development'と'production')によって動作を切り替える関数
        function isDevelopment() {
            return argv.mode === 'development'
        }

        var config = {
        entry: {
                        editor: './src/editor.js',
                        script: './src/script.js',
        },
        output: {
            filename: "[name].js"
        },
        optimization: {
            minimizer: [
                                // sourceMapをmodeによって削除する
                new TerserWebpackPlugin({
                    sourceMap: isDevelopment()
                                }),
                                // CSSを圧縮する
                new OptimizeCSSAssetsWebpackPlugin(
                    {
                        cssProcessorOptions: {
                            map: {
                                inline: false,
                                annotation: true
                            }
                        }
                    }
                )
            ]
        },
        plugins: [
                        // assets.phpファイルを出力する
                        new DependencyExtractionWebpackPlugin(),
                        // ビルドの度にビルドフォルダーを削除する
                        new CleanWebpackPlugin(),
                        // CSSファイルを別ファイルで出力する
            new MiniCSSExtractPlugin({
                            chunkFilename: "[id].css",
                            filename: chunkData => {
                                    return chunkData.chunk.name === "script" ? 'style.css' : '[name].css';
                            }
                        })
                ],
                // source-mapを使用する
        devtool: 'source-map',
        module: {
            rules: [
                {
                                        // JavaScriptのローダーの設定
                    test: /\.js$/,
                    exclude: /node_modules/,
                    use: {
                                                // BABELを使用する
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                '@babel/preset-env',
                                [
                                    '@babel/preset-react',
                                    {
                                                                                // Reactで使用する関数をBABELをトランスパイルします
                                                                                // 通常は"React.createElement.xxxx"を記述しますが、WordPressのブロックエディターはWordPress用のReactを使用するため、以下のように記述します
                                        "pragma": "wp.element.createElement",
                                        "pragmaFrag": "wp.element.Fragment",
                                        "development": isDevelopment()
                                    }
                                ]
                            ]
                        }
                    }
                },
                {
                                        // SASSのローダーの設定
                    test: /\.(sa|sc|c)ss$/,
                    use: [
                                                // CSSを別ファイル化するためのローダーを指定
                        MiniCSSExtractPlugin.loader,
                        'css-loader',
                        {
                                                        // postcss-loaderのプラグインを使って、ベンダープレフィックスをCSSに追加
                            loader: 'postcss-loader',
                            options: {
                                plugins: [
                                    autoprefixer()
                                ]
                            }
                        },
                        'sass-loader'
                    ]
                }
            ]
        },
    };
    return config;
}

Reactで使用する関数をBABELをトランスパイルします。
通常は"React.createElement.xxxx"を記述しますが、WordPressのブロックエディターはWordPress用のReactを使用するため、以下のように記述します。

'@babel/preset-react',
{
    "pragma": "wp.element.createElement",
    "pragmaFrag": "wp.element.Fragment",
}

WordPress用のwebpack(wp-scripts、dependency-extraction-webpack-plugin)を使用して、ビルドをしない方は、WordPressの依存関係を解決するために以下のような記述が必要なので注意してください。

ご参考)https://developer.wordpress.org/block-editor/packages/packages-dependency-extraction-webpack-plugin/

externals: {
    "@wordpress/blocks": ["wp", "blocks"],
    "@wordpress/editor": ["wp", "editor"],
    "@wordpress/components": ["wp", "components"],
    "@wordpress/element": ["wp", "element"],
}

プラグインとしてカスタムブロックを開発

カスタムブロックを作成する流れを図にすると以下のような感じです。

sample-block-make.png

どこから作成しても問題ありませんが、カスタムブロックJSメインコードとカスタムブロックスタイルコードを作成して、最後にカスタムブロック登録コードを作るのが良いと思います。

firstblock/editor.js(折りたたんでいます)
import './editor.scss';
import { registerBlockType } from "@wordpress/blocks";
import { RichText, InspectorControls } from "@wordpress/editor";
import { PanelBody, ColorPalette } from "@wordpress/components";

registerBlockType('sample-block/firstblock', {
    title: 'サンプルブロック(editor)',

    icon: 'wordpress-alt',

    category: 'common',

    example: {},

    attributes: {
        content: {
            type: 'string',
            source: 'html',
            selector: 'p'
        },
        colorPrefix: {
            type: 'string',
            default: ''
        }
    },

    edit( { className, attributes, setAttributes } ) {
        const { content, colorPrefix } = attributes;

        const changeBackGroundColor = (backGroundColor) => {
            let color_prefix = '';

            switch ( backGroundColor ) {
                case 'blue':
                    color_prefix = '--blue' ;
                    break;
                case 'red':
                    color_prefix = '--red';
                    break;
                case 'green':
                    color_prefix = '--green';
                    break;
                case 'yellow':
                    color_prefix = '--yellow';
                    break;
                default:
                    color_prefix = '';
                    break;
            }
            setAttributes({colorPrefix: color_prefix})
        }

        return (
            <div className={ className }>
                <InspectorControls>
                        <PanelBody title='背景カラー設定'>
                                <ColorPalette
                                    colors={[
                                        {name: 'ブルー', color: 'blue'},
                                        {name: 'レッド', color: 'red'},
                                        {name: 'グリーン', color: 'green'},
                                        {name: 'イエロー', color: 'yellow'},
                                    ]}
                                    disableCustomColors='true'
                                    onChange={ changeBackGroundColor }
                                />
                        </PanelBody>
                </InspectorControls>
                <RichText
                    className={`wp-block-sample-block-firstblock__content${colorPrefix}`}
                    tagName='p'
                    onChange={ ( content ) => setAttributes( { content: content } ) }
                    value={ content }
                />
            </div>
        );
    },

    save( { attributes } ) {
        const { content, colorPrefix } = attributes;

        return (
            <div>
                { content && (
                    <RichText.Content
                        className={`wp-block-sample-block-firstblock__content${colorPrefix}`}
                        tagName='p'
                        value={ content }
                    />
                )}
            </div>
        );
    }
});

firstblock/editor.scss(折りたたんでいます)
.wp-block-sample-block-firstblock {
  &__content {
    color: black;
    background-color:   white;
  }

  &__content--red {
    color: white;
    background-color:   red;
  }

  &__content--green {
    color: white;
    background-color:   green;
  }

  &__content--blue {
    color: white;
    background-color:   blue;
  }

  &__content--yellow {
    color: red;
    background-color:   yellow;
  }
}

edit関数がエディター表示の部分、save関数がフロントを表示する部分になります。
attributesは、主にエディターで設定した値(edit関数で設定した値)をフロントへ渡す値(save関数へ渡す値)を定義します。
上記だと、RichText書かれたValue値やColorPaletteで選択したクラス名のプレフィックスなどです。

サイドナビゲーションは、InspectorControls、PanelBodyなどを組み合わせれば簡単に実装できます。

plugin.php(折りたたんでいます)
<?php
/**
 * Plugin Name: Sample-Block
 * Plugin URI: *
 * Description: Sample用カスタムブロック.
 * Version: 1.0
 * Author: Kodak
 * Author URI: *
 * License: GPL2
 *
 * @package sample-block
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * カスタムブロックの登録.
 */
function sample_block_register() {
    // スクリプトファイルをWordPressに登録する.
    $editor_asset_file = include( plugin_dir_path( __FILE__ ) . 'dist/editor.asset.php' );
    $script_asset_file = include( plugin_dir_path( __FILE__ ) . 'dist/script.asset.php' );

    // editor-script用のJSファイル.
    wp_register_script(
        'sample-block-editor-script',
        plugins_url( 'dist/editor.js', __FILE__ ),
        $editor_asset_file['dependencies'],
        $editor_asset_file['version']
    );

    // editor-style用のCSSファイル.
    wp_register_style(
        'sample-block-editor-style',
        plugins_url( 'dist/editor.css', __FILE__ ),
        [ 'wp-edit-blocks' ],
        filemtime( plugin_dir_path( __FILE__ ) . 'dist/editor.css' )
    );

    // script用のJSファイル.
    wp_register_script(
        'sample-block-script',
        plugins_url( 'dist/script.js', __FILE__ ),
        $script_asset_file['dependencies'],
        $script_asset_file['version']
    );

    // style用のCSSファイル.
    wp_register_style(
        'sample-block-style',
        plugins_url( 'dist/style.css', __FILE__ ),
        [ 'wp-edit-blocks' ],
        filemtime( plugin_dir_path( __FILE__ ) . 'dist/style.css' )
    );

    // カスタムブロックの登録(editor).
    register_block_type(
        'sample-block/firstblock',
        [
            'editor_script' => 'sample-block-editor-script', // エディター画面の時のみスクリプトを読み込む.
            'editor_style'  => 'sample-block-editor-style', // エディター画面の時のみスタイルを読み込む.
        ]
    );

    // カスタムブロックの登録.
    register_block_type(
        'sample-block/secondblock',
        [
            'script' => 'sample-block-script', // エディター・フロントの両画面でスクリプトを読み込む.
            'style'  => 'sample-block-style', // エディター・フロントの両画面でスタイルを読み込む.
        ]
    );
}
add_action( 'enqueue_block_editor_assets', 'sample_block_register' );
add_action( 'enqueue_block_assets', 'sample_block_register', 1 );

WordPress用のwebpack(wp-scripts、dependency-extraction-webpack-plugin)を使用して、ビルドした場合、スクリプトファイル(xxxx.asset.php)が作られます。
ライブラリの依存関係やバージョン情報が書かれているので、それらを使ってJSファイルを登録します。

カスタムブロックの登録は、editor_scripteditor_stylescriptstyleの4種類があります。
4つの違いは以下の通りです。

  • editor_script ・・・ エディター画面の時のみスクリプトを読み込む
  • editor_style ・・・ エディター画面の時のみスタイルを読み込む
  • script ・・・ エディター・フロントの両画面でスクリプトを読み込む
  • style ・・・ エディター・フロントの両画面でスタイルを読み込む

例えば、本記事の最初に見せたブロックをプレビューしてフロントから見てみると以下のように表示されます。

sample-block-preview.png

上:エディターとフロントにCSSが適応されるブロック(scriptstyle)を使用した場合
下:エディターのみCSSが適応されるブロック(editor_scripteditor_style)を使用した場合

上の方は、エディターで指定した背景色(赤色)がフロントに適用されていることがわかります。
対して、下の方はエディターで指定した背景色(緑色)がフロントには反映されていないことがわかります。

最後にHookですが、editor_scripteditor_styleを使用する場合はenqueue_block_editor_assetsを使いましょう。
scriptstyleを使用する場合はenqueue_block_assetsを使いましょう。
ちゃんとブロックエディター用のHookが用意されているので、適切なHookを使用するようにしましょう。間違ってもinitは使用しないようにしましょう。

さいごに

というわけで、長々とカスタムブロックの作り方を書いてみましたが、いかがだったでしょうか。
個人的に一番苦労したのはwebpackの設定です。
私自身バックエンドのエンジニアなので、webpackの設定方法を理解するのにえらい時間が掛かりました・・・
カスタムブロックはモダンな作りになっていて面白いのですが、学習コストが高いのが辛いところですね。

今度は、動的ブロックの作成にも挑戦したいと思います。

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

tailコマンド メモ

PHP側のログを追うことになったのでメモ(FuelPhp以外だとよくわからん)

▼追記されるログを表示
 tail -f ファイル名
 tailf ファイル名

 ■参考URL
 https://uxmilk.jp/12218

▼PHPのログ
 これしか使ってない
 log::info('abcdeとかとか・・なんでも日本語もいけました')

 ■参照URL
 http://fuelphp.jp/docs/1.7/classes/log.html

▼Gitコマンド

 普段SourceTreeでコマンドあんまり打たないからリンクだけ貼っておこう。
 https://qiita.com/2m1tsu3/items/6d49374230afab251337
 

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

ポートフォリオ

記事の概要

PHP,MySQLを使って作った掲示板アプリです。
レスポンシブ対応してあります。

作った背景

自分の母親がピアノ教室の先生をやっていて、生徒同士や先生と生徒が気軽に情報を交換したり、質問ができるようなものを作りたいと思ったからです。

機能

会員登録していなくても掲示板を見ることができ、会員登録することでメッセージを投稿できるようになるといった感じです。

会員登録

会員のお名前、メールアドレス、パスワードを入力します。
会員登録する際に、空欄があったり、既に登録されている会員だったり、パスワードが3文字以下だった場合は、エラーメッセージを表示するようにしました。
sampleapp.gif

ログイン機能

メールアドレスとパスワードでログインができるといったシステムです。
ログインでも空欄があったり、登録されていない場合などはエラーメッセージを表示します。
sampleapp2.gif

メッセージ送信機能

自由にメッセージを投稿することができます。
また、返信することもできます。
sampleapp3.gif

削除機能

自分が投稿したメッセージだけを削除できるようにしました。
sampleapp4.gif

ログアウト機能

ログアウトができます。
sampleapp5.gif

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

WordPressブログの始め方を徹底解説します!

以下の記事をご参考に!

https://newmtube07.com/wordpress-blog-start/

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

Nginxのエラー「413 Request Entity Too Large」の対処

問題

Nginxを使ったアプリにおいて少し大きめのファイルをアップロードしたところ、このようなエラーが
image.png

ファイルサイズが大きすぎるためこのようなエラーが出るようです。

Nginxはデフォルトでは最大のアップロードのサイズが1MBとなっているようです。

対処法

Nginxの設定を変更

自分は環境構築にDockerを利用しているのでdocker/nginx/default.confを編集します。

default.conf
server {
    listen       0.0.0.0:80;
    server_name  localhost;
    charset      utf-8;
    client_max_body_size 10M; #追加

serverブロック内にclient_max_body_sizeの項目を追加またはサイズの変更をします。

php.iniの設定を変更

自分の場合はphp側の変更をしなくても行けたのですが、必要な場合もあるようなのでメモ程度に書きます。
post_max_sizeとupload_max_filesizeの項目を編集します。

php.ini
post_max_size = 10M
upload_max_filesize = 10M

設定を変更したらnginxを再起動します。

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

正規表現で制御文字以外とマッチさせる方法(in PHP)

※半角スペースは制御文字に含めない

preg_match('/\A[^\x00-\x1f\x7f]+\z/', $text)

参考: https://kanonji.hatenadiary.com/entry/20100226/1267173195

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

lockファイルがコンフリクトしたときの対処法

ヾ(・ω<)ノ" 三三三● ⅱⅲ コロコロ♪


------------------- ↓ 余談はここから ↓-------------------

lockファイルでインストールを制御ってのは、
割と理想論だと思っている。

まぁ、それはいいとして、
lockファイルをコミットすると、
ほぼ間違いなくコンフリクトする。
そんなときの対処法を残しておこう。


------------------- ↓ 本題はここから ↓-------------------

composer.lock

コンフリクトを適当に解消して以下のコマンドを打つ

$ composer update --lock

package-lock.json

コンフリクトした状態のまま以下のコマンドを打つ

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

Laravel7 新たに追加された3つのマイグレーションエイリアス

Laravel7 で追加されたマイグレーションエイリアス

使用できるカラムタイプのエイリアス

  • id
  • foreignId

外部キー制約のエイリアス

  • constrained

上記の3つのエイリアスが追加されてます。

環境

$ php artisan -V
Laravel Framework 7.2.2

使い方

Schema::table('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained();
});

上の定義は、下記のように定義したのと同じ内容になります。

Schema::table('posts', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->unsignedBigInteger('user_id');
    $table->foreign('user_id')->references('id')->on('users');
});

補足

id

src/Illuminate/Database/Schema/Blueprint.php
    /**
     * Create a new auto-incrementing big integer (8-byte) column on the table.
     *
     * @param  string  $column
     * @return \Illuminate\Database\Schema\ColumnDefinition
     */
    public function id($column = 'id')
    {
        return $this->bigIncrements($column);
    }

シンプルなエイリアスですね。
個人的には bigIncrements って指定されてた方が分かりやすいと思うんですけど...

foreignId

src/Illuminate/Database/Schema/Blueprint.php
    /**
     * Create a new unsigned big integer (8-byte) column on the table.
     *
     * @param  string  $column
     * @return \Illuminate\Database\Schema\ForeignIdColumnDefinition
     */
    public function foreignId($column)
    {
        $this->columns[] = $column = new ForeignIdColumnDefinition($this, [
            'type' => 'bigInteger',
            'name' => $column,
            'autoIncrement' => false,
            'unsigned' => true,
        ]);

        return $column;
    }

ForeignIdColumnDefinition のインスタンスが返ってきますね。

constrained

src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php
    /**
     * Create a foreign key constraint on this column referencing the "id" column of the conventionally related table.
     *
     * @param  string|null  $table
     * @return \Illuminate\Support\Fluent|\Illuminate\Database\Schema\ForeignKeyDefinition
     */
    public function constrained($table = null)
    {
        return $this->references('id')->on($table ?: Str::plural(Str::before($this->name, '_id')));
    }

foreignId に返ってきた ForeignIdColumnDefinition にメソッドチェーンでつなげて呼び出します。
$table->foreign('user_id')->references('id')->on('users'); このようにテーブル名を指定しますが、 ->foreign('user_id') から users と複数形のテーブルを自動的に設定してくれます。

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

Laravel7 新たに追加された id, foreignId, constrained マイグレーションエイリアス

Laravel7 で追加されたマイグレーションエイリアス

使用できるカラムタイプのエイリアス

  • id
  • foreignId

外部キー制約のエイリアス

  • constrained

上記の3つのエイリアスが追加されてます。

環境

$ php artisan -V
Laravel Framework 7.2.2

使い方

Schema::table('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained();
});

上の定義は、下記のように定義したのと同じ内容になります。

Schema::table('posts', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->unsignedBigInteger('user_id');
    $table->foreign('user_id')->references('id')->on('users');
});

補足

id

src/Illuminate/Database/Schema/Blueprint.php
    /**
     * Create a new auto-incrementing big integer (8-byte) column on the table.
     *
     * @param  string  $column
     * @return \Illuminate\Database\Schema\ColumnDefinition
     */
    public function id($column = 'id')
    {
        return $this->bigIncrements($column);
    }

シンプルなエイリアスですね。
個人的には bigIncrements って指定されてた方が分かりやすいと思うんですけど...

foreignId

src/Illuminate/Database/Schema/Blueprint.php
    /**
     * Create a new unsigned big integer (8-byte) column on the table.
     *
     * @param  string  $column
     * @return \Illuminate\Database\Schema\ForeignIdColumnDefinition
     */
    public function foreignId($column)
    {
        $this->columns[] = $column = new ForeignIdColumnDefinition($this, [
            'type' => 'bigInteger',
            'name' => $column,
            'autoIncrement' => false,
            'unsigned' => true,
        ]);

        return $column;
    }

ForeignIdColumnDefinition のインスタンスが返ってきますね。

constrained

src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php
    /**
     * Create a foreign key constraint on this column referencing the "id" column of the conventionally related table.
     *
     * @param  string|null  $table
     * @return \Illuminate\Support\Fluent|\Illuminate\Database\Schema\ForeignKeyDefinition
     */
    public function constrained($table = null)
    {
        return $this->references('id')->on($table ?: Str::plural(Str::before($this->name, '_id')));
    }

foreignId に返ってきた ForeignIdColumnDefinition にメソッドチェーンでつなげて呼び出します。
$table->foreign('user_id')->references('id')->on('users'); このようにテーブル名を指定しますが、 ->foreign('user_id') から users と複数形のテーブルを自動的に設定してくれます。

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

PHPStormでクラスのnamespaceを移動する

以前 PHPStormでnamespaceを一括変更 でディレクトリの移動にあわせてnamespaceも一緒に移動する方法を書いたのですが、今回はクラスを特定のnamespaceに移動させる方法です。

PHPStormでファイルを開き、クラス名にカーソルを当てた状態でF6キーを押すと、
namespaceの移動のダイアログが出ます。

phpstorm.jpg

Move Class 〇〇 to namespaceのところを編集して保存するとクラスのnamespaceとあわせてファイルのディレクトリも移動します。

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

PHP Intelephense の有償版を Neovim で使ってみる

PHP Intelephense は PHP の Language Server の一つ。
VS Code での利用例は

など様々見られる一方、 Neovim での利用に関する情報があまり見つからなかったので、導入手順をメモ。
また有償版だとクラスやメソッドの実装へのジャンプやリネームなどの機能も利用できるようになるとのことで、せっかくなので有償版を購入して使ってみた。

有償版のライセンスキー取得

ライセンス購入ページ から必要な情報を入力して購入する(記事投稿時点で ¥1,000 )。
後から送付されるメールにライセンスキーが記載されている。

Neovim 側の設定

language server のプラグインは coc.nvim を使用した。
coc.nvim の PHP 拡張である coc-phpls を使えばライセンスキーを coc-settings.json に記載するだけで特にその他の設定なしで利用できる。
ちなみにキーに含まれる licence のスペルは license ではないので注意( Intelephense の開発元はオーストラリアらしいが、アメリカ英語と異なる?)。

coc-settings.json
{
    "intelephense.licenceKey": "your licence key",
}

後は :CocInstall phpls で拡張機能を追加すればオーケー。

課題

前述した実装へのジャンプやリネームもきちんと動作したので、 PHP の language server としてはこれがベストかもしれない。
ただインターフェースへのジャンプが上手くできなかったりするので、まだ諸々の設定を調べる余地がありそう...。

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

Mac Catalina 10.15 に、古いバージョンのPHPをインストールする

Mac Catalina 10.15 に、古いバージョンのPHPをインストールする

CakePHP2のアプリを修正依頼が合ったが、Mac Catalinaではhomebrewに
もう古いバージョンが無く・・・

(なんでそんなに古いまま、ほっといたんだよってツッコミはなしで、、、)

brew tap exolnet/homebrew-deprecated
でも、php 5.6

どうしても、php5.5を入れたいと調べてたら、binary packageを入れる方法がありました。

https://php-osx.liip.ch/

PHP 5.3〜PHP 7.3がある

インストールする

curl -s https://php-osx.liip.ch/install.sh | bash -s 5.5

pathを通す

export PATH=/usr/local/php5/bin:$PATH
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Phoenix LiveViewでもPHP的に気軽なWebアプリ開発ができるんです

fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます :bow:

前作のPHP的気軽さでWebアプリを作る方法を、今度はLiveViewでも展開してみたいと思います

前作同様、LiveViewにおいても、ルーティングは不要になります

なおLiveViewでは、パラメータ渡しによるMPA(Multiple Page Application)的ハンドリングでは無く、handle_event()等によるSPA(Single Page Application)的イベントハンドラが中心となるのですが、ここでは、できるだけMPAのようなテイストになるように作ってみます

LiveViewでのSPAフォーム開発に抵抗が高い方もまだまだ多いんでは無いかと思いますが、このスタートだと案外、気楽になれるかもです

本コラムの検証環境

本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)

なおPhoenixは、1.3系でも動作します(ElixirもPhoenixのバージョンに準じた古いものでも大丈夫です)

事前準備:Phoenix PJの作成、LiveView導入

まずPhoenix PJを作成します

mix phx.new basic --no-ecto
…
Fetch and install dependencies? [Yn] 【Nを入力してEnter】
cd basic

続けて、下記コラムの手順に従って、Phoenix PJにLiveViewを導入します(ただしPJ名は、LvSampleでは無く、上記で作成したBasicで読み替えて実施してください)

LiveViewでSPA開発①: Elixirのみでフロントのリアルタイム入力/反映するSPAを実現
https://qiita.com/piacerex/items/3f8ee18c9443d63955bf

更に、LiveView用HTMLをテンプレートファイル化するために、下記コラムも実施します(こちらもPJ名は、LvSampleでは無く、上記で作成したBasicで読み替えて実施してください)

LiveViewでSPAを作る③: LiveView用のHTMLをテンプレートファイルに分離
https://qiita.com/piacerex/items/5b1aabb0c45bb9934bb9

ここまでが終わったら、ブラウザで「http://localhost:4000/realtime」にアクセスすると、LiveViewで作成された、Qiitaリアルタイム検索SPAが表示されることを確認して、準備完了です
image.png

手順①:html.leexの表示をルーティング不要に

まずはルーティングで、前作同様、「*_ path」というワイルドカード指定をすることで、多階層のURLパスを「_path」パラメータから取得できるようにします

接続先のコントローラは、LiveView専用のマッピングコントローラとします

なお、前作の続きでやられている場合は、getやpostのワイルドカード指定とぶつかるため、getやpostの分は、コメントアウトや削除しておいてください

lib/basic_web/router.ex
defmodule BasicWeb.Router do

  scope "/", BasicWeb do
    pipe_through :browser

#    get "/", PageController, :index  # <-- remove here
#    post "/*_path", PageController, :index  # <-- remove here
#    get "/*_path", PageController, :index  # <-- remove here
    live "/*_path", LiveViewController  # <-- add here

手順②:html.leexの複数階層フォルダ指定可能に

render()の第2引数に指定するパスの複数階層指定は、前作と全く同じView設定記述になります(前作の続きでやっている場合は、この手順自体が実施不要です)

lib/basic_web.ex
defmodule BasicWeb do

  def view do
    quote do
      use Phoenix.View,
        pattern: "**/*",  # <-- add here
        root: "lib/basic_web/templates",
        namespace: BasicWeb, 

手順③:パスの自動マッピング、GETパラメータの素通し

LiveView専用のマッピングコントローラを追加します

「_path」パラメータのフォルダ階層リストを、html.leexのパスにマッピングします

通常だと、mount()の第1引数であるparamsは、「_params」と利用不可にしますが、ここではGETパラメータを素通ししたいので、アンダースコア無しの「params」で定義し、テンプレートに引き渡します

lib/basic_web/live/liveview_controller.ex
defmodule BasicWeb.LiveViewController do
  use Phoenix.LiveView

  def render( assigns ) do
    params = assigns.params
    template = if params[ "_path" ] == nil, do: "blank.html", else: to_path_string( params[ "_path" ] ) <> ".html"
    BasicWeb.LiveView.render( template, assigns )
  end

  def to_path_string( path_list ), do: String.slice( Enum.reduce( path_list, "", & "#{ &2 }#{ &1 }/" ), 0..-2 )

  def mount( params, _session, socket ) do
    { :ok, assign( socket, params: params ) }
  end
end

LiveView起動直後は、assignsに「_path」パラメータが未指定(というかparams自体が未指定)なので、空のページを追加しておきます

lib/basic_web/templates/live/blank.html.leex
<h1>Now loading...</h1>

手順④:html.leexページを追加する

1)templates/live直下にhtml.leex追加

これで、page配下にhtml.leexを追加するだけで、新たなLiveViewページが追加可能となったので、追加してみます

lib/basic_web/templates/live/another.html.leex
<h1>This is another page</h1>

<%= inspect( System.build_info() ) %>
<hr>
<%= inspect( @params ) %>

ブラウザで「http://localhost:4000/another」にアクセスすると、以下のようなページが表示されます
image.png

URLにパラメータを指定してアクセスすると、@ paramsパラメータに入るので、GET処理も気軽に使えるようになります
image.png

2)templates/live配下のサブフォルダ下にhtml.leex追加

liveフォルダ配下にフォルダ階層を掘って、そこにhtml.leexを配置しても動きます

「uvw」というフォルダを掘り、「xyz.html.leex」というLiveViewテンプレートを追加します

lib/basic_web/templates/page/uvw/xyz.html.leex
<h1>I'm xyz page</h1>

<%= inspect( @params ) %>

ブラウザで「http://localhost:4000/uvw/xyz」にアクセスすると、以下のようなページが表示されます(「_path」パラメータにフォルダ階層がリストで入っていることも確認できます)
image.png

3)MPAフォームのノリでSPAフォームを気軽に書く

ここまでの手順により、LiveViewページは、html.leex追加で増やせるようになりましたが、前作で扱ったようなサーバサイドレンダリング(SSR)でのMPAと異なり、LiveViewはSPAなので、GETやPOSTによるページ間のパラメータ渡しは行いません

代わりに、LiveViewコントローラにハンドラを追加したり、mount()にパラメータ追加することで処理するため、MPAとは書き方が根本的に異なります

しかし、パラメータ設計によっては、MPAソックリな記述も可能なため、これを実現してみます

まず、引き渡すパラメータは、paramsのみとすることで、mount()のパラメータリストをいじる必要が無くなります

次に、submitのハンドラは、formタグに「phx-submit」を指定し、コントローラ側にハンドラを設けることで実現します

なお、「_path」パラメータは、「phx-submit」や「phx-change」のたびにrouter.exから来る訳では無いため、パス形式に直しておき、hiddenパラメータ化して持ち回るようにします

lib/basic_web/templates/page/uvw/form.html.leex
<h1>I'm form page</h1>

<font color="blue"><h2><%= @params[ "submited" ] %></h2></font>

<form phx-submit="submit">

<p>
メモ:
<input name="memo" type="text">
</p>

<p>
好きな言語:
<input name="language[]" type="checkbox" value="Elixir">Elixir
<input name="language[]" type="checkbox" value="Rust">Rust
<input name="language[]" type="checkbox" value="Julia">Julia
</p>

<p>
年代:
<select name="age">
  <option value="10">10代
  <option value="20">20代
  <option value="30">30代
  <option value="30">40代
  <option value="50">50代
  <option value="60">60代
  <option value="70">70代以上
</select>
</p>

<input type="submit" value="送信">

<input name="_path" type="hidden" value="<%= @_path %>">

</form>

<%= inspect @params %>

LiveView用コントローラの内容は、下記で置き換えます

まずrender()では、「_path」パラメータをリスト形式とパス形式の両方を受け付けられるように変更します)

mount()は、変更無しです

handle_event()が、「phx-submit」のためのハンドラで、ここではフォームサブミット時に、メッセージが変わるようにしているだけですが、実際はDB登録などのサブミット時処理を入れます

lib/basic_web/live/liveview_controller.ex
defmodule BasicWeb.LiveViewController do
  use Phoenix.LiveView

  def render( assigns ) do
    params = assigns.params
    page = if params[ "_path" ] == nil do
        "blank.html"
      else
        if is_list( params[ "_path" ] ) do
          to_path_string( params[ "_path" ] ) <> ".html"
        else
          params[ "_path" ] <> ".html"
        end
      end
    BasicWeb.LiveView.render( page, assigns )
  end

  def mount( params, _session, socket ) do
    { :ok, assign( socket, params: params, _path: to_path_string( params[ "_path" ] ) ) }
  end

  def to_path_string( path_list ), do: String.slice( Enum.reduce( path_list, "", & "#{ &2 }#{ &1 }/" ), 0..-2 )

  def handle_event( "submit", params, socket ) do
    new_params = 
      params
      |> Map.put( "submited",  "submited" )
    { :noreply, assign( socket, params: new_params ) }
  end
end

ブラウザで「http://localhost:4000/uvw/xyz」にアクセスすると、以下のようなページが表示されます(「_path」パラメータにフォルダ階層がリストで入っていることも確認できます)
image.png

「送信」ボタンを押下すると、サブミット処理に相当する処理が走り、画面表示が変わります
image.png

なお、「_path」をhiddenパラメータ化しないと、入力や「送信」ボタン押下した瞬間に、以下のブランクページが表示されます
image.png

4)バリデーションチェックをリアルタイムで実行

よりSPAらしい造りにするため、フォーム内容が変更された際は、バリデーションチェックがリアルタイムで実行されるようにしてみましょう

formタグに「phx-change」を指定し、コントローラ側に変更時ハンドラを設けます

ちなみに入力都度、「phx-change」が呼び出されることで、入力内容が消えてしまうため、維持するためのコードも入れています(分かりやすくするために愚直な書き方をしています、実際はフォーム値のリストを渡してループ展開するような書き方が良いでしょう)

下記内容で、form.html.leexを置き換えてください

lib/basic_web/templates/page/uvw/form.html.leex
<h1>I'm form page</h1>

<font color="red"><%= @params[ "validated" ] %></font><font color="blue"><h2><%= @params[ "submited" ] %></h2></font>

<form phx-submit="submit" phx-change="change">

<p>
メモ:
<input name="memo" type="text" value="<%= @params[ "memo" ] %>">
</p>

<p>
好きな言語:
<input name="language[]" type="checkbox" value="Elixir" <%= if @params[ "language" ] != nil && Enum.any?( @params[ "language" ], & &1 == "Elixir" ), do: "checked", else: "" %>>Elixir
<input name="language[]" type="checkbox" value="Rust" <%= if @params[ "language" ] != nil && Enum.any?( @params[ "language" ], & &1 == "Rust" ), do: "checked", else: "" %>>Rust
<input name="language[]" type="checkbox" value="Julia" <%= if @params[ "language" ] != nil && Enum.any?( @params[ "language" ], & &1 == "Julia" ), do: "checked", else: "" %>>Julia
</p>

<p>
年代:
<select name="age">
  <option value="10" <%= if @params[ "age" ] == "10", do: "selected", else: "" %>>10代
  <option value="20" <%= if @params[ "age" ] == "20", do: "selected", else: "" %>>20代
  <option value="30" <%= if @params[ "age" ] == "30", do: "selected", else: "" %>>30代
  <option value="30" <%= if @params[ "age" ] == "40", do: "selected", else: "" %>>40代
  <option value="50" <%= if @params[ "age" ] == "50", do: "selected", else: "" %>>50代
  <option value="60" <%= if @params[ "age" ] == "60", do: "selected", else: "" %>>60代
  <option value="70" <%= if @params[ "age" ] == "70", do: "selected", else: "" %>>70代以上
</select>
</p>

<input type="submit" value="送信">

<input name="_path" type="hidden" value="<%= @_path %>">

</form>

<%= inspect @params %>

バリデーションチェック用のhandle_event()も追加します

ここでも、バリデーションチェックエラー時のメッセージを愚直に作っていますが、もっとスマートな書き方をすることもできますし、Ectoのchangesetを使うこともできます

lib/basic_web/live/liveview_controller.ex
defmodule BasicWeb.LiveViewController do

  def handle_event( "change", params, socket ) do
    validated = ( if String.length( params[ "memo" ] ) > 30, do: "(メモは30文字以内にしてください)", else: "" ) <>
      ( if params[ "language" ] == [], do: "(言語は必ず1つ選択してください)", else: "" )
    new_params = 
      params
      |> Map.put( "validated", validated )
      |> Map.put( "submited",  "" )
    { :noreply, assign( socket, params: new_params ) }
  end

ブラウザで「http://localhost:4000/uvw/xyz」にアクセスし、入力を行うと、バリデーションチェックに相当する処理が走り、画面表示が変わります(「_path」パラメータがパス形式の文字列に変更されていることが確認できます)
image.png

5)フォーム毎にバリデーション/サブミットを書き分けるには?

複数のフォームがある場合、html.leexの方は簡単に追加できる一方、フォーム毎に入力項目は異なるため、バリデーション/サブミットを書き分ける必要がありますが、コントローラは1つのため、何らかの対応が必要です

最も簡単なのは、「_path」パラメータにパスがあるため、下記のように、各パス毎にスイッチしてあげることです

lib/basic_web/live/liveview_controller.ex
defmodule BasicWeb.LiveViewController do

  def handle_event( "change", params, socket ) do
    validated= case params[ "_path" ] do
      "uvw/form" => 
        ( if String.length( params[ "memo" ] ) > 30, do: "(メモは30文字以内にしてください)", else: "" ) <>
        ( if params[ "language" ] == [], do: "(言語は必ず1つ選択してください)", else: "" )
      _         => "(undefined validatd)"
    end
    new_params = 
      params
      |> Map.put( "validated", validated)
      |> Map.put( "submited",  "" )
    { :noreply, assign( socket, params: new_params ) }
  end

もちろん、もっとスマートに書ける訳ですが、入門的な内容としては、ここまでで充分かと思います

終わり

LiveViewでも、PHPみたいに気軽なWebアプリ開発ができるようになりました

このテクニックを使って、LiveView SPAによるWebアプリ構築にも慣れていっていただけたら幸いです

Vue.jsやReact等でリアルタイムフロントに慣れている方であれば、ハンドラを多少覚えるだけで使いこなせるハズです

p.s.このコラムが、面白かったり、役に立ったら…

image.pngimage.png にて、どうぞ応援よろしくお願いします:bow:

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

【Laravel7.2】Migrationを用いてテーブルを作成。Seederを用いてデータを投入する

前提環境

Laravel7.2環境を構築。Welcome!画面が表示された状態から開始。
その他、DBの設定等も済み。詳細は以下参照。

【準備編】https://qiita.com/katsuhito_01/items/db5b9581a2b6af6dc803
【導入編】https://qiita.com/katsuhito_01/items/439e3af4cea8ff00832c

Migrationを用いてテーブルを作成

まずはMigrationを用いて参照するテーブルを作成する。今回は都道府県マスタを作成。

Migration作成

Laravelプロジェクトのディレクトリ内にて、以下コマンドを実行

# php artisan make:migration create_mst_prefectures_table
Created Migration: 2020_03_22_163920_create_mst_prefectures_table

すると、database\migrations\日付_create_mst_prefectures_table.phpというファイルが作成される。
作成されたファイルをいじり、作成するテーブルの構造を記述する。
今回は以下のように記述。

【参考】:https://readouble.com/laravel/7.x/ja/migrations.html

<?php

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

class CreateMstPrefecturesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mst_prefectures', function (Blueprint $table) {
            $table->integer('id')->primary();
            $table->string('name', 10);
            $table->string('name_kana', 10);
            $table->timestamps();
        });
    }

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

Migration実行

構造を記述したら、Migrationを実行。

# php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.02 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0.01 seconds)
Migrating: 2020_03_22_163920_create_mst_prefectures_table
Migrated:  2020_03_22_163920_create_mst_prefectures_table (0.01 seconds)

MySQLに入り、テーブルを確認。

mysql> show tables;
+-----------------------+
| Tables_in_mypage01_db |
+-----------------------+
| failed_jobs           |
| migrations            |
| mst_prefectures       |
| users                 |
+-----------------------+
4 rows in set (0.00 sec)

mysql> desc mst_prefectures;
+------------+-------------+------+-----+---------+-------+
| Field      | Type        | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| id         | int(11)     | NO   | PRI | NULL    |       |
| name       | varchar(10) | NO   |     | NULL    |       |
| name_kana  | varchar(10) | NO   |     | NULL    |       |
| created_at | timestamp   | YES  |     | NULL    |       |
| updated_at | timestamp   | YES  |     | NULL    |       |
+------------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

出来てますね!

Seederを用いてテーブルにデータを投入

市区町村マスタができたので、Seederを用いてデータを投入する。

Modelを作成する

まずはModelを作成。
以下実行で、app直下にModelが作成される。
とりあえず今は何も書かない。

php artisan make:model MstPrefecture

ちなみに作成されたModelの中身を見てみると、空のクラスがあるだけ。
テーブルとの紐づけを明示する記述がどこにもない。

明示的に設定することもできるが、公式ドキュメントには以下の記述がある。

他の名前を明示的に指定しない限り、クラス名を複数形の「スネークケース」にしたものが、テーブル名として使用されます。今回の例で、EloquentはFlightモデルをflightsテーブルに保存します。モデルのtableプロパティを定義し、カスタムテーブル名を指定することもできます。

【引用元】https://readouble.com/laravel/5.7/ja/eloquent.html

今回でいうと、「MstPrefectur」を「mst_prefectures」と自動的に結び付けてつけてくれる。
逆を言うと、テーブル名を決める際はLaravelの命名規則に則っていないと、こういった恩恵が受けられない。
「おせっかいだ」と思った方は、Modelのクラス内に以下記述を追加することで、Modelとテーブルを明示的に紐づけることができる。

protected $table = 'テーブル名';

Seeder作成

続いてSeederを作成。

# php artisan make:seeder MstPrefecturesSeeder
Seeder created successfully.

以下のファイルが作成される。
database\seeds\MstPrefecturesSeeder.php

早速中身をいじり、投入するデータを記述する。

<?php

use Illuminate\Database\Seeder;
use App\MstPrefecture;

class MstPrefecturesSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        MstPrefecture::insert([
            ['id' =>  1, 'name' => '北海道', 'name_kana' => 'ホッカイドウ', 'created_at' => now(), 'updated_at' => now()],
            ['id' =>  2, 'name' => '青森県', 'name_kana' => 'アオモリケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' =>  3, 'name' => '岩手県', 'name_kana' => 'イワテケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' =>  4, 'name' => '宮城県', 'name_kana' => 'ミヤギケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' =>  5, 'name' => '秋田県', 'name_kana' => 'アキタケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' =>  6, 'name' => '山形県', 'name_kana' => 'ヤマガタケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' =>  7, 'name' => '福島県', 'name_kana' => 'フクシマケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' =>  8, 'name' => '茨城県', 'name_kana' => 'イバラキケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' =>  9, 'name' => '栃木県', 'name_kana' => 'トチギケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 10, 'name' => '群馬県', 'name_kana' => 'グンマケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 11, 'name' => '埼玉県', 'name_kana' => 'サイタマケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 12, 'name' => '千葉県', 'name_kana' => 'チバケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 13, 'name' => '東京都', 'name_kana' => 'トウキョウト', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 14, 'name' => '神奈川県', 'name_kana' => 'カナガワケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 15, 'name' => '新潟県', 'name_kana' => 'ニイガタケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 16, 'name' => '富山県', 'name_kana' => 'トヤマケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 17, 'name' => '石川県', 'name_kana' => 'イシカワケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 18, 'name' => '福井県', 'name_kana' => 'フクイケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 19, 'name' => '山梨県', 'name_kana' => 'ヤマナシケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 20, 'name' => '長野県', 'name_kana' => 'ナガノケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 21, 'name' => '岐阜県', 'name_kana' => 'ギフケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 22, 'name' => '静岡県', 'name_kana' => 'シズオカケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 23, 'name' => '愛知県', 'name_kana' => 'アイチケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 24, 'name' => '三重県', 'name_kana' => 'ミエケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 25, 'name' => '滋賀県', 'name_kana' => 'シガケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 26, 'name' => '京都府', 'name_kana' => 'キョウトフ', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 27, 'name' => '大阪府', 'name_kana' => 'オオサカフ', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 28, 'name' => '兵庫県', 'name_kana' => 'ヒョウゴケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 29, 'name' => '奈良県', 'name_kana' => 'ナラケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 30, 'name' => '和歌山県', 'name_kana' => 'ワカヤマケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 31, 'name' => '鳥取県', 'name_kana' => 'トットリケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 32, 'name' => '島根県', 'name_kana' => 'シマネケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 33, 'name' => '岡山県', 'name_kana' => 'オカヤマケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 34, 'name' => '広島県', 'name_kana' => 'ヒロシマケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 35, 'name' => '山口県', 'name_kana' => 'ヤマグチケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 36, 'name' => '徳島県', 'name_kana' => 'トクシマケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 37, 'name' => '香川県', 'name_kana' => 'カガワケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 38, 'name' => '愛媛県', 'name_kana' => 'エヒメケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 39, 'name' => '高知県', 'name_kana' => 'コウチケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 40, 'name' => '福岡県', 'name_kana' => 'フクオカケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 41, 'name' => '佐賀県', 'name_kana' => 'サガケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 42, 'name' => '長崎県', 'name_kana' => 'ナガサキケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 43, 'name' => '熊本県', 'name_kana' => 'クマモトケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 44, 'name' => '大分県', 'name_kana' => 'オオイタケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 45, 'name' => '宮崎県', 'name_kana' => 'ミヤザキケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 46, 'name' => '鹿児島県', 'name_kana' => 'カゴシマケン', 'created_at' => now(), 'updated_at' => now()],
            ['id' => 47, 'name' => '沖縄県', 'name_kana' => 'オキナワケン', 'created_at' => now(), 'updated_at' => now()]
        ]);
    }
}

実行するSeederに追加

投入するデータの記述ができたら、以下を編集。
実行するSeederとして追加しておく。
database\seeds\DatabaseSeeder.php

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(MstPrefecturesSeeder::class);
    }
}

Seederを実行

まずはComposerオートローダを再作成。
基本的に新しいクラスを追加したときは、このコマンドを実行しておく

# composer dump-autoload
Package manifest generated successfully.
Generated optimized autoload files containing 4098 classes

そしてSeederを実行。

# php artisan db:seed
Seeding: MstPrefecturesSeeder
Seeded:  MstPrefecturesSeeder (0.06 seconds)
Database seeding completed successfully.

MySQLよりテーブルを見てみると…

mysql> select * from mst_prefectures order by id;
+----+--------------+--------------------+---------------------+---------------------+
| id | name         | name_kana          | created_at          | updated_at          |
+----+--------------+--------------------+---------------------+---------------------+
|  1 | 北海道       | ホッカイドウ       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
|  2 | 青森県       | アオモリケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
|  3 | 岩手県       | イワテケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
|  4 | 宮城県       | ミヤギケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
|  5 | 秋田県       | アキタケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
|  6 | 山形県       | ヤマガタケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
|  7 | 福島県       | フクシマケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
|  8 | 茨城県       | イバラキケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
|  9 | 栃木県       | トチギケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 10 | 群馬県       | グンマケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 11 | 埼玉県       | サイタマケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 12 | 千葉県       | チバケン           | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 13 | 東京都       | トウキョウト       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 14 | 神奈川県     | カナガワケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 15 | 新潟県       | ニイガタケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 16 | 富山県       | トヤマケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 17 | 石川県       | イシカワケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 18 | 福井県       | フクイケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 19 | 山梨県       | ヤマナシケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 20 | 長野県       | ナガノケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 21 | 岐阜県       | ギフケン           | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 22 | 静岡県       | シズオカケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 23 | 愛知県       | アイチケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 24 | 三重県       | ミエケン           | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 25 | 滋賀県       | シガケン           | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 26 | 京都府       | キョウトフ         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 27 | 大阪府       | オオサカフ         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 28 | 兵庫県       | ヒョウゴケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 29 | 奈良県       | ナラケン           | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 30 | 和歌山県     | ワカヤマケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 31 | 鳥取県       | トットリケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 32 | 島根県       | シマネケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 33 | 岡山県       | オカヤマケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 34 | 広島県       | ヒロシマケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 35 | 山口県       | ヤマグチケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 36 | 徳島県       | トクシマケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 37 | 香川県       | カガワケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 38 | 愛媛県       | エヒメケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 39 | 高知県       | コウチケン         | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 40 | 福岡県       | フクオカケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 41 | 佐賀県       | サガケン           | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 42 | 長崎県       | ナガサキケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 43 | 熊本県       | クマモトケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 44 | 大分県       | オオイタケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 45 | 宮崎県       | ミヤザキケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 46 | 鹿児島県     | カゴシマケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
| 47 | 沖縄県       | オキナワケン       | 2020-03-23 03:17:52 | 2020-03-23 03:17:52 |
+----+--------------+--------------------+---------------------+---------------------+
47 rows in set (0.00 sec)

入ってますね!
今回はDatabaseSeeder記述のSeederをすべて実行する形でしたが、以下のように個別に実行することも可能。

php artisan db:seed --class=MstPrefecturesSeeder

次回から、CRUDを作っていきましょうか。

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

PHP サイト でも Capistrano (Ruby Gem)

環境

  • Ruby 2.6.5

設定手順

Capistrano 設定ファイルの用意

bundle add capistrano
bundle exec cap install

これで必要なファイルが作成されます。

あとは deploy.rb, config/deploy/* を変更して設定完了です。

ファイルを置けば使えるPHPサイトなら、認証設定程度を記述すればOKです。

設定ファイル例

staging.rb/production.rb
# server configuration
role :web, %w{ec2-user@123.123.123.123}
# file location
set :deploy_to, "/var/www/xxx"
deploy.rb
# application name
set :application, "xxxxxx"
# repository
set :repo_url, "sample@sample.git.jp:/sample.git"

# Default branch is :master
ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

# revision to be kept
set :keep_releases, 2

デプロイ手順

# config/deploy/staging.rb を使ったデプロイ
bundle exec cap staging deploy
# config/deploy/production.rb を使ったデプロイ
bundle exec cap production deploy

ロールバック手順

cap deploy:rollback

このほかにも cap deploy:rollback:code などがある。

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

Mac Visual Studio Code Xdebugを頑張って設定したのに全くデバッグできなくて困った話

目的

  • たくさんの人に協力してもらったのに思いも寄らない障壁があったため記憶に留めるためにまとめる

実施環境

項目 情報
OS macOS Catalina(10.15.3)
ハードウェア MacBook Pro (16-inch ,2019)
プロセッサ 2.6 GHz 6コアIntel Core i7
メモリ 16 GB 2667 MHz DDR4
グラフィックス AMD Radeon Pro 5300M 4 GB Intel UHD Graphics 630 1536 MB

困った内容

原因

  • セキュリティソフトのファイアウォールが本機能をブロックしてしまっていた。
  • 一時的にファイアウォールの設定を切りサイドデバッグモードを起動したところ各ボタンもアクティブになり単一行での実行も可能になった。

    スクリーンショット 2020-03-17 14.59.18.png

謝辞

  • 本件に関して数多くの方からフォローしていただきました。
  • 筆者の問題解決に協力いただいた皆様に感謝申し上げるとともに、これから質問する際は各種詳細情報を明確にした上で、自分がどこまでわかっていて何ができていないのかを伝えて質問するように努めます。
  • この度は誠にありがとうございます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む