20200324のPHPに関する記事は11件です。

【SEO】【canonical】重複ページ

canonicalとは

正式なURLを検索エンジンに伝えるURLの正規化を目的としたものです。
重複ページが存在している場合は検索エンジンに優先させるべきページを伝えることができます。

重複ページとは

今回のテーマですが、重複ページの具体例を挙げると、ECサイトなどで色違いの商品がそれぞれ別urlで存在する場合や、リストの並び替えでurlが異なる場合もページの内容自体は同様で順序のみが異なるためこれに該当します。
今回は並び替えの例を紹介します。

https://evsmart.net/spot/kanagawa/l141305/q141313/
上のページでは並び替えができ、並び替え後のurlでは
https://evsmart.net/spot/kanagawa/l141305/q141313/?p=1&l=20&o=2
このようなパラメータがつきますが、順序が変わっているだけでページの内容としては前者と重複しているため、どちらのページもデフォルト表示の
https://evsmart.net/spot/kanagawa/l141305/q141313/
のurlをcanonicalに書きます。

canonical.html
<link rel="canonical" href="https://evsmart.net/spot/kanagawa/l141305/q141313/" />

従って実際のurlとcanonicalが異なるため、単純に例えば$_SERVER['REQUEST_URI']を使って一律に書くということはできません。

あとcanonicalは絶対パスで書く必要があります。

終わりに

これだけだったら割と簡単です。
ただ実際にはページネーションと並び替えの両方があったりとか面倒くさいですよね。
ということでそういった場合の例も今度紹介したいと思います。

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

【Laravel】テストDBとしてSQLiteを設定する

config/database.php

config/database.phpにテスト用のDBとしてSQLiteを追加します。

database.php
    'connections' => [

        'testing_sqlite' => [
            'driver' => 'sqlite',
            'database' => ':memory:',
            'prefix' => '',
        ],
    ],

'database' => ':memory:'ではインメモリの設定をしています。

phpunit.xml

phpunit.xmlに<server name="DB_CONNECTION" value="testing_sqlite"/>を追加します。

phpunit.xml
                       (略)
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="DB_CONNECTION" value="testing_sqlite"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <server name="MAIL_DRIVER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>   
    </php>
</phpunit>

自分は5.8ですが、Laravelのバージョンによってはタグが<server />ではなく<env />となっている場合もあるようです。

設定キャッシュをクリア

忘れないようにしましょう。

$ php artisan config:clear

これでテスト用のDBがSQLiteに設定されたと思います。

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

PHPStan level=max で "Function myFunction() has parameter $hoge with no value type specified in iterable type array. " が発生する場合の対処法

? Consider adding something like array<Foo> to the PHPDoc.

PHPStan を --level=max で実行すると no value type specified in iterable type array. と叱られ、「PHPDoc に array<Foo> っぽいものを書け」と言われるも、何をいっているのかわからない。

さらに「Consider adding something like "array<Foo>" to the PHPDoc.」とググってみてもピンと来ない。

TL; DR

「引数」もしくは「返り値」の型宣言array で配列を指定しているが「配列を構成する各要素の型が不明確なので PHPDoc で明示してね」という意味。

keyint 型、値が string 型の要素を持つ配列の場合は、array<int,string> と記載します。この型<キーの型, 値の型>の記法を「ジェネリクス記法」と呼びます。

引数の型宣言のarrayをPHPDocに記載する際に、その構成内容をジェネリクス記法で明示する場合の変更例
/**
 * myDemoFunction
 *
- * @param  array $data
+ * @param  array<int,string> $data
 * @return string
 */
function myDemoFunction(array $data): string
{
   ...
}

TS;DR

no value type specified in iterable type array. の説明

iterable type とは「foreach でループ可能な型」を言います。ここでは array なので「配列」を指します。

no value type specified とあるので「(配列の)値の型が指定されていません」という意味になり、「iterablearray の、valuetype が指定されていません。」という意味です。

日本語でいうと「foreach ループをした際の、各々の値(要素)の型が明記されていない状態である」と PHPStan が型々言っているのです。

関数/メソッドの引数と返り値、そしてオブジェクトのプロパティは比較的に型がつけやすいところですが、現状で無法地帯な箇所があります。

そうです、配列の内部構造です。実際のところ、PHPDocに @param array@return array と書くことは mixed と書くのとあまり大きな違いはありません。

(「array shapes記法(Object-like arrays)と旧PSR-5記法で型をつける」 @ Qiita より)

つまり、下記のように PHPDoc に引数の型を記載したとしても、問題は同じということです。

$items[] = new Foo(1);
$items[] = new Foo(2);
$items[] = new Foo(3);

/**
 * @param  array $items
 * @return string
 */
function myFunc(array $items): string
    foreach($items as $item){
       // ここで $items が配列というのはわかっていても、
       // $item の型が「配列」なのか「クラスオブジェクト」なのか
       // 全体を見ないとわからない。
    }
    return $result;
}

では、どうやって子要素を指定してあげればいいのでしょうか。

phpDocumentor や PhpStorm を含めた複数の処理系がサポートする PHPDoc のデファクトな仕様では Book[] のように記述することで [new Book("a"), new Book("b"), new Book("c")] のような Book クラスのオブジェクトだけが並んだ配列を表現することができます。
...

/**
 * @return Books[]
 */
function getBook(): array
{
    // ...
}

(「array shapes記法(Object-like arrays)と旧PSR-5記法で型をつける」 @ Qiita より)

では、以下のように @param Foo[] $items と指定すれば良い気がします。

$items[] = new Foo(1);
$items[] = new Foo(2);
$items[] = new Foo(3);

/**
 * @param  Foo[] $items
 * @return string
 */
function myFunc(array $items): string
    foreach($items as $item){
       // ここで $items が配列というのはわかっていても、
       // $item の型が「配列」なのか「クラスオブジェクト」なのか
       // 全体を見ないとわからない。
    }
    return $result;
}

しかし、上記のように記載しても PHPStan のワーニングは回避できません。

この記法には弱点があります。PHPには複数のイテレータがありますが、[]は飽くまで配列を表すので、ジェネレータや ArratIterator、あるいはその他のコレクションクラスなどは表現できないのです。

(「array shapes記法(Object-like arrays)と旧PSR-5記法で型をつける」 @ Qiita より)

? Consider adding something like array<Foo> to the PHPDoc. の説明

先のワーニングの後にサジェスチョンされている ? Consider adding something like array<Foo> to the PHPDoc. は、「PHPDoc に array<Foo> っぽい何かを追記することを検討してください」という意味です。

では「array<Foo>っぽい何か」(something like array<Foo>)とは「A型 < B型 >」の形式で「親要素の型<子要素の型>」と「何か指定してください」という意味です。つまり、array<Foo> の場合は「配列で、各要素は Foo クラスである」ということを意味します。

従来記法では値の型のみを指定できましたが、ジェネリクス記法はキーと値の型を同時に指定できます。

   /**
    * @return ArraryObject<int, Book>
    */
   function getBook(): array
   {
       // ...
   }

(「array shapes記法(Object-like arrays)と旧PSR-5記法で型をつける」 @ Qiita より)

このジェネリクス記法でやっと --level=max で PHPStan を実行してもワーニングが表示されなくなりました。 ?

具体例

PHPStanを--level=maxで実行する例
$ ./vendor/bin/phpstan analyse src --level=max
 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ------------------------------------------------------------------------- 
  Line   functions.php                                                            
 ------ ------------------------------------------------------------------------- 
  12     Function KEINOS\MyApp\myDemoFunction() has parameter $data with no       
         value type specified in iterable type array.                             
         ? Consider adding something like array<Foo> to the                       
         PHPDoc.                                                                  
         You can turn off this check by setting                                   
         checkMissingIterableValueType: false in your                             
         phpstan.neon.                                                            
  12     Function KEINOS\MyApp\myDemoFunction() return type has no value type     
         specified in iterable type array.                                        
         ? Consider adding something like array<Foo> to the                       
         PHPDoc.                                                                  
         You can turn off this check by setting                                   
         checkMissingIterableValueType: false in your                             
         phpstan.neon.                                                            
 ------ ------------------------------------------------------------------------- 


 [ERROR] Found 2 errors 
問題のソース
<?php
declare(strict_types=1);

namespace KEINOS\MyApp;

/**
 * myDemoFunction
 *
 * @param  array $data
 * @return array
 */
function myDemoFunction(array $data): array
{
    $result = [];

    foreach($data as $item){
        $result[] = $item;
    }

    return $result;
}
修正後のソース
<?php
declare(strict_types=1);

namespace KEINOS\MyApp;

/**
 * myDemoFunction
 *
 * @param  array<int,string> $data
 * @return array<int,string>
 */
function myDemoFunction(array $data): array
{
    $result = [];

    foreach($data as $item){
        $result[] = $item;
    }

    return $result;
}
環境情報
$ ./vendor/bin/phpstan --version              
PHPStan - PHP Static Analysis Tool 0.12.17

$ php --version
PHP 7.4.4 (cli) (built: Mar 19 2020 22:02:49) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Xdebug v2.9.3, Copyright (c) 2002-2020, by Derick Rethans

参考文献

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

PHPStan level=max で"Function myFunction() has parameter $hoge with no value type specified in iterable type array. "

? Consider adding something like array<Foo> to the PHPDoc.

PHPStan を --level=max で実行すると no value type specified in iterable type array. と叱られ、「PHPDoc に array<Foo> っぽいものを書け」と言われるも、何をいっているのかわからない。

さらに「Consider adding something like "array<Foo>" to the PHPDoc.」とググってみてもピンと来ない。

TL; DR

「引数」もしくは「返り値」の型宣言array で配列を指定しているが「配列を構成する各要素の型が不明確なので PHPDoc で明示してね」という意味。

keyint 型、値が string 型の要素を持つ配列の場合は、array<int,string> と記載します。この型<キーの型, 値の型>の記法を「ジェネリクス記法」と呼びます。

引数の型宣言のarrayをPHPDocに記載する際に、その構成内容をジェネリクス記法で明示する場合の変更例
/**
 * myDemoFunction
 *
- * @param  array $data
+ * @param  array<int,string> $data
 * @return string
 */
function myDemoFunction(array $data): string
{
   ...
}

TS;DR

(以下「array shapes記法(Object-like arrays)と旧PSR-5記法で型をつける」 @ Qiita より引用)

関数/メソッドの引数と返り値、そしてオブジェクトのプロパティは比較的に型がつけやすいところですが、現状で無法地帯な箇所があります。
そうです、配列の内部構造です。実際のところ、PHPDocに @param array@return array と書くことは mixed と書くのとあまり大きな違いはありません。
...

   /**
    * @return Books[]
    */
   function getBook(): array
   {
       // ...
   }

この記法には弱点があります。PHPには複数のイテレータがありますが、[]は飽くまで配列を表すので、ジェネレータや ArratIterator、あるいはその他のコレクションクラスなどは表現できないのです。
...
従来記法では値の型のみを指定できましたが、ジェネリクス記法はキーと値の型を同時に指定できます。

   /**
    * @return ArraryObject<int, Book>
    */
   function getBook(): array
   {
       // ...
   }

具体例

環境情報
$ ./vendor/bin/phpstan --version              
PHPStan - PHP Static Analysis Tool 0.12.17

$ php --version
PHP 7.4.4 (cli) (built: Mar 19 2020 22:02:49) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Xdebug v2.9.3, Copyright (c) 2002-2020, by Derick Rethans
PHPStanを--level=maxで実行する例
$ ./vendor/bin/phpstan analyse src --level=max
 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ------------------------------------------------------------------------- 
  Line   functions.php                                                            
 ------ ------------------------------------------------------------------------- 
  12     Function KEINOS\MyApp\myDemoFunction() has parameter $data with no       
         value type specified in iterable type array.                             
         ? Consider adding something like array<Foo> to the                       
         PHPDoc.                                                                  
         You can turn off this check by setting                                   
         checkMissingIterableValueType: false in your                             
         phpstan.neon.                                                            
  12     Function KEINOS\MyApp\myDemoFunction() return type has no value type     
         specified in iterable type array.                                        
         ? Consider adding something like array<Foo> to the                       
         PHPDoc.                                                                  
         You can turn off this check by setting                                   
         checkMissingIterableValueType: false in your                             
         phpstan.neon.                                                            
 ------ ------------------------------------------------------------------------- 


 [ERROR] Found 2 errors 
問題のソース
<?php
declare(strict_types=1);

namespace KEINOS\MyApp;

/**
 * myDemoFunction
 *
 * @param  array $data
 * @return array
 */
function myDemoFunction(array $data): array
{
    $result = [];

    foreach($data as $item){
        $result[] = $item;
    }

    return $result;
}
修正後のソース
<?php
declare(strict_types=1);

namespace KEINOS\MyApp;

/**
 * myDemoFunction
 *
 * @param  array<int,string> $data
 * @return array<int,string>
 */
function myDemoFunction(array $data): array
{
    $result = [];

    foreach($data as $item){
        $result[] = $item;
    }

    return $result;
}

参考文献

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

Laravel Auditingでデータベースの変更を監視する

『誰がいつ、データをいじったか知りたい』というデータの監視が必要な時に、Laravel Auditingが便利です。

概要

Laravel Auditingはデータベース上の指定した監視対象のテーブルに追加・更新・削除などの変更が行われた場合に、誰がどのような変更を行ったかを自動でログ保存するパッケージです。

outline.001.jpeg

インストール & 設定

インストール

composerで行います。

composer require owen-it/laravel-auditing

設定

インストール後、config/app.phpにプロバイダーを追加します。

config/app.php
'providers' => [
+    OwenIt\Auditing\AuditingServiceProvider::class,
],

追加したら、Laravel Auditing用の設定ファイルを作成するために以下のコマンドを実行します。

php artisan vendor:publish --provider "OwenIt\Auditing\AuditingServiceProvider" --tag="config"

実行すると、config/audit.phpというファイルが作成されます。特にカスタマイズする必要がなければそのまま、データベースのマイグレーションファイルを以下のコマンドで作成します。

php artisan vendor:publish --provider "OwenIt\Auditing\AuditingServiceProvider" --tag="migrations"

実行すると、auditsテーブルを作成するためのdatabase/migrations/yyyy_mm_dd_nnnnnn_create_audits_table.phpというファイルが作成されます。作成後にマイグレーションしてauditsテーブルを作成します。

php artisan migrate

監視

基本的な設定

設定が完了したら、監視したいデータのモデルに追記します。例えばitemsテーブルのデータを監視したい場合は、

app/Item.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
+ use OwenIt\Auditing\Contracts\Auditable;
+
+ class Item extends Model implements Auditable
- class Item extends Model
{

+    use \OwenIt\Auditing\Auditable;

ちょっとややこしいのですが、インターフェイスはOwenIt\Auditing\Contracts\Auditable、traitは\OwenIt\Auditing\Auditableを指定します。これらを追記するだけで、監視対象となります。

実際に監視

では、このテーブルに対して新規登録・変更・削除を行ってみましょう。これらを実行すると、auditsテーブルに以下のようなデータが追加されます。

id user_type user_id event auditable_type auditable_id old_values new_values url ip_address user_agent tags created_at updated_at
1 NULL NULL updated App\Item 2 {"name":"\u8ffd\u52a01"} {"name":"\u8ffd\u52a02"} http://localhost:8000/items/2 127.0.0.1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15 NULL 2020-03-23 12:20:34 2020-03-23 12:20:34
2 App\User 1 created App\Item 3 [] {"name":"\u307b\u3052","stock":"10","id":3} http://localhost:8000/items 127.0.0.1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15 NULL 2020-03-23 12:21:39 2020-03-23 12:21:39
3 App\User 1 deleted App\Item 3 {"id":"3","name":"\u307b\u3052","stock":"10"} [] http://localhost:8000/items/3 127.0.0.1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15 NULL 2020-03-23 12:51:57 2020-03-23 12:51:57

監視対象のデータは以下のような内容が蓄積されます。

フィールド名 蓄積内容
id 監視データのID
user_type ユーザモデルのクラス名
user_id ユーザID
event 実行したイベント
auditable_type 更新したデータモデルのクラス名
auditable_id 更新したデータのID
old_values 更新前のデータ
new_values 更新後のデータ
url 実行されたURL
ip_address IPアドレス
user_agent ユーザエージェント
tags タグ
created_at 作成日時(= データが更新された日時)
updated_at 更新日時

あまりないと思いますが、ログインせずにデータを更新した場合は、user_type, user_idがNULLになります。

監視データの取得

監視データは監視対象のモデルに関連しています。

<?php

$item = Item::find(1);

// 該当データの全ての監視データ
$audits = $item->audits;

// 該当データの最初の監視データ
$first = $item->audits()->first();

// 該当データの直近の監視データ
$latest = $item->audits()->latest()->first();

// 該当データの監視データのID指定
$audit = $item->audits()->find(1);

監視データもEloquentモデルなので、直接取得できます。

<?php

use OwenIt\Auditing\Models\Audit;

// 全件取得
$audits = Audit::all();

// ID指定
$audit = Audit::find(1);

カスタマイズ

コンソールコマンドでの実行も監視したい

デフォルトでは、コンソールコマンドやジョブの更新は監視対象外となっています。監視したい場合は、config/audit.phpで設定を変更します。

config/audit.php
-    'console' => false,
+    'console' => true,

監視するフィールドを絞り込みたい

デフォルトでは、登録・更新日時以外を対象とします。監視するフィールドを絞り込みたい場合は監視対象モデルに設定を追記します。

app/Item.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;

class Item extends Model implements Auditable
{
    use \OwenIt\Auditing\Auditable;


+    protected $auditInclude = [
+        'name',
+    ];

逆に除外したい場合は、$auditExcludeに指定することで、除外できます。

蓄積するデータを変えたい

蓄積するデータをカスタマイズしたい場合は、独自のResolverを作成します。Laravel Auditingでは以下のResolver用インターフェイスが用意されています。

インターフェイス 概要
OwenIt\Auditing\Contracts\IpAddressResolver IPアドレス用I/F
OwenIt\Auditing\Contracts\UrlResolver URL用I/F
OwenIt\Auditing\Contracts\UserAgentResolver ユーザエージェント用I/F
OwenIt\Auditing\Contracts\UserResolver ユーザモデル用I/F

これらを実装することで、蓄積するデータを変えることができます。

app/resolvers/IpAddressResolver.php
<?php
namespace App\Resolvers;

use Illuminate\Support\Facades\Request;

class IpAddressResolver implements \OwenIt\Auditing\Contracts\IpAddressResolver
{
    /**
     * {@inheritdoc}
     */
    public static function resolve(): string
    {
        return Request::header('HTTP_X_FORWARDED_FOR', '0.0.0.0');
    }
}

独自Resolverはconfig/audit.phpで設定することで反映されます。

config/audit.php
return [
    'resolver' = [
        'user'       => OwenIt\Auditing\Resolvers\UserResolver::class,
-        'ip_address' => OwenIt\Auditing\Resolvers\IpAddressResolver::class,
+        'ip_address' => App\Resolvers\IpAddressResolver::class,
        'user_agent' => OwenIt\Auditing\Resolvers\UserAgentResolver::class,
        'url'        => OwenIt\Auditing\Resolvers\UrlResolver::class,
    ],
];

マスクしたい

監視データは変更前・後の値をそのまま保存しますが、場合によってはマスクして保存したい時もあります。その場合は、監視対象のモデルに設定することで、マスクできます。

app/Item.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;
+use OwenIt\Auditing\Redactors\LeftRedactor;

class Item extends Model implements Auditable
{
    use \OwenIt\Auditing\Auditable;


+    protected $attributeModifiers = [
+        'title' => LeftRedactor::class,
+    ];

LeftRedactorは左から90%マスクし残り10%はそのまま、RightRedactorは右から90%マスクします。マスク部分は#####で保存されます。

特定のイベントのみ監視したい

デフォルトは追加・更新・削除を監視しますが、設定によりイベントを絞り込むことができます。例えば削除のみ監視したい場合は以下のように設定します。

全体

config/audit.php
    'events' => [
-        'created',
-        'updated',
        'deleted',
-        'restored',
    ],

特定の監視対象のみ

app/Item.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;

class Item extends Model implements Auditable
{
    use \OwenIt\Auditing\Auditable;


+    protected $auditEvents = [
+        'deleted',
+    ];

監視ログの上限を設ける

デフォルトでは監視ログは延々と蓄積され続けます。上限を設けたい場合は以下のように設定するこで、上限を超えた古いデータが削除されていきます。

全体

config/audit.php
-    'threshold' => 0, // 0だと上限なし
+    'threshold' => 10,

特定の監視対象のみ

app/Item.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;

class Item extends Model implements Auditable
{
    use \OwenIt\Auditing\Auditable;


+    protected $auditThreshold = 10;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】出したエラーの数々

エラーが出た原因と解決法の備忘録

シンタックスエラー

syntax error, unexpected ';', expecting ']'
']'付近に想定外の';'があると怒られました。
連想配列の最後にセミコロン付けてるのが原因です。

$result = [
          'result' => 'NG',
          'message' => $e->getMessage();
        ];

// getMessage();の';'を消して解決↓

$result = [
          'result' => 'NG',
          'message' => $e->getMessage()
        ];
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Phoenixお気軽API開発①:PHP的ハックを応用してNode.js Express/Go的な軽量APIをPhoenixで実現してみた

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

前々作は「PHP的気軽さでWebアプリを作る方法」前作はこれを応用して「リアルタイムフロントであるLiveViewにも気軽さを導入」してみましたが、今作では、Phoenix API開発に、Node.js Express/Go的な軽量APIのテイストを付与してみます

この対応により、APIも、WebアプリやLiveView同様、ルーティング/MVCが不要になります

また、従来通りのmix phx.gen.jsonによるScaffold(≒コード自動生成)で構築されるAPIだと、JSONフォーマットがカスタマイズしにくかったり、カスタムフィールドを乗せるのにもノウハウが必要ですが、ここでは、シンプルなJSONテンプレートによるAPI構築を実現するので、普段のPhoenix使いでは体験できない気楽さを感じていただけたら幸いです

本コラムの検証環境

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

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

事前準備:PHP的ハック導入済みPhoenix PJの作成

下記コラムの手順で、PHP的ハック導入済みのPhoenix PJを作成します

ElixirでもPHP的に気軽なWebアプリ開発をしたい(Phoenixより薄いWebFW実現?)
https://qiita.com/piacerex/items/25c82153976dcb6d57c1

ここまでが終わったら、ブラウザで「http://localhost:4000/abc/def?abc=xyz123」にアクセスすると、以下のようなページが表示されることを確認して、準備完了です
image.png

手順①:REST APIをルーティング不要に

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

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

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

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

  scope "/", BasicWeb do
    pipe_through :browser

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

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

ここは上記のコラム内で実施済みなので、特にやることはありません

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

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

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

また、mix phx.newで生成されたコントローラが「_params」という感じでアンダースコアで未使用にしているので、これを解除し、renderの第3引数に渡すことで、GETパラメータを素通しさせます

lib/basic_web/controllers/api_controller.ex
defmodule BasicWeb.ApiController do
  use BasicWeb, :controller

  def index( conn, params ) do
    template = if params[ "path_" ] == nil, do: "index.json", else: to_path_string( params[ "path_" ] ) <> ".json"
    render( conn, template, params: params )
  end

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

「path_」パラメータ未指定時のためのindex.json.eexを追加します

なお、JSONテンプレートは、templates配下に「api」というフォルダを掘って、配置することにします

lib/basic_web/templates/json/index.json.eex
%{
}

手順④:json.eexをビュー向けにテンプレート展開

API専用のビューを追加します

PhoenixにおけるJSONレンダリングは、Phonenixのビュー配下で、マップもしくはマップリストをJSONに変換することで実現されるため、コントローラでのrender()、つまりeexテンプレート展開(<%= ~ %>)のように文字列展開されると、都合が悪いです

そこで、ビューのrender()にて、Code.eval_string()を使って、自前でテンプレート展開を行います

Elixirのビルトインモジュールや自作モジュールは、Code.eval_string()で展開可能ですが、入力パラメータは自前でバインディングをかけなければならないため、Code.eval_string()の第2引数にてバインディングを行います

ちなみに、Code.eva_file()というもっと便利そうなものもあるのですが、Code.eval_string()のようなバインディングができないため、今回の目的には沿いませんでした

Phoenixは、カスタムテンプレートエンジンを定義できるので、JSON専用テンプレートを構築することもできるのですが、今回は、バインディングもできる強力なCode.eval_string()の恩恵に預かることにしました

lib/basic_web/views/api_view.ex
defmodule BasicWeb.ApiView do
  use BasicWeb, :view

  def render( path, %{ view_template: view_template, params: params } ) do
    File.read!( "lib/basic_web/templates/api/#{ view_template }.eex" ) 
    |> Code.eval_string( [ params: params ] ) 
    |> elem( 0 )
  end
end

なお、Webページテンプレート同様、「@ param」で入力パラメータ指定可能にしたかったのですが、Code.eval系はモジュールアトリビュートが通常変数と扱いが異なるためNGで、EEx.eval系は@が利用できますが文字列返却になってしまうため、断念しました

手順⑤:json.eexを追加する

1)templates/api直下にjson.eex追加

これで、json.eexを追加するだけで、新たなAPIが追加可能となったので、追加してみます

JSONテンプレートは、Elixirのマップとリストで構成し、中身は通常のマップやリストと同様、Elixirコードを記述できます

まずはシンプルに、マップ1つだけとし、入力パラメータ/リテラル/Elixirビルトインモジュール関数を値に設定します

lib/basic_web/templates/api/sample.json.eex
%{
  id:      params[ "id" ], 
  name:    "Elixir", 
  version: System.version()
}

RESTクライアントで「http://localhost:4000/sample?id=123」にアクセスすると、以下のようなページが表示されます

入力パラメータや、Elixirビルトインモジュール関数が実行され、その結果がJSONとして返却されます
image.png

idを変えると、返却されるJSONも変わります
image.png

2)templates/api配下のサブフォルダ下にjson.eex追加

apiフォルダ配下にフォルダを掘って、その配下にjson.eexを配置しても動きます

「abc」というフォルダを掘り、「def.json.eex」というJSONテンプレートを追加します

今度は、リストマップで構成し、各マップの値をランダム値で変動させてみます

lib/basic_web/templates/api/abc/def.json.eex
[
  %{
    greeting:  "I'm def-1", 
    type:      "plane API", 
    random_no: :rand.uniform
  }, 
  %{
    greeting: "I'm def-2", 
    type:     "plane API", 
    random_no: :rand.uniform
  }, 
  %{
    greeting: "I'm def-3", 
    type:     "plane API", 
    random_no: :rand.uniform
  }
]

RESTクライアントで「http://localhost:4000/abc/def」にアクセスすると、以下のようなページが表示されます

ランダム値が展開されたJSONオブジェクトを梱包するJSON配列が返却されました
image.png

終わり

JSONテンプレート追加だけで実現できるお気軽API開発を実現してみました

前々作のPHP的ハックをAPIに応用すると、Scaffoldを用いることなく、ルーティング/MVCにも煩わされない、軽量なAPI開発が可能となることがお分かりいただけたでしょうか?

Phoniexは、ファットなWebフレームワークだと誤解されがちですが、ここまでの3部作で見てきたように、ちょっとした工夫を施すことで、軽量フレームワークに化ける「プログレッシブWebフレームワーク」としての一面があり、そこが上手く共有できていたら嬉しいです

さて、次回以降では、より本格的なAPI利用に向けて、REST API対応(POST/PUT/DELETEやURLとしてのid指定)や、軽量APIからのDB利用、APIバージョン管理とWebページ共存なども行っていきたいと思います

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

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

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

本日の学習内容Part3

追加ボタンがクリックされたかどうか判断する

input.php
<form action="" method="post">
   <input type="submit" name="add" vaule="追加">
</form>
<?php 
if (isset($_POST['add']) {
   print('追加されました');
}
?>

?>

今さらながら学習記録について、その日に特に使い方を覚えておいた方がいいものを一つだけ取り上げます。

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

Wordpress プラグインの作成と管理画面の作り方

プラグインの作成

プラグインを作るにはプラグインフォルダを作成し、プラグインの設定項目を記入する。

プラグインフォルダは、wp-content/pluginsに作成する。

プラグインフォルダの最初のファイルはプラグイン名と同じようにする。ここではtest-plugin.phpにした。

このファイルに最低限記入が必要なのは次のような内容。

<?php
// wp-content/plugins/test-plugin/test-plugin.php
/*
Plugin Name: プラグイン名(他と被らないようにする)
Description: プラグインの説明文
Author: 作者名
Version: 0.1
*/

これだけだと何の変化もないので、Hello Worldと表示してみる。

<?php
/*
Plugin Name: Test plugin
Description: A test plugin to demonstrate wordpress functionality
Author: Test Name
Version: 0.1
*/

function test_function() {
  echo '<h1>Hello World</h1>';
}
add_action( 'wp_footer', 'test_function' );

プラグイン一覧画面に作成したプラグインが表示されてるので有効化する。

wp_footerにアクションを追加したので、トップページを開くと画面下にHello Worldが表示されるはず。

プラグインにメニューと設定画面を追加する

次に管理画面を作成してみる。

管理画面は、add_menu_page()に簡単な設定項目を記入するだけ。

<?php
/*
Plugin Name: Test plugin
Description: A test plugin to demonstrate wordpress functionality
Author: Test Name
Version: 0.1
*/

function test_function() {
  echo '<h1>Hello World</h1>';
}

function test_plugin_setup_menu(){
  add_menu_page(
    __( 'TestPlugin', 'textdomain' ), // ページタイトル
    'Test Plugin',  // メニュータイトル
    'manage_options', // 
    'test-plugin',  // メニューslug
    'test_function',  // 実行する関数
    'dashicons-chart-pie',  // メニューに表示するアイコン
    6 // メニューの表示位置
  );
}
add_action('admin_menu', 'test_plugin_setup_menu');

plugin.png

メニューのリンクをクリックすると、add_menu_page()で指定した関数が実行されている。

あとはHTMLとCSSでページを作っていくだけ。

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

Mac Laravelでライブラリ「laravel-dompdf」をインストールする

目的

  • Laravelのライブラリインストールが初めてだったので実施方法をまとめる
  • ライブラリ「laravel-dompdf」のインストール方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
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
  • ライブラリ環境
項目 情報 備考
laravel-dompdf v0.8.6 アプリ名ディレクトリに移動後コマンド`composer show -i

実施方法概要

  1. インストール
  2. 設定
  3. 確認

実施方法詳細

  • ローカル開発環境を前提に説明を記載する。
  1. インストール

    1. 下記コマンドを実行してライブラリ「laravel-dompdf」をインストールする。

      $ composer require barryvdh/laravel-dompdf
      
  2. 設定

    1. 下記コマンドを実行して設定を記載するファイルを開く。

      $ vi アプリ名ディレクトリ/app.php
      
    2. 先のコマンドを実行後、viのコマンドモードにて下記のコマンドを入力して設定を追記する行を検索する。

      /'providers' => [
      
    3. 検索にヒットした部分が下記の様な記載になっていることを確認する。

      app.php
      'providers' => [
      
          /*
           * Laravel Framework Service Providers...
           */
          Illuminate\Auth\AuthServiceProvider::class,
          Illuminate\Broadcasting\BroadcastServiceProvider::class,
          Illuminate\Bus\BusServiceProvider::class,
          Illuminate\Cache\CacheServiceProvider::class,
          Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
          Illuminate\Cookie\CookieServiceProvider::class,
          Illuminate\Database\DatabaseServiceProvider::class,
          Illuminate\Encryption\EncryptionServiceProvider::class,
          Illuminate\Filesystem\FilesystemServiceProvider::class,
          Illuminate\Foundation\Providers\FoundationServiceProvider::class,
          Illuminate\Hashing\HashServiceProvider::class,
          Illuminate\Mail\MailServiceProvider::class,
          Illuminate\Notifications\NotificationServiceProvider::class,
          Illuminate\Pagination\PaginationServiceProvider::class,
          Illuminate\Pipeline\PipelineServiceProvider::class,
          Illuminate\Queue\QueueServiceProvider::class,
          Illuminate\Redis\RedisServiceProvider::class,
          Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
          Illuminate\Session\SessionServiceProvider::class,
          Illuminate\Translation\TranslationServiceProvider::class,
          Illuminate\Validation\ValidationServiceProvider::class,
          Illuminate\View\ViewServiceProvider::class,
      
          /*
           * Package Service Providers...
           */
      
          /*
           * Application Service Providers...
           */
          App\Providers\AppServiceProvider::class,
          App\Providers\AuthServiceProvider::class,
          // App\Providers\BroadcastServiceProvider::class,
          App\Providers\EventServiceProvider::class,
          App\Providers\RouteServiceProvider::class,
      
      ],
      
    4. 先に「app.php」ファイルを検索したファイルのApp\Providers\RouteServiceProvider::class,の後に下記の内容を追記する。

      Barryvdh\DomPDF\ServiceProvider::class,
      
    5. 先の内容を追記後、下記の様に「app.php」ファイルが記載されていることを確認する。

      app.php
      'providers' => [
      
          /*
           * Laravel Framework Service Providers...
           */
          Illuminate\Auth\AuthServiceProvider::class,
          Illuminate\Broadcasting\BroadcastServiceProvider::class,
          Illuminate\Bus\BusServiceProvider::class,
          Illuminate\Cache\CacheServiceProvider::class,
          Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
          Illuminate\Cookie\CookieServiceProvider::class,
          Illuminate\Database\DatabaseServiceProvider::class,
          Illuminate\Encryption\EncryptionServiceProvider::class,
          Illuminate\Filesystem\FilesystemServiceProvider::class,
          Illuminate\Foundation\Providers\FoundationServiceProvider::class,
          Illuminate\Hashing\HashServiceProvider::class,
          Illuminate\Mail\MailServiceProvider::class,
          Illuminate\Notifications\NotificationServiceProvider::class,
          Illuminate\Pagination\PaginationServiceProvider::class,
          Illuminate\Pipeline\PipelineServiceProvider::class,
          Illuminate\Queue\QueueServiceProvider::class,
          Illuminate\Redis\RedisServiceProvider::class,
          Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
          Illuminate\Session\SessionServiceProvider::class,
          Illuminate\Translation\TranslationServiceProvider::class,
          Illuminate\Validation\ValidationServiceProvider::class,
          Illuminate\View\ViewServiceProvider::class,
      
          /*
           * Package Service Providers...
           */
      
          /*
           * Application Service Providers...
           */
          App\Providers\AppServiceProvider::class,
          App\Providers\AuthServiceProvider::class,
          // App\Providers\BroadcastServiceProvider::class,
          App\Providers\EventServiceProvider::class,
          App\Providers\RouteServiceProvider::class,
          Barryvdh\DomPDF\ServiceProvider::class,
      
      ],
      
    6. 先のコマンドを実行後、viのコマンドモードにて下記のコマンドを入力して設定を追記する行を検索する。

      /'aliases' => [
      
    7. 検索にヒットした部分が下記の様な記載になっていることを確認する。

      app.php
      'aliases' => [
      
          'App' => Illuminate\Support\Facades\App::class,
          'Arr' => Illuminate\Support\Arr::class,
          'Artisan' => Illuminate\Support\Facades\Artisan::class,
          'Auth' => Illuminate\Support\Facades\Auth::class,
          'Blade' => Illuminate\Support\Facades\Blade::class,
          'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
          'Bus' => Illuminate\Support\Facades\Bus::class,
          'Cache' => Illuminate\Support\Facades\Cache::class,
          'Config' => Illuminate\Support\Facades\Config::class,
          'Cookie' => Illuminate\Support\Facades\Cookie::class,
          'Crypt' => Illuminate\Support\Facades\Crypt::class,
          'DB' => Illuminate\Support\Facades\DB::class,
          'Eloquent' => Illuminate\Database\Eloquent\Model::class,
          'Event' => Illuminate\Support\Facades\Event::class,
          'File' => Illuminate\Support\Facades\File::class,
          'Gate' => Illuminate\Support\Facades\Gate::class,
          'Hash' => Illuminate\Support\Facades\Hash::class,
          'Http' => Illuminate\Support\Facades\Http::class,
          'Lang' => Illuminate\Support\Facades\Lang::class,
          'Log' => Illuminate\Support\Facades\Log::class,
          'Mail' => Illuminate\Support\Facades\Mail::class,
          'Notification' => Illuminate\Support\Facades\Notification::class,
          'Password' => Illuminate\Support\Facades\Password::class,
          'Queue' => Illuminate\Support\Facades\Queue::class,
          'Redirect' => Illuminate\Support\Facades\Redirect::class,
          'Redis' => Illuminate\Support\Facades\Redis::class,
          'Request' => Illuminate\Support\Facades\Request::class,
          'Response' => Illuminate\Support\Facades\Response::class,
          'Route' => Illuminate\Support\Facades\Route::class,
          'Schema' => Illuminate\Support\Facades\Schema::class,
          'Session' => Illuminate\Support\Facades\Session::class,
          'Storage' => Illuminate\Support\Facades\Storage::class,
          'Str' => Illuminate\Support\Str::class,
          'URL' => Illuminate\Support\Facades\URL::class,
          'Validator' => Illuminate\Support\Facades\Validator::class,
          'View' => Illuminate\Support\Facades\View::class,
      
      ],
      
    8. 先に「app.php」ファイルを検索したファイルの'View' => Illuminate\Support\Facades\View::class,の後に下記の内容を追記する。

      'PDF' => Barryvdh\DomPDF\Facade::class,
      
    9. 先の内容を追記後、下記の様に「app.php」ファイルが記載されていることを確認する。

      app.php
      'aliases' => [
      
          'App' => Illuminate\Support\Facades\App::class,
          'Arr' => Illuminate\Support\Arr::class,
          'Artisan' => Illuminate\Support\Facades\Artisan::class,
          'Auth' => Illuminate\Support\Facades\Auth::class,
          'Blade' => Illuminate\Support\Facades\Blade::class,
          'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
          'Bus' => Illuminate\Support\Facades\Bus::class,
          'Cache' => Illuminate\Support\Facades\Cache::class,
          'Config' => Illuminate\Support\Facades\Config::class,
          'Cookie' => Illuminate\Support\Facades\Cookie::class,
          'Crypt' => Illuminate\Support\Facades\Crypt::class,
          'DB' => Illuminate\Support\Facades\DB::class,
          'Eloquent' => Illuminate\Database\Eloquent\Model::class,
          'Event' => Illuminate\Support\Facades\Event::class,
          'File' => Illuminate\Support\Facades\File::class,
          'Gate' => Illuminate\Support\Facades\Gate::class,
          'Hash' => Illuminate\Support\Facades\Hash::class,
          'Http' => Illuminate\Support\Facades\Http::class,
          'Lang' => Illuminate\Support\Facades\Lang::class,
          'Log' => Illuminate\Support\Facades\Log::class,
          'Mail' => Illuminate\Support\Facades\Mail::class,
          'Notification' => Illuminate\Support\Facades\Notification::class,
          'Password' => Illuminate\Support\Facades\Password::class,
          'Queue' => Illuminate\Support\Facades\Queue::class,
          'Redirect' => Illuminate\Support\Facades\Redirect::class,
          'Redis' => Illuminate\Support\Facades\Redis::class,
          'Request' => Illuminate\Support\Facades\Request::class,
          'Response' => Illuminate\Support\Facades\Response::class,
          'Route' => Illuminate\Support\Facades\Route::class,
          'Schema' => Illuminate\Support\Facades\Schema::class,
          'Session' => Illuminate\Support\Facades\Session::class,
          'Storage' => Illuminate\Support\Facades\Storage::class,
          'Str' => Illuminate\Support\Str::class,
          'URL' => Illuminate\Support\Facades\URL::class,
          'Validator' => Illuminate\Support\Facades\Validator::class,
          'View' => Illuminate\Support\Facades\View::class,
          'PDF' => Barryvdh\DomPDF\Facade::class,
      
      ],
      
  3. 確認

    1. 下記コマンドをアプリ名ルートディレクトリで実行してライブラリ導入確認のためのコントローラを作成する。

      $ php artisan make:controller PdfController
      
    2. 先のコマンドで作成されたコントローラを下記の様に修正する。

      アプリ名ルートディレクトリ/app/Http/Controllers/pdf.Controller.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      
      use PDF;
      
      class PdfController extends Controller
      {
          public function test(){
          $pdf = PDF::loadHTML('<h1>test</h1>');
          return $pdf->stream();
          }
      }
      
    3. ルーティングファイルに下記の記載を追記する。

      アプリ名ルートディレクトリ/routes/web.php
      Route::get('/pdf', 'PdfController@test');
      
    4. 下記のリンクにアクセスする。

    5. 下記の様にPDF形式で「test」の文字が表示されていることを確認する。

      スクリーンショット 2020-03-23 17.23.44.png

    6. 上記の様に表示されればLaravelのライブラリ「laravel-dompdf」のインストールは完了である。

参考文献

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

1分で読める GET POST 違い

それでは解説していきます

GET

ブラウザでURLを入力し検索するときにGETメソッドを使っています。
基本的には読み取り専用な機能に対して使うべきメソッドになります。
・HTTP通信で、サーバから情報を取得してくる時に使用する
・情報を検索したり取得するために使うためのもの。

POST

HTTP通信で、サーバへ情報を登録する時に使用する(データベースへの格納など)
・ツイートや記事を投稿する
・新規ユーザーを登録
・既存のデータを更新する時

なんとなくこうゆうときに使うんだなあーと思っていただけばいいかなと思います!!
細かいところもあるのでぜひ他の記事やサイトで深掘りしてください:fist:

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