20200628のPHPに関する記事は17件です。

PHP基礎<変数と定数>

前書き

PHPを基礎から学習しています。
積み重ねのアウトプットとして更新していきます。

変数とは

データを入れる「箱」のようなものです。 この「箱」の中には色々なものを出し入れすることができます。
「変数」は変わる数で、「定数」は変わらない数を示します。

index.php
<?php
    $name = '山田太郎';
    $age = 20;

    echo '私の名前は';
    echo $name;
    echo 'です。';

    echo '私の年齢は';
    echo $age;
    echo '歳です。';


ここでの $name や $age が変数です。

変数名

変数名には以下のようなルールがあります。

  • 先頭には $ が必要
  • 英数字およびアンダースコア(_)を利用可能
  • 数字は先頭の文字には使えない
  • 定数は一般的には全て大文字

また、変数名は内容が分かりやすい名前をつけます。

index.php
?php

// 良い例
$name = '山田太郎';
$age = 40;

// 良くない例
$var = '山田太郎'; // どんな変数なのかが分かりづらい
$nenrei = 40; // 基本的に英単語を元につけたほうが良い

さらに、長い変数名の場合は、「スネークケース」「キャメルケース」などを利用します。

index.php
<?php
    // スネークケース
    // スネークケースでは、アンダースコアで単語間をつなげ、基本的には全て小文字となる
    $user_name = '山田太郎';

    // キャメルケース
    // キャメルケースでは、2番目の単語の初めを大文字とする
    $userName = '山田太郎';

変数の上書き

変数は、値を変更することができます。

index.php
<?php
    $name = '山田太郎';
    echo $name;
    $name = '鈴木花子'; // こちらが上書きされる
    echo $name;

あとがき

初回は変数と定数についてまとめました。
間違い等あればご指摘ご指導お願いいたします!

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

Docker で PHP 8.0.0 Alpha 1 環境構築

概要

2020年6月25日に PHP 8.0.0 Alpha 1 がリリースされました。
Docker イメージ も公開されていたので最低限の環境を構築してみます。

構成

$ tree
.
├── docker-compose.yml
├── nginx
│   └── default.conf
└── public
    └── index.php

ファイル

docker-compose.yml
version: "3.8"

services:
  nginx:
    image: nginx
    ports:
    - "80:80"
    volumes:
    - ./public:/var/www/html
    - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
  php:
    image: php:8.0.0alpha1-fpm
    volumes:
    - ./public:/var/www/html
public/index.php
<?php
phpinfo();
nginx/default.conf
server {
    index index.php index.html;
    root /var/www/html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri = 404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

コードは SnowCait/php8-docker-compose-example にも置いてあります。
さくっと試したい方は git clone して使ってください。

起動

docker-compose up -d

ブラウザでアクセスする

http://localhost/ にアクセスすると phpinfo() の内容が表示されます。
ポートを変えたい場合は "80:80" の部分を "8080:80" にすると http://localhost:8080/ でアクセスできます。
php8.png

コマンドラインから実行する

docker-compose exec php php -v
PHP 8.0.0alpha1 (cli) (built: Jun 26 2020 19:56:42) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies

コマンドラインだけあれば良い方は nginx の設定は不要です。

停止

docker-compose down

拡張

実際にアプリを動かそうとすると今回紹介したコードだけでは足りないと思います。
データーベースや KVS が必要だったり Composer が必要だったり public/ 以外のディレクトリが必要だったり。
Docker Compose であればコンテナや設定を追加するだけなので色々と拡張しやすいです。

Composer を使いたい方は 環境を汚さずに Composer を使いたい - Qiita に書いていますのでもし興味があれば。
docker-compose.yml に 4 ~ 5 行追加するだけで使えるようになります。

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

ラズパイでcronがうまく設定できないときのTips

概要

ラズパイを買って、なにかしら定期処理させようとしたときにちょっとハマった。
そもそもcron触ったことない場合のcronの注意と
ラズパイだとコマンドちょっと違うよっていう話。

僕と同レベルの初学者向けに書いてます。

ラズパイ編

cronの起動、再起動

/etc/init.d/cron start
/etc/init.d/cron restart

↓は検索するとよく出てくるけど

service crond restart

debian(ラズパイのOS)だとそんなものないよってなるので注意。

ログ

設定

/etc/rsyslog.conf(抜粋)
###############
#### RULES ####
###############

#
# First some standard log files.  Log by facility.
#
auth,authpriv.*         /var/log/auth.log
*.*;auth,authpriv.none      -/var/log/syslog
#cron.*             /var/log/cron.log
daemon.*            -/var/log/daemon.log
kern.*              -/var/log/kern.log
lpr.*               -/var/log/lpr.log
mail.*              -/var/log/mail.log
user.*              -/var/log/user.log

以下の記述がデフォルトでは、
コメントアウトされてるので、解除。

#cron.*             /var/log/cron.log
↓
cron.*             /var/log/cron.log

確認方法

当たり前だけど、これでいけます。

cat /var/log/cron.log

cron

設定方法

以下のコマンドでエディターが開きます。
色々書いてるけど、一番下に実行したいコマンド書いたら実行されます。

crontab -e

※PHPを実行したいけどエラーが出たとき

フルパスで書くのが重要

例えば、
/var/www/html/cron.php
を実行したいとき。

このファイル内でなにかしらインクルードしてる場合は、

cron.php
include("./hogehoge.php")

↑こう書くとエラーが出る。

cron.log
(CRON) error (grandchild #***** failed with exit status 255)

なのでフルパスで書いてあげる。

cron.php
include("/var/www/html/hogehoge.php")

また、実行したいファイルでなく、PHPの場所も書いたほうがいい。
僕は書いたら動きました。

* * * * * /usr/bin/php /var/www/html/cron.php

終わりに

間違ってたら指摘ください

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

最後のおさらい!PHP8で削除される&動かなくなる機能 ~レガシーシステムでありそうなものピックアップ~

まえがき

PHP8が2020/12にリリースされる予定で、先日2020/06/25にアルファ版が公開されました。

https://twitter.com/official_php/status/1276198231714783232?s=20

PHP7.2~7.4では既に非推奨になっている機能が廃止され、エラーで止まったりするということで、今一度PHP8で動かなくなる機能を調べ、そのなかでもレガシーシステムで使われてそう・古いFWで使われてそうで万人に身近そうなものを独断でピックアップして記載したいと思います。

PHP8で削除される&動かなくなる機能

PHP7.2から非推奨→廃止

$errcontext argument of error handler

エラーハンドラのコールバック関数のシグニチャに指定できる$errcontextが廃止されます。
この変数にはエラーが起きたときのローカル変数の値をすべて保持しています。

が、現在はデバッガで確認するような内容で実運用では使用頻度があまりなさそうなものにこれからもこの変数を実現するために保守し続けるのは割に合わない...とのことで廃止されます。

この機能に関しては、deprecationのワーニングスローがされていません。 もしこの引数を利用していればエラー処理がうまく作動しなくなるかもしれませんので今一度注意したほうがよいと思います。

Proposed action: Throw deprecation notice if error handler has five or more arguments. Otherwise, do not pass the $errcontext. This prevents circumvention with func_get_args().

Update: Due to technical issues, this does not throw a deprecation warning. It is a documentation-only deprecation.

PHP7.3から非推奨→廃止

String search functions with integer needle

文字列内検索に関する関数の振る舞いが一部変わります。
検索文字列が数値だった場合、falseを返していたのを、与えられた数値を文字列に変換してくれるようになります。

...今までfalseになって分岐に入っていなかった処理が実行される..いいことであるはずですが、もしかするとなにか"トラブル"にもなるかもしれません。

~PHP7.4:

$str = "There are 10 apples";
var_dump(strpos($str, "10")); // int(10)
var_dump(strpos($str, 10));   // bool(false)

PHP8:

$str = "There are 10 apples";
var_dump(strpos($str, "10")); // int(10)
var_dump(strpos($str, 10));   // int(10)

こちらからそのままソースを引用

PHP7.4から非推奨→廃止

The 'real' type

typefloatのエイリアスとしてdoublerealが存在するのですが、ほぼrealが利用されていないため廃止されます。(既にsettype($type)関数には'real'と入れても効果がないようになっているとのこと)

お金の計算などを行うドメインを扱うシステムでは利用されているかもしれません。

公式の対処法では、単に'real'となっている箇所を'float'に置き換えれば同じふるまいになるそうなのでそこまで難しくはなさそうです。

array_key_exists() with objects

関数名の通り、配列を渡すための関数ですが、オブジェクトを渡しても問題ないように見えています。オブジェクトのプロパティが存在するかは別関数があるのでそちらに差し替えるようにする必要があります。

過去との互換性を保つため、key が仮に array で指定したオブジェクトのプロパティであっても array_key_exists() は TRUE を返します。 しかし、この挙動に頼ってはいけません。 array にはオブジェクトではなく配列を渡すようにしましょう。
オブジェクトのプロパティが存在するかどうかを調べるには、 property_exists() を使いましょう。
https://www.php.net/manual/ja/function.array-key-exists.php

mb_strrpos() with encoding as 3rd argument

公式ドキュメントにそのままのっていました。現在はencodingは第4引数になっているのですが、移行時間を設けていままで第3引数でも通るようになっていました。が、ついにPHP8で削除されます。

注意: encoding パラメータは、 PHP 5.2.0 以降は三番目のパラメータではなく四番目のパラメータに変わりました。 過去との互換性を保つために encoding を三番目の引数で指定することもできますが、これは推奨されません。 将来は削除される予定です。
https://www.php.net/manual/ja/function.mb-strrpos.php

implode() parameter order mix

公式ドキュメントに注意書きの非推奨文言がありました。ついに歴史に終止符を打つようです。

注意:
implode()は、歴史的な理由により、引数をどちら の順番でも受けつけることが可能です。しかし、 explode() との統一性の観点からは、 ドキュメントに記述された引数の順番を使用しないことは推奨されません。
https://www.php.net/manual/ja/function.implode.php

以下のシグニチャに書き換える必要があります。

implode ( string $glue , array $pieces ) : string
implode ( array $pieces ) : string

所感

記載したもの以外にも非推奨コマンドはあるのと、業務ドメインによってはささるようなメソッドもあったのでいまいちど確認したほうがいいなと感じました。PHPの、コミュニティとともに変化していくのが楽しいと思いつつ、実システムの考慮はやや大変だなと思いつつ。。これからのPHP8を楽しみに待ちたいです。

https://wiki.php.net/rfc/deprecations_php_7_2
https://wiki.php.net/rfc/deprecations_php_7_3
https://wiki.php.net/rfc/deprecations_php_7_4

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

PHP: $HTTP_GET_VARS [非推奨]の書き換え

$HTTP_GET_VARSは非推奨になっています。

しかし、古いプログラムでは使われています。
例えば、

test_args.php
<?php
// -----------------------------------------------------------------
echo "<html>";
echo '<meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" />';
echo "<body>";
echo "test_args.php<p />";
echo "<h2>テスト</h2>";
$in_vars = "";
while (list($key, $val) = each($HTTP_GET_VARS)) {
    $in_vars    .= $key."=".$val. "<p />";
}
echo $in_vars;
echo "Jun/28/2020<p />";
echo "</body>";
echo "</html>";
// -----------------------------------------------------------------
?>

このプログラムを走らせると、次のようなログが出ます。

/var/log/apache2/error.log
[Sun Jun 28 20:40:57.834991 2020] [php7:warn] [pid 8012] [client 127.0.0.1:41472] PHP Warning:  Variable passed to each() is not an array or object in /home/uchida/html/data_base_language/test_dir/php/test_args.php on line 10, referer: http://localhost/html/data_base/test_dir/php/

次のように修正すれば、正常に動きます。

<?php
// -----------------------------------------------------------------
echo "<html>";
echo '<meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" />';
echo "<body>";
echo "test_args.php<p />";
echo "<h2>テスト</h2>";
$in_vars = "";

// while (list($key, $val) = each($HTTP_GET_VARS)) {
while (list($key, $val) = each($_GET)) {
    $in_vars    .= $key."=".$val. "<p />";
}
echo $in_vars;
echo "Jun/28/2020<p />";
echo "</body>";
echo "</html>";
// -----------------------------------------------------------------
?>

次のように引数を与えて実行した結果です。
test_args.php?aa=12&bb=34&cc=56

test_args_jun28.png

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

【Laravel】idごとの情報を表示する

ユーザーのマイページのような、特定のidの情報を表示する方法のメモ。

Route

web.php
Route::group(['prefix'=>'~~~~~~','middleware'=>'auth'], function () {
    Route::get('index', '*******Controller@index')->name('~~~~~~.index');
    Route::get('create', '*******Controller@create')->name('~~~~~~.create');
    Route::post('store', '*******Controller@store')->name('~~~~~~.store');
    Route::get('show/{id}', '*******Controller@show')->name('~~~~~~.show');//この部分
});

Controller

****Controller.php
public function show($id)
    {
        $***** = モデル名::find($id);  //idを見つける

        //条件分岐の処理
        if($*****->gender === 0){
            $gender = '男性';
        }elseif ($*****->gender === 1) {
            $gender = '女性';
        }

        return view('*****.show',compact('*****','gender'));
    }

条件分岐する必要がある場合は、Controller内で処理させてその変数をcompactの引数に入れる。

View

show.blade.php
<div>
    {{$contact->name}}
    {{$gender}}  //Controllerでcompactの引数にセットしているので変数がそのまま使える。
</div>

表示できてたらおk。

おまけ

indexページからshowに飛ばす時のボタン。

index.blade.php
<button onclick="location.href='{{route('*****.show',['id' => $*****->id])}}'">詳細</button>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel基礎】DBにデータを保存・保存内容を表示

LaravelでDBにデータを保存する方法と、保存した内容を表示する方法のメモ。

前提

resourceでControllerを作っています。

注意

コード部分の「****」「&&&&」みたいなのは代替文字です。

DBに保存

Controller,Route,Viewのそれぞれに書いていきます。

Route

web.php
Route::group(['prefix'=>'~~~~~~','middleware'=>'auth'], function () {
    Route::get('index', '*******Controller@index')->name('~~~~~~.index');
    Route::get('create', '*******Controller@create')->name('~~~~~~.create');
    Route::post('store', '*******Controller@store')->name('~~~~~~.store'); //この部分
    Route::get('show/{id}', '*******Controller@show')->name('~~~~~~.show');
});

postでviewで入力されたパラメータを受け取る。

View

create.blade.php
<form method="POST" action="{{route('~~~~~.store')}}">
    @csrf
    <input class="input-group" type="text" name="name" placeholder="Name">
    <input class="input-group" type="text" name="email" placeholder="Mail">
    男性:<input type="radio" name="gender" value="0">
    女性:<input type="radio" name="gender" value="1">
    <input class="input-group" type="text" name="title" placeholder="Title">
    <textarea class="input-group" name="contact" placeholder="Contact"></textarea>
    <input class="btn btn-primary" type="submit" value="Submit">
</form>

methodをpostに設定してパラメータをstoreに送信する。

Controller

~~~~~Controller.php
use App\Models\モデル名;

public function store(Request $request)
    {
        //Modelをインスタンス化
        $***** = new ContactForm;

        $*****->name = $request->input('name');
        $*****->email = $request->input('email');
        $*****->title = $request->input('title');
        $*****->gender = $request->input('gender');
        $*****->contact = $request->input('contact');

        $*****->save();  //保存

        return redirect('*****/index');  //indexページにリダイレクト
    }

Modelファイルを利用して送られてきたパラメータをDBに保存します。

保存できてるかどうか一度DBで確認してください。
できてたらおk。

Viewに表示

Controller,Viewのそれぞれに書いていきます。

Controller

*****Controller.php
use Illuminate\Support\Facades\DB;  //クエリビルダを利用

public function index()
    {
        // クエリビルダ
        $&&&&&& = DB::table('テーブル名')
        ->select('id','name','gender')  //表示したい情報のカラム名を記述
        ->get();  //取得
        return view('******.index',compact('&&&&&'));
    }

必要なデータだけ欲しい時はクエリビルダを使った方が処理速度が遅くならなくて済む。

View

index.blade.php
@foreach($****** as $×××××)
    {{$×××××->id}}
    {{$×××××->name}}
    @if($×××××->gender===0)
        男性
    @else
        女性
    @endif
@endforeach

データをforeachで回してあげる。
カラム名を指定して必要な情報を表示する。

Viewで表示できてるのが確認できたらおk。

まとめ

基本的にはController,View,Routeをいじればできます。

本当に基礎の基礎なので多分もっと良い書き方とか他にあるかと思いますが、基礎学習中の人間のメモなのでご容赦を。

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

PHP-Parserで、phpコードを拡張・作成するメタプログラミング入門

背景

仕事で、レガシーなソースコードに名前空間やPHPDocを機械的に付与するために調べました。

基本的に、nikic/PHP-Parserの公式docの和訳まとめです。
英語に抵抗がなく、急いでない方は公式を読まれると良いと思います。

https://github.com/nikic/PHP-Parser/tree/master/doc

要約

  • PHP-Parserで既存のファイルを最小限の変更で拡張できる
  • 名前空間を付与したり、特定の関数の書き換え、PHPDocの追加なども可能
  • かなり自由なPHPのメタプログラミングができる

nikic/PHP-Parser

https://github.com/nikic/PHP-Parser

phpで書かれたphpパーサーです。phpだけで動くので、使いやすいです。

PHP 5.2からPHP 7.4のコードを解析でき、ヒューマンリーダブルなphpファイルに出力できます。

安定版のv4.0以上の実行環境は、php7.1以上です。

静的解析ライブラリのphpstan/phpstanの、ベースのPHPの解釈にも採用されていたり、安心感があります。

インストール

composer でインストールできます。

composer require nikic/php-parser

基本編

構文木に変換、ヒューマンリーダブルなコードに戻す流れを追います。

phpコードの構文木への変換

parserにコード文字列を渡すと、変換してくれます。
パーサーは、細かくカスタマイズすることができ、詳しくは後述します。

<?php
use PhpParser\Error;
use PhpParser\NodeDumper;
use PhpParser\ParserFactory;

$code = <<<'CODE'
<?php

function test($foo)
{
    var_dump($foo);
}
CODE;

$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    return;
}

$dumper = new NodeDumper;
echo $dumper->dump($ast) . "\n";

パースした結果、下記のようなオブジェクトの配列構造になっています。
この配列の中身をチェックし、任意のものに書き換えたり、新しいオブジェクトを追加することで、新しいPHPを作成します。

array(
    0: Stmt_Function(
        byRef: false
        name: Identifier(
            name: test
        )
        params: array(
            0: Param(
                type: null
                byRef: false
                variadic: false
                var: Expr_Variable(
                    name: foo
                )
                default: null
            )
        )
        returnType: null
        stmts: array(
            0: Stmt_Expression(
                expr: Expr_FuncCall(
                    name: Name(
                        parts: array(
                            0: var_dump
                        )
                    )
                    args: array(
                        0: Arg(
                            value: Expr_Variable(
                                name: foo
                            )
                            byRef: false
                            unpack: false
                        )
                    )
                )
            )
        )
    )
)

構文木の変更

PHP-Parser にはNodeVisitorという interface があり、実装したclassをNodeTraverserに追加すると、所定のイベントで呼び出されて構文木を書き換えたり値を書き換えられます。

interface NodeVisitor {
    public function beforeTraverse(array $nodes);
    public function enterNode(Node $node);
    public function leaveNode(Node $node);
    public function afterTraverse(array $nodes);
}

beforeTraverse()およびafterTraverse()は、トラバーサルの前と後に呼ばれ、全体のASTを渡されます。これらを使用して、必要な状態のセットアップまたはクリーンアップを実行できます。

enterNode()メソッドは、ノードが最初に検出されたときに、その子が処理される前に呼び出されます。一方で、leaveNode()メソッドは、すべての子が訪問された後に呼び出されます。

実際には、これを実装したNodeVisitorAbstractを継承して、使うことが多いと思います。
今回は、var_dumpprintに書き換えます。

use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;

class VarDumpConvertPrintVisitor extends NodeVisitorAbstract
{
    public function leaveNode(Node $node)
    {
        if ($node instanceof Node\Expr\FuncCall && $node->name->parts->getLast() == 'var_dump') {
            $node->name->parts = ['print'];
        }
    }
}

$traverser = new NodeTraverser;
$traverser->addVisitor(new VarDumpConvertPrintVisitor);
$stmts = $traverser->traverse($stmts);

わかりづらいですが、下記のようにExpr_FuncCallの中身のname->partsprintに変更されると思います。

            0: Stmt_Expression(
                expr: Expr_FuncCall(
                    name: Name(
                        parts: array(
                            0: print
                        )
                    )
                    args: array(
                        0: Arg(
                            value: Expr_Variable(
                                name: foo
                            )
                            byRef: false
                            unpack: false
                        )
                    )
                )

phpコードへの出力

一番シンプルな出力方法は、下記です。

$prettyPrinter = new PhpParser\PrettyPrinter\Standard();
$newCode = $prettyPrinter->prettyPrintFile($stmts);
<?php

function test($foo)
{
    print($foo);
}

しかしこの方法だと、「既存のコードのリファクタリングでは、改行などに差分がでる」という問題があります。
構文木に解体する際に、改行コードが読み捨てられてしまうためです。

改行を維持した出力

PHP-Parser v4.0以降、コードのフォーマット(変更されていないASTノード)を保持し、変更または新しく挿入されたコードのみをフォーマットするモードが利用できます。

ちょっと記述が増えますが、必要最低限の変更に抑え上記の問題を回避できます。

※まだ実験段階の機能なので、変更があるかもしれません。

https://github.com/nikic/PHP-Parser/blob/master/doc/component/Pretty_printing.markdown

use PhpParser\{Lexer, NodeTraverser, NodeVisitor, Parser, PrettyPrinter};

$lexer = new Lexer\Emulative([
    'usedAttributes' => [
        'comments',
        'startLine', 'endLine',
        'startTokenPos', 'endTokenPos',
    ],
]);
$parser = new Parser\Php7($lexer);

$traverser = new NodeTraverser();
$traverser->addVisitor(new NodeVisitor\CloningVisitor());

$printer = new PrettyPrinter\Standard();

$oldStmts = $parser->parse($code);
$oldTokens = $lexer->getTokens();

$newStmts = $traverser->traverse($oldStmts);

// Nodeを組み替える

$newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens);

コード全文

改行読み捨て

https://gist.github.com/komtaki/514f13fa07f4e8bdd9bd0d4fa61e0719

改行保持

https://gist.github.com/komtaki/7e2163a958440e99b630bbbe1512d368

応用編 細かいNodeの歩き方

VisitorにわたってくるNodeオブジェクトはとても多様です。
NodeDumperを使って、「自分が拡張したいオブジェクトは、どんな形で渡ってくるのか」確認することから始めるのがよいと思います。

ここでは、一部特殊な機能について紹介します。

名前解決

NameResolverを使用することで、基本的なclassの名前解決ができます。

しかし、名前空間内の修飾されていない関数と定数名は解決できません。

例えば、Foo名前空間内のstoren()は、名前空間\Foo\strlen()またはグローバル\strlen()のいずれかを参照できます。しかし、PHP-Parserにはこれを決定するために必要な情報がないためです。

https://github.com/nikic/PHP-Parser/blob/master/doc/component/Name_resolution.markdown

$nameResolver = new PhpParser\NodeVisitor\NameResolver;
$nodeTraverser = new PhpParser\NodeTraverser;
$nodeTraverser->addVisitor($nameResolver);

// Resolve names
$stmts = $nodeTraverser->traverse($stmts);

ノードの除去

トラバース中に、特定のタイプを返却することで、ノードを除去することが出来ます。

public function leaveNode(Node $node) {
    if ($node instanceof Node\Stmt\Return_) {
        // すべてのreturnを削除します。
        return NodeTraverser::REMOVE_NODE;
    }
}

トラバースの中断、パフォーマンス改善

複数のVisitorを設定している場合、Node数の増加によって速度がおそくなるケースがあります。

特定のNodeを探している時などは、下記のようにすることでトラバースを終了できます。

1. 子ノードのトラバース回避

private $classes = [];
public function enterNode(Node $node) {
    if ($node instanceof Node\Stmt\Class_) {
        $this->classes[] = $node;
        return NodeTraverser::DONT_TRAVERSE_CHILDREN;
    }
}

2. トラバースの中止

private $class = null;
public function enterNode(Node $node) {
    if ($node instanceof Node\Stmt\Class_) {
        $this->class = $node;
        return NodeTraverser::STOP_TRAVERSAL;
    }
}

まとめ

PHP-Parserで、名前空間を付与したり、変数を書き変えたり、PHPDocを追加したり動的にPHPコードを拡張することが出来ます。

ただ限界はあり、コードの整形などは出来ないので、他のツールも組み合わせて柔軟に対応していくのがよいと思います。

参考

下記、参考にさせて頂きました。ありがとうございます。

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

【heroku,FuelPHP】デプロイ時のyahooメール設定方法

思うようにメール設定がうまくいかず時間がかかったので、同じ環境の方がさくっと対処できるように手順を残しておきます。

対象の方

herokuにFuelPHPを使ったwebサービスをデプロイ済みでyahooメール設定がうまくいかない方。
※MacOS環境での方法を記載しています。

手順

5ステップで解説していきます。

1.config.phpにemailを追加
2.email.phpを..fuel/app/configに複製
3.email.phpを編集
4.yahooメールの設定変更
5.email.phpのnewlineを編集(それでもエラーが出る場合)

1.config.phpにemailを追加

fuel/app/config/config.phpのpackages=>array()にemailを追加
※Emailパッケージ読み込んでEmailクラスを利用できるようにします。

fuel/app/config/config.php
'packages'  => array(   //
  'email',            //新規で追加
),

2.email.phpを..fuel/app/configに複製

/fuel/packages/email/config/email.phpをコピーし、/fuel/app/configディレクトリ内にemail.phpを複製します。
※mail設定の編集はこちらで行なっていきます。

3.email.phpを編集

複製したemail.phpを下記のように編集していきましょう。

fuel/app/config/email.php
...
 /**
  * Mail driver (mail, smtp, sendmail, noop)
  */
  'driver' => 'smtp',                       //smtpに変更
...

 'smtp' => array(
    'host'     => 'smtp.mail.yahoo.co.jp', //変更
    'port'     => 587,             //変更
    'username' => 'yahooメールのログインID',   //変更
    'password' => 'yahooメールのパスワード',   //変更
    'timeout'  => 5,
    'starttls' => false,
    'options'  => array(
    ),
 ),
...

設定が気になる方はyahoo公式サイト(メールソフトやモバイル端末での設定)にも解説してあるので参考にしてみてください。
スクリーンショット 2020-06-28 12.58.36.png

4.yahooメールの設定変更

ここまで設定して下記の認証エラーが出る場合はyahooメール側の設定を確認してみましょう。
スクリーンショット 2020-06-28 13.01.16.png

先ほどと同じyahoo公式サイト(Yahoo!メールでの設定)に解説してあるので確認してみましょう。
※「Yahoo! JAPAN公式サービス以外からのアクセスも有効にする」にチェックを入れ、「IMAP」「POP」「SMTP」をそれぞれ「有効にする」を選択できていればOKです。

私はここに行き着くのに結構時間が掛かってしましました笑

5.email.phpのnewlineを編集(それでもエラーが出る場合)

4まで設定してメール送信できた方は見なくて良いですが、私の場合は下記エラーが出ました。
スクリーンショット 2020-06-28 13.06.24.png

色々と試してみましたが、fuel/app/config/email.phpのnewlineを変更することでメール送信できるようになりました。

fuel/app/config/email.php
/**
 * Newline
 */
 'newline' => "\r\n",                //"\n"から変更

gmailの場合ですが、公式アカウントにも記載ありました。

改行のコードを変更する必要があるみたいですね。
スクリーンショット 2020-06-28 13.27.39.png

それでは最後まで読んで頂きありがとうございました。

参考になった方はLGTMをポチッと押していただけると励みになります。

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

カラーミーAPIを利用して商品一覧のjsonを取得する

「カラーミーAPI」を利用して、
カラーミーで作ったショップの全商品一覧および各商品の詳細を、
別サーバー(ここではheteml)にjsonとして保存した時のソースコードです。
以下のようなコードをhetemlのweb以下に「products.php」として置き、
cronで10分ごとに動かし、
同ディレクトリに「cache.json」として保存します。

#!/usr/local/php/7.3/bin/php
<?php
$request_options = array(
  'http' => array(
  'method' => 'GET',
  'header'=> "Authorization: Bearer オーソリゼーションコードだよ〜\r\n"
  )
);
//アクセストークンなどAPI取得に必要な情報だよ
$context = stream_context_create($request_options);
//商品一覧を取得する
//先に商品が全部でいくつあるか取得する
$url = 'https://api.shop-pro.jp/v1/products.json'; //あとでoffsetで51件目以降の処理を作る
$response_body = file_get_contents($url, false, $context);//API取得に必要な情報を一緒に送る
$response_body = json_decode($response_body, true);
$all_the_products_num = $response_body["meta"]["total"];
//次に、1-50件、51-100件、と回していく
for($i = 0; $i < $all_the_products_num; $i++){
  $key_num = $i%50;
  if($i%50 == 1){
    $url = 'https://api.shop-pro.jp/v1/products.json?limit=50&offset='.($i-1);
    $response_body = file_get_contents($url, false, $context);
    $response_body = json_decode($response_body, true);
  }
  $product_ids[$i] = $response_body["products"][$i%50]["id"];
}
//productIDの一覧ができたので、それをもとに商品詳細のapiを叩く
$tmp .= "{\"product\":[";
for($i = 0; $i < count($product_ids); $i++){
    $url = 'https://api.shop-pro.jp/v1/products/'.$product_ids[$i].'.json';
    $response_body = file_get_contents($url, false, $context);//API取得に必要な情報を一緒に送る
    $result = json_decode($response_body);
    $tmp .= json_encode($result->{"product"});
    if($i != count($product_ids)-1){
      $tmp .= ",";
    }
}
$tmp .= "]}";
$file = '書き出し先のhetemlの絶対パス/cache.json';
// ファイルをオープンして既存のコンテンツを取得します
$current = file_get_contents($file);
// 結果をファイルに書き出します
file_put_contents($file, $tmp);
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(自分用)Flask_AWS_1(AWS仮想環境にPHP,MySQL,phpMyAdmin,Pythonのインストール)

項目

  1. PHPのインストール
  2. MySQLのインストール
  3. PHPMyAdminのインストール
  4. Pythonのインストール

1.PHPのインストール

ターミナル
# まずLinux仮想マシンに接続、ここを覚えてないなら1つ前のやつを見て
$ ssh -i ~/.ssh/FirstKey.pem ec2-user@(パブリックIP)

# 管理者権限に変更
$$ sudo su

# PHPをインストール
$$ yum install -y php

# PHPの設定を変える前にバックアップを取っておく
$$ cp /etc/php.ini /etc/php.bak

# viでPHPの設定変更
$$ vi /etc/php.ini
  • :set numberで行番号を表示
  • :520で520行目に移動
  • iにて記述モードにし、error_reporting = E_ALL & ~E_DEPRECATEDという行を、error_reporting = E_ALL & ~E_DEPRECATED & ~E_NOTICEに変更する
  • escでコマンドモードに戻る
  • :537で537行目へ
  • iからのdisplay_errors = OffOnに変更、esc
  • :wqで保存終了
ターミナル
# Apacheを再起動し適用
$$ service httpd restart

# [OK]*2出たら多分大丈夫

2.MySQLのインストール

  • 前回に引き続き管理者権限でログインしてる前提
ターミナル
# MySQLのインストール
$$ yum install -y mysql-server

# MySQLをPHPがいい感じに動かせるものを入れる
$$ yum install -y php-mysqlnd

# Pythonでいい感じにね
$$ yum install mysql-connector-python

# MySQLを起動
$$ service mysqld start

# MySQLの設定をあれこれ
$$ mysql_secure_installation
# 一発目のrootパスワード聞いてくるところはそのままEnter
# rootパスワード新しくするか聞かれるので、yes
# 好きなパスワードを入力
# anonymousユーザ消すか聞いてくるのでyes
# あとは全部yでも良さげ

# MySQLでの文字コードを設定
$$ vi /etc/my.cnf

# `:set number`で行数を表示し空白の10行目に
$$ character-set-server = utf8
# と入力する、繰り返すが`i`で入力モードにし、入力後`esc`でコマンドモード
# `:wq`で保存終了

# MySQLを再起動
$$ service mysqld restart
#[OK]と出れば`OK`

3.PHPMyAdminのインストール

  • 前回に続き仮想環境の管理者権限内を前提
ターミナル
# phpMyAdminのインストール先を作成
$$ yum-config-manager --enable epel

# phpMyAdminをインストール
$$ yum install -y phpmyadmin

# phpMyAdminの設定を開く
$$ vi /etc/httpd/conf.d/phpMyAdmin.conf
# ここで127.0.0.1って書いてあるところをグローバルIPに変更すると、自分のローカルなマシンだけが接続変更出来る様になる
# やらなくていいと思う
# もし変えたい場合は、`:%s/127.0.0.1/自分のグローバルIP/g`で変更

# Apacheの再起動
$$ service httpd restart
  • http://前に設定したAWSのパブリックIP/phpmyadmin/に接続すると、phpMyAdminのページが出てくる
  • ユーザ名はroot、パスワードはMySQLインストールで設定したやつを入れる

4.Pythonのインストール

  • 前に続き仮想環境内(管理者権限ではない)
ターミナル
# 一回Linux環境アップデート
$$ sudo yum -y update

# Pythonインストール
$$ sudo yum install python36-devel python36-libs python36-setuptools
# もうMySQLをPythonでいい感じにするやつはインストール済み

# Apacheを再起動
$$ service httpd restart

5.終わりに

  • 環境構築がいっっっちばんつまらない最悪
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

環境を汚さずに Composer を使いたい

要約

Composer どうやって実行してますか?

PHP 開発に Composer は必須です。
ではどうやって実行しているでしょうか。

  • ローカルで composer コマンドを使う
    • ローカルにインストールする
    • composer.phar をコミットする
  • Docker コンテナで composer コマンドを使う
    • PHP コンテナにインストールする
      • 自前インストール
      • Composer イメージを使ってインストール
    • コミットされた composer.phar を PHP コンテナ内で使う
    • Composer コンテナを使う
      • Docker で使う
      • Docker Compose で使う ← 今回はこれ
  • GitHub Actions で composer コマンドを使う

様々な実行方法があると思います。
ローカルで composer コマンドを使うには PHP もインストールしないといけません。
ローカルに環境を構築するのは大変です。チームメンバー全員の面倒なんて見たくありません。
そこで Docker コンテナの出番です。
よく見かけるのは PHP コンテナに Composer をインストールする方法です。
ですが Composer を使うのはライブラリのインストール(復元)時だけです。
稼働中の PHP コンテナに果たして必要なものでしょうか?
そこで今回は独立した Composer コンテナでコマンドを実行する方法を紹介します。

Composer コンテナ

composer - Docker Hub

docker コマンドで直接使用する方法は ComposerをDockerコンテナで動かす - Qiita を、
PHP コンテナにインストールする方法は Docker に Composer をインストールするベストプラクティス(と解説) - Qiita を参考にしてください。
この記事では独立した Composer コンテナを docker-compose コマンドで使用する方法を紹介します。
PHP コンテナは不要なので Composer だけ使いたい方にもおすすめです。

Docker Compose で定義する

docker-compose.yml
version: "3.8"

services:
  composer:
    image: composer
    command: "composer install"
    volumes:
      - ./:/app

./:/app: の前がローカルのディレクトリパス、後が composer コンテナ内のパスです。
ローカルは composer.json, composer.lock を作成したい場所、 composer コンテナは /app を指定します。

ライブラリのインストール (composer require)

docker-compose run composer composer require --dev phpunit/phpunit

composer が 2 つあるのは 1 つ目がコンテナ名(変更可)で 2 つ目がコマンド名です。

ライブラリの復元 (composer install)

docker-compose.yml に command: "composer install" を定義しておけば docker-compose up -d する際に自動的に composer install が実行されます。定義しなくても良いです。

もちろんコマンドでも実行できます。

docker-compose run composer composer install

ライブラリのバージョンアップ

composer.lock の更新。

docker-compose run composer composer update

その他のコマンド

他のコマンドも同様に docker-compose run composer から実行できます。
どんなコマンドがあるかは PHP開発でComposerを使わないなんてありえない!基礎編 - Qiita を参照してみてください。

サンプルコード

SnowCait/docker-compose-composer-example にサンプルコードを置いています。
Docker がインストールされていれば git clone して気軽に試せると思うので良かったら試してみてください。

参考にした記事

Docker環境でcomposerのimageつかってupdateしてみる - Qiita
補足:リンク先では command: "composer update" となっていますが起動する度にバージョンが変わるのはよろしくないので command: "composer install" にしておいた方がいいのではないかと思います。

余談

(Docker) Compose と Composer が似ていて間違えそう。

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

PHPUnit 配列の順番は考慮・気にしないAssert

概要

配列(連想配列も含む)の順番は気にせず、要素は同じであることを検証するためのAssert

※ そもそも、順番も保証された上でのテストであったほうが良いというのも分かるのですが、今回はスコープ外とします

※ こんなAssertがあればいいなと作成したものなので「既にPHPUnitで用意されてるよ」などあればコメントお願いします?

本題

利用したいテストでこのTraitを使う想定でこんな感じで実装しました :pencil:

<?php

trait ArraySimilarTrait
{
    /**
     * PHPUnit assert arrays ignore orders.
     *
     * @param array $expected
     * @param array $actual
     */
    protected function assertArraySimilar(array $expected, array $actual)
    {
        $this->assertSame([], array_diff_key($actual, $expected));

        array_multisort($expected);
        array_multisort($actual);

        foreach ($expected as $key => $value) {
            if (is_array($value)) {
                $this->assertArraySimilar($value, $actual[$key]);
            } else {
                $this->assertContains($value, $actual);
            }
        }
    }
}

gistでも公開しています

ポイントとしては以下の3つです。

  1. 比較する2つの配列でキーが同じであること
  2. 配列・連想配列どちらでも対応できること
  3. どの次元においても順番は異なっても問題ないこと

1. 比較する2つの配列でキーが同じであること

$this->assertSame([], array_diff_key($actual, $expected));

言葉の通りですが、比較する2つの配列で同じキーで差分がないことを検証しています。

2. 配列・連想配列どちらでも対応できること

array_multisort($expected);
array_multisort($actual);

「順番を考慮しないはずなのに、なぜ並び替えをしているのか。」という疑問が生まれると思います。

今回、配列のどの次元においても順番は気にない仕様にしております。

そのために、要素が配列であれば、続くコードで以下のように再帰的に assertArraySimilar メソッドを呼び出すようにしています。

foreach ($expected as $key => $value) {
  if (is_array($value)) {
    $this->assertArraySimilar($value, $actual[$key]); // ? 配列(Array)の場合、添字での参照になります
  } else {
    $this->assertContains($value, $actual);
  }
}

コメントにも書いたように $actual[$key] のように書くと、配列(Array)の場合、添字での参照になります。

そのため、配列(Array)の場合でも、同じ要素へ参照するために並び替えをしています。

※ ここで 配列(Array) という書き方をしたのは、 連想配列(Hash) と区別するためです。

PHPには、配列(Array), 連想配列(Hash)は型として区別されていませんが、 array_multisort メソッドを利用することで、
配列(Array)であれば、キーを振り直す sort メソッドにあたる挙動を
連想配列(Hash)であれば、キーを振り直さない asort メソッドにあたる挙動をしてくれます。

3. どの次元においても順番は異なっても問題ないこと

2. 配列・連想配列どちらでも対応できること でほぼ書いてしまいましたが、
再帰的に assertArraySimilar メソッドを呼び出すことで、
どの次元においても順番は異なっても気にない = Pass します。


テスト用Traitのテスト

今回、作成したAssertが期待した挙動になっているのか確認のためテストも書きました。

どういうパターンだと成功・失敗するのかこちらのほうがわかりやすいかと思います。

<?php

class ArraySimilarTraitTest extends \PHPUnit\Framework\TestCase
{
    use ArraySimilarTrait;

    /**
     * @test
     * @dataProvider data_assertArraySimilar_expected_passed
     * @param array $expected
     * @param array $actual
     */
    public function assertArraySimilar_expected_passed(array $expected, array $actual)
    {
        $this->assertArraySimilar($expected, $actual);
    }

    public function data_assertArraySimilar_expected_passed()
    {
        return [
            '1D Array' => [
                [1, 2, 3],
                [3, 2, 1],
            ],
            '1D Hash' => [
                ['a' => 1, 'b' => 2, 'c' => 3],
                ['c' => 3, 'b' => 2, 'a' => 1],
            ],
            '1D Array, 2D Array' => [
                [[1, 2], [3, 4]],
                [[2, 1], [4, 3]],
            ],
            '1D Array, 2D Hash' => [
                [['a' => 1, 'b' => 2], ['c' => 3, 'd' => 4]],
                [['b' => 2, 'a' => 1], ['d' => 4, 'c' => 3]],
            ],
            '1D Hash, 2D Array' => [
                ['a' => [1, 2], 'b' => [3, 4]],
                ['b' => [4, 3], 'a' => [2, 1]],
            ],
            '1D Hash, 2D Hash' => [
                ['a' => ['a1' => 11, 'a2' => 12], 'b' => ['b1' => 21, 'b2' => 22]],
                ['b' => ['b2' => 22, 'b1' => 21], 'a' => ['a2' => 12, 'a1' => 11]],
            ],
            'Array, Hash mixed' => [
                ['a' => ['a1' => 11, 'a2' => 12], 'b' => [21, 22]],
                ['b' => [22, 21], 'a' => ['a2' => 12, 'a1' => 11]],
            ],
        ];
    }

    /**
     * @test
     * @dataProvider data_assertArraySimilar_expected_failure
     * @param array $expected
     * @param array $actual
     */
    public function assertArraySimilar_expected_failure(array $expected, array $actual)
    {
        try {
            $this->assertArraySimilar($expected, $actual);
        } catch (\PHPUnit\Framework\ExpectationFailedException $e) {
            return;
        }
        $this->fail('Expected exception was not thrown.');
    }

    public function data_assertArraySimilar_expected_failure()
    {
        return [
            '1D Array' => [
                [1, 2, 3],
                [1, 2, 4],
            ],
            '1D Hash' => [
                ['a' => 1, 'b' => 2, 'c' => 3],
                ['a' => 1, 'b' => 2, 'd' => 3],
            ],
            '1D Array, 2D Array' => [
                [[1, 2], [3, 4]],
                [[1, 2], [3, 5]],
            ],
            '1D Array, 2D Hash' => [
                [['a' => 1, 'b' => 2], ['c' => 3, 'd' => 4]],
                [['a' => 1, 'b' => 2], ['c' => 3, 'e' => 4]],
            ],
            '1D Hash, 2D Array' => [
                ['a' => [1, 2], 'b' => [3, 4]],
                ['a' => [1, 2], 'b' => [3, 5]],
            ],
            '1D Hash, 2D Hash' => [
                ['a' => ['a1' => 11, 'a2' => 12], 'b' => ['b1' => 21, 'b2' => 22]],
                ['a' => ['a1' => 11, 'a2' => 12], 'b' => ['b1' => 21, 'b3' => 22]],
            ],
            'Array, Hash mixed' => [
                ['a' => ['a1' => 11, 'a2' => 12], 'b' => [21, 22]],
                ['a' => ['a1' => 11, 'a3' => 12], 'b' => [21, 22]],
            ],
        ];
    }
}

さいごに、冒頭で述べたように、本来、できることなら、順番も保証された上でのテストであったほうが良いとは思うので「なぜ期待した順番にならないのか。」というところも調査するのがいいのかもしれません。

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

学習日記8日目(2020/6/27)

学習内容

  • JavaScript基礎学習:Progateの学習コース全終了

 JSの学習を終了してPHPやLaravelの学習に移っていきたいと思います。

その他

  • タイピング練習
  • paizaのスキルチェックに挑戦

 参加しているサロンの方々がやっているのを目にし、気になったのでpaizaのスキルチェックやってみました。しかし、、標準入力ってなんやねん!という感じでそもそも入力値を変数等に代入できなくて積みました?

 そんなこんなでfgets関数を使ったことがなかったので調べ、どうもfgets関数を繰り返し実行すれ入力を一行目から順に取り出せるらしいことがわかったので明日以降Cランクの問題にチャレンジしようと思います。今日はDランクの問題1問解いて終了です。

明日の予定

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

Laravelアプリ同士をWeb APIで連携させる

オンプレで動く Laravel と、クラウドで動く Laravel を、Web API でシステム間連携させた話。
image.png

やりたいこと

社内オンプレの給与明細APサーバにアクセスすると、クラウドの勤怠打刻APサーバのデータも取得し、給与明細と合わせて勤怠実績明細も表示させる。
image.png

Web API の設計

設計時に決めておくべき項目。

  • HTTPメソッド
    GET

  • エンドポイント
    /api/v1/kintai/{yyyyMM}/{勤怠個人番号}

  • 機能概要
    従業員の勤怠実績データを返す

  • リクエスト

    • yyyyMM ・・・ 年月("last" と指定された場合は最終年月とする)
    • 勤怠個人番号 ・・・ 従業員の勤怠個人番号(Active Directory に照会すること)
  • レスポンス

    • ステータスコード
      • 200 ・・・ 成功
      • 404 ・・・ リクエストされた勤怠実績が存在しなかった
    • 応答形式 ・・・ JSON

REST APIはステートレスなので、認証にCookieやセッションは使えない。APIではトークン認証BASIC認証が一般的だが、今回は社内システムということもあり、簡素化のため認証は省略する。

【参考】人事労務パッケージが提供するAPI

Laravelでの実装方法

勤怠打刻APサーバ

APIが呼ばれる側のサーバ。
api.phpにエンドポイントを定義する。web.phpとは違い、api.phpにはCSRF保護が無い。

routes\api.php
Route::get('v1/kintai/{yyyymm}/{id}', 'Api\KintaiController@index');

呼ばれるコントローラを作成する。

app\Http\Controllers\Api\KintaiController.php
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Models\Result;
use Carbon\Carbon;
use App\Http\Controllers\Controller;

/*
  給与明細サーバから呼ばれるクラス
*/
class KintaiController extends Controller
{
    public function index(Request $request, $yyyymm, $syaincd)
    {
        if ($yyyymm == 'last') {  // last指定時は最終年月を仮定
            $yyyymm = Carbon::yesterday()->format('Ym');
        }
        $baseDate = Carbon::create(substr($yyyymm, 0, 4), substr($yyyymm, 4, 2), 1);  // 基準日の設定

        // 勤怠実績データベースの読み込み
        $query = Result::where('syaincd', $syaincd)
            ->whereBetween('kintaijissekiymd', array($baseDate->copy()->startOfMonth()->format('Ymd'), $baseDate->copy()->endOfMonth()->format('Ymd')))
            ->orderBy('kintaijissekiymd');

        // レコード件数が0より大きければデータを返し、そうでなければエラーレスポンスとする
        if ($query->count() > 0) {
            return response()->json([
                'code'     => 200,
                'contents' => $query->get()
              ], 200);
        } else {
            return response()->json([
                'code'     => 404,
                'message'  => 'Not Found'
              ], 404);
        }
    }
}

Eloquentモデルでは、文字列にキャストされるときJSONに変換されるため、Eloquentオブジェクトをそのまま返すことができる。

コマンドからAPIをテストする

curlコマンドで得られるJSONデータをjqコマンドで整形して確認。

シェルからコマンドを実行
curl -s https://example.com/api/v1/kintai/202006/12765 | jq
出力結果
{
  "code": 200,
  "contents": [
    {
      "syaincd": 12765,
      "syainmei": "山田 太郎",
      "kanrisyouninkengencode": "一般社員",
      "kintaijissekiymd": "20200625",
      "syugyozikantaikaishihhmi": "0850",
      "syugyozikantaisyuryohhmi": "1730",
      "meisyo": "出勤",
      "barcodesyukkinhhmi": "0842",
      "barcodetaikinhhmi": "1738",
      :

給与明細APサーバ

APIを呼ぶ側のサーバ。
昨今のWebアプリでは、APIで取得したデータはVueやReactで表示することが多いが、ここではLaravelで受けてビュー(Blade)に渡す。

app\Http\Controllers\KintaiController.php(一部)
$context = stream_context_create(array(
  'http' => array('ignore_errors' => true)
));
$kintaicd = $request->session()->get('user')->kintaicd;
// APIを呼び出す
$json = file_get_contents("https://example.com/api/v1/kintai/$yyyymm/$kintaicd", false, $context);
$ary = json_decode($json);  // JSONを配列に

// 正常なら "contents" をViewに渡す
if ($ary->code == 200) {
    $reports = $ary->contents;
} else {
    $reports = null;
}

return view('kintai', compact('reports'))
   ->with('spec_type', 'kintai')
   ->with('ym', substr($yyyymm, 0, 4) . '年' . ltrim(substr($yyyymm, 4, 2), '0') . '月')
   ->with('prev_month', $dt->copy()->subMonth(1)->format('Ym'))
   ->with('next_month', $dt->copy()->addMonth(1)->format('Ym'));

ビュー(Blade)で $reports を展開する。

resources\views\kintai.blade.php
<div class="table-responsive">
    <table class="table text-nowrap table-bordered table-striped table-hover table-condensed">
      <thead>
      <tr>
        <td rowspan="2">日付</td>
        <td rowspan="2">勤怠種別</td>
        <td colspan="2">打刻</td>
         省略
      </tr>
      <tr>
        <td>出勤</td>
        <td>退勤</td>
        <td>就業開始</td>
         省略
      </tr>
      </thead>
      <tbody>
      @foreach ($reports as $report)
      <tr>
          <td>{{ $report->kintaijissekiymd }}</td>
          <td>{{ $report->syukkinhhmi }}</td>
          <td>{{ $report->taikinhhmi }}</td>
           省略
      </tr>
      @endforeach
      </tbody>
    </table>
</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelアプリ同士をWeb APIで連携させるには

オンプレで動く Laravel と、クラウドで動く Laravel を、Web API でシステム間連携させてみた話。
image.png

やりたいこと

社内オンプレの給与明細APサーバにアクセスすると、クラウドの勤怠打刻APサーバのデータも取得し、給与明細と合わせて勤怠実績明細も表示できるようにする。
シーケンス図で書くと次のようになる。
image.png
明細表示例
image.png

Web API の設計

設計時に決めておくべき項目。

  • HTTPメソッド
    GET

  • エンドポイント
    /api/v1/kintai/{yyyyMM}/{勤怠個人番号}

  • 機能概要
    従業員の勤怠実績データを返す

  • リクエスト

    • yyyyMM ・・・ 年月("last" と指定された場合は最終年月とする)
    • 勤怠個人番号 ・・・ 従業員の勤怠個人番号(Active Directory に照会すること)
  • レスポンス

    • ステータスコード
      • 200 ・・・ 成功
      • 404 ・・・ リクエストされた勤怠実績が存在しなかった
    • 応答形式 ・・・ JSON

REST APIはステートレスなので、認証にCookieやセッションは使えない。APIではトークン認証BASIC認証が一般的だが、今回は社内限定利用ということもあり、簡素化のため認証は省略する。

【参考】人事労務パッケージが提供するAPI

Laravelでの実装方法

勤怠打刻APサーバ

APIが呼ばれる側のサーバ。
api.phpにエンドポイントを定義する。web.phpとは違い、api.phpにはCSRF保護が無い。

routes\api.php
Route::get('v1/kintai/{yyyymm}/{id}', 'Api\KintaiController@index');

呼ばれるコントローラを作成する。

app\Http\Controllers\Api\KintaiController.php
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Models\Result;
use Carbon\Carbon;
use App\Http\Controllers\Controller;

/*
  給与明細サーバから呼ばれるクラス
*/
class KintaiController extends Controller
{
    public function index(Request $request, $yyyymm, $syaincd)
    {
        if ($yyyymm == 'last') {  // last指定時は最終年月を仮定
            $yyyymm = Carbon::yesterday()->format('Ym');
        }
        $baseDate = Carbon::create(substr($yyyymm, 0, 4), substr($yyyymm, 4, 2), 1);  // 基準日の設定

        // 勤怠実績データベースの読み込み
        $query = Result::where('syaincd', $syaincd)
            ->whereBetween('kintaijissekiymd', array($baseDate->copy()->startOfMonth()->format('Ymd'), $baseDate->copy()->endOfMonth()->format('Ymd')))
            ->orderBy('kintaijissekiymd');

        // レコード件数が0より大きければデータを返し、そうでなければエラーレスポンスとする
        if ($query->count() > 0) {
            return response()->json([
                'code'     => 200,
                'contents' => $query->get()
              ], 200);
        } else {
            return response()->json([
                'code'     => 404,
                'message'  => 'Not Found'
              ], 404);
        }
    }
}

Eloquentモデルでは、文字列にキャストされるときJSONに変換されるため、Eloquentオブジェクトをそのまま返すことができる。

コマンドからAPIをテストする

curlコマンドで得られるJSONデータをjqコマンドで整形して確認。

シェルからコマンドを実行
curl -s https://example.com/api/v1/kintai/202006/12765 | jq
出力結果
{
  "code": 200,
  "contents": [
    {
      "syaincd": 12765,
      "syainmei": "山田 太郎",
      "kanrisyouninkengencode": "一般社員",
      "kintaijissekiymd": "20200625",
      "syugyozikantaikaishihhmi": "0850",
      "syugyozikantaisyuryohhmi": "1730",
      "meisyo": "出勤",
      "barcodesyukkinhhmi": "0842",
      "barcodetaikinhhmi": "1738",
      :

給与明細APサーバ

APIを呼ぶ側のサーバ。
昨今のWebアプリでは、APIで取得したデータはVueやReactで表示することが多いが、ここではLaravelで受けてビュー(Blade)に渡す。

app\Http\Controllers\KintaiController.php(一部)
$context = stream_context_create(array(
  'http' => array('ignore_errors' => true)
));
$kintaicd = $request->session()->get('user')->kintaicd;
// APIを呼び出す
$json = file_get_contents("https://example.com/api/v1/kintai/$yyyymm/$kintaicd", false, $context);
$ary = json_decode($json);  // JSONを配列に

// 正常なら "contents" をViewに渡す
if ($ary->code == 200) {
    $reports = $ary->contents;
} else {
    $reports = null;
}

return view('kintai', compact('reports'))
   ->with('spec_type', 'kintai')
   ->with('ym', substr($yyyymm, 0, 4) . '年' . ltrim(substr($yyyymm, 4, 2), '0') . '月')
   ->with('prev_month', $dt->copy()->subMonth(1)->format('Ym'))
   ->with('next_month', $dt->copy()->addMonth(1)->format('Ym'));

ビュー(Blade)で $reports を展開する。

resources\views\kintai.blade.php
<div class="table-responsive">
    <table class="table text-nowrap table-bordered table-striped table-hover table-condensed">
      <thead>
      <tr>
        <td rowspan="2">日付</td>
        <td rowspan="2">勤怠種別</td>
        <td colspan="2">打刻</td>
         省略
      </tr>
      <tr>
        <td>出勤</td>
        <td>退勤</td>
        <td>就業開始</td>
         省略
      </tr>
      </thead>
      <tbody>
      @foreach ($reports as $report)
      <tr>
          <td>{{ $report->kintaijissekiymd }}</td>
          <td>{{ $report->syukkinhhmi }}</td>
          <td>{{ $report->taikinhhmi }}</td>
           省略
      </tr>
      @endforeach
      </tbody>
    </table>
</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel MySQL DBのカラムタイプを途中で変更する

目的

  • Laravelにて新規作成後のDBのカラムタイプの変更方法をまとめる

実施環境

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

前提情報

  • MySQLを使用しているが確認箇所以外はそのほかのRDSでも作業内容は変わらない。
  • カラム名を変更するとLaravelアプリで自分で実装したデータ取得処理を変更しないといけない場合があるので注視する。
  • すでに存在するカラムのタイプ(データ型や有効文字数など)を変更する際の方法を記載する。
  • 現在のカラムタイプが下記に記載されているものではないと変更することはできない。
    • bigInteger
    • binary
    • boolean
    • date
    • dateTime
    • dateTimeTz
    • decimal
    • integer
    • json
    • longText
    • mediumText
    • smallInteger
    • string
    • text
    • time
    • unsignedBigInteger
    • unsignedInteger
    • unsignedSmallInteger
    • uuid
  • 公式ドキュメントに記載されている内容を元に自分なりにまとめてみる。

概要

  1. ライブラリのインストール
  2. マイグレーションファイルの作成と記載
  3. マイグレート
  4. 確認

詳細

  1. ライブラリのインストール

    1. アプリ名ディレクトリで下記コマンドを実行してライブラリをインストールする。

      $ composer require doctrine/dbal
      
  2. マイグレーションファイルの作成と記載

    1. アプリ名ディレクトリで下記コマンドを実行してマイグレーションファイルを作成する。(下記のファイル名出なくても良い。自分がわかりやすいと思うものにする必要がある。)

      $ php artisan make:migration change_カラムタイプ変更するカラム名_既存カラムタイプ_to_変更後のカラムタイプ_on_所属テーブル名_table --table=所属テーブル名
      
    2. アプリ名ディレクトリで下記コマンドを実行してマイグレーションファイルを開く。

      $ vi database/migrations/YYYY_MM_DD_XXXXXX_change_カラムタイプ変更するカラム名_既存カラムタイプ_to_変更後のカラムタイプ_on_所属テーブル名_table
      
    3. マイグレーションファイルを下記の様に修正する。(public function up()にはマイグレーションにより実行したい処理(今回だとカラムタイプの変更)処理を記載する。public function down()にはロールバックで実行したい(カラムタイプを元に直す)処理)

      database/migrations/YYYY_MM_DD_XXXXXX_change_変更する前のカラム名_to_変更した後のカラム名_on_所属テーブル名_table.php
      <?php
      
      use Illuminate\Database\Migrations\Migration;
      use Illuminate\Database\Schema\Blueprint;
      use Illuminate\Support\Facades\Schema;
      
      class Rename対象カラム名変更前カラムタイプTo変更後カラムタイプOnテーブル名Table extends Migration
      {
          /**
           * Run the migrations.
           *
           * @return void
           */
          public function up()
          {
              Schema::table('テーブル名', function (Blueprint $table) {
                  //下記を追記する
                  $table->変更したいデータ型('カラム名')->change();
              });
          }
      
          /**
           * Reverse the migrations.
           *
           * @return void
           */
          public function down()
          {
              Schema::table('テーブル名', function (Blueprint $table) {
                  //下記を追記する
                  $table->今までのデータ型('カラム名')->change();
              });
          }
      }
      
  3. マイグレート

    1. アプリ名ディレクトリで下記コマンドを実行してマイグレートを実行する。

      $ php artisan migrate
      
  4. 確認

    1. 下記コマンドを実行してMySQLにターミナルからログインする。(MySQLのパスワードを忘れてしまった方はこちら→Mac ローカル環境の MySQL 8.0 のrootパスワードを忘れた時のリセット方法)

      $ mysql -u root -p
      
    2. 下記を実行してカラムタイプが変更されたことを確認する。

      mysql> show columns from DB.テーブル名;
      

メモ

  • stringからintだとできなかったけどstringからintegerの指定だといけるかも知れん。試してみて

参考文献

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