20200927のPHPに関する記事は19件です。

オブジェクト指向の真髄がわからなくてもそれっぽいコードが書ける実装ルール

オブジェクト指向よくわからんと思っている方達も「ThoughtWorksアンソロジー」の第5章「オブジェクト指向エクササイズ」にて紹介されている9つのルールを取り入れることで、こいつオブジェクト指向が分かっている!!と思われるようなコードが書ける。はず。

今まで手続き型で実装しオブジェクト指向に慣れていない方にとってはとても異様で非現実的なルールと思われるでしょう。
しかしオブジェクト指向で実装する場合はどれも当たり前に使われるルールとなります。
すぐにでも守ることのできる簡単なルールもあるので是非、今すぐ取り入れてオブジェクト指向完全理解したったって周りにそれっぽい顔していきましょう。

※ サンプルコードはPHPで書いてます。

  1. 1つのメソッドにつきインデントは1段階までにすること
  2. else 句を使用しないこと
  3. すべてのプリミティブ型と文字列型をラップすること
  4. 1行につきドットは1つまでにすること
  5. 名前を省略しないこと
  6. すべてのエンティティを小さくすること
  7. 1つのクラスにつきインスタンス変数は2つまでにすること
  8. ファーストクラスコレクションを使用すること
  9. Getter, Setter, プロパティを使用しないこと

1つのメソッドにつきインデントは1段階までにすること

インデントは一つの処理単位となる。
それ以上深くなる場合は、メソッドやクラスに抽出しよう。

else 句を使用しないこと

ガード節や早期リターンを使いelse句を無くす。

と、あるが else if は使用しないとして else は処理によっては使用しても良いかなと思う。
逆に else(ではない時) が分離されることで可読性が落ちてしまうこともあるなーと個人的には思う。

すべてのプリミティブ型と文字列型をラップすること

その値専用のクラスを作ろう。DDD的に言うと「値オブジェクト」
その値に関わる判断/加工/計算するロジックをそのクラスに置いてあげることでコードの重複を防ぐことや変更の影響範囲を閉じ込めることができる。

NG

class User {
    /** @var String */
    private $email;
    /** @var String */
    private $password;

    // EmailやPasswordに関わる判断/加工/計算メソッドをここに持つ
}

OK

class User {
    /** @var Email */
    private $email;
    /** @var Password */
    private $password;
}

class Email {
    /** @var String */
    private $email;

    // Emailに関わる判断/加工/計算メソッドをここに持つ
}

class Password {
    /** @var String */
    private $password;

    // passwordに関わる判断/加工/計算メソッドをここに持つ
}

小さな小さなクラスが沢山できていく。

1行につきドットは1つまでにすること

ドットは、phpで言うとアロー演算子(->)になる。
オブジェクトを返すメソッドを呼び出し、その返却されたオブジェクトのメソッドをそのまま呼び出すコードなどドットが繋がっている場合、オブジェクトが他のオブジェクトを深く掘り進んでいる事になる。つまりこれはカプセル化に違反している。
ドット一つごとに変数に格納し一行になっているのを別の行に分けていく。

名前を省略しないこと

プログラミングの習慣として、数量(Quantity)をqやqtyなどと省略することがある。
人によって解釈は異なる為、ソフトウェアをわかりやすく保つには省略をせず明確な単語を使う。

ただ、まぁ習慣として一般的なものであればそれは使っても良いだろうと個人的に思う。

すべてのエンティティを小さくすること

オブジェクト指向は短いメソッドと小さなクラスでプログラムを組み立てる技術。
大きくなったメソッドやクラスなどは小さく分解する。
適切な名前で小さいクラスを作ることでどこに何の処理が書いているかが分かり変更もしやすくなる。

エンティティを小さく保つガイドライン

ここら辺を守ると良さそう

要素が30を超えるサブ要素で構成されている場合、重大な問題がある可能性が高くなります。

a)メソッドのコード行は平均30行を超えてはなりません(行スペースとコメントはカウントされません)。
b)クラスには平均30未満のメソッドが含まれている必要があり、結果として最大900行のコードになります。
c)パッケージには30を超えるクラスを含めることはできません。したがって、最大27,000のコード行で構成されます。
d)パッケージが30を超えるサブシステムは避けてください。このようなサブシステムは、最大810,000行のコードで最大900クラスをカウントします。
e)したがって、30のサブシステムを持つシステムは、27,000のクラスと2,430万のコード行を所有します。

1つのクラスにつきインスタンス変数は2つまでにすること

インスタンス変数が増えると、クラスの意図がだんだんぼやけてくる。
その結果、複数の目的に使われはじめ、巨大なクラスとなってしまう。

ファーストクラスコレクションを使用すること

配列やコレクション(ListやMap等)の操作は、コードが複雑になりがち。
その配列やコレクションをインスタンス変数に持つクラスとして抜き出す。

NG

class Profile {
    /** @var String */
    private $fullName;
    /** @var array */
    private $phoneNumbers;
}

OK

class Profile {
    /** @var String */
    private $fullName;
    /** @var PhoneNumbers */
    private $phoneNumbers;
}

class PhoneNumbers {
    /** @var array */
    private $phoneNumbers;
}

Getter, Setter, プロパティを使用しないこと

データクラスは、ただの入れ物ではなく何らかの判断/加工/計算を行うメソッドを持つ為、インスタンス変数をそのまま返すだけの getter は不要。
setter を用意すると自分以外の実装者または3ヶ月後の自分が別の処理で値を書き換えてしまう可能性があり、バグを生み出す原因となる。生成の時にインスタンス変数に詰める(完全コンストラクタパターン)などの方法を取り不変にすると良い。

まとめ

あくまでエクササイズなのでこれを絶対のルールにするという訳では無い。
一度エクササイズをしたら、緩めてガイドラインとして使うと良い。

参考

「ThoughtWorksアンソロジー ―アジャイルとオブジェクト指向によるソフトウェアイノベーション」「第5章 オブジェクト指向エクササイズ」
「現場で役立つシステム設計の原則: 変更を楽で安全にするオブジェクト指向の実践技法」「第10章 オブジェクト指向設計の学び方と教え方」

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

【備忘録】オブジェクト指向らしい開発を行う為のルール

「ThoughtWorksアンソロジー」の第5章「オブジェクト指向エクササイズ」にて紹介されている9つのルールを纏めておきます。

  1. 1つのメソッドにつきインデントは1段階までにすること
  2. else 句を使用しないこと
  3. すべてのプリミティブ型と文字列型をラップすること
  4. 1行につきドットは1つまでにすること
  5. 名前を省略しないこと
  6. すべてのエンティティを小さくすること
  7. 1つのクラスにつきインスタンス変数は2つまでにすること
  8. ファーストクラスコレクションを使用すること
  9. Getter, Setter, プロパティを使用しないこと

1つのメソッドにつきインデントは1段階までにすること

インデントは一つの処理単位となる。
それ以上深くなる場合は、メソッドやクラスに抽出しよう。

else 句を使用しないこと

ガード節や早期リターンを使いelse句を無くす。

と、あるが else if は使用しないとして else は処理によっては使用しても良いかなと思う。
逆に else を分離することで可読性が落ちてしまうこともあるなーと個人的には思う。

すべてのプリミティブ型と文字列型をラップすること

その値専用のクラスを作ろう。DDD的に言うと「値オブジェクト」
その値に関わる判断/加工/計算するロジックをそのクラスに置いてあげることでコードの重複を防ぐことや変更の影響範囲を閉じ込めることができる。

1行につきドットは1つまでにすること

ドットは、phpで言うとアロー演算子(->)になる。
オブジェクトを返すメソッドを呼び出し、その返却されたオブジェクトのメソッドをそのまま呼び出すコードなどドットが繋がっている場合、オブジェクトが他のオブジェクトを深く掘り進んでいる事になる。つまりこれはカプセル化に違反している。
ドット一つごとに変数に格納し一行になっているのを別の行に分けていく。

名前を省略しないこと

プログラミングの習慣として、数量(Quantity)をqやqtyなどと省略することがある。
人によって解釈は異なる為、ソフトウェアをわかりやすく保つには省略をせず明確な単語を使う。

ただ、まぁ習慣として一般的なものであればそれは使っても良いだろうと個人的に思う。

すべてのエンティティを小さくすること

オブジェクト指向は短いメソッドと小さなクラスでプログラムを組み立てる技術。
大きくなったメソッドやクラスなどは小さく分解する。
適切な名前で小さいクラスを作ることでどこに何の処理が書いているかが分かり変更もしやすくなる。

エンティティを小さく保つガイドライン

ここら辺を守ると良さそう

要素が30を超えるサブ要素で構成されている場合、重大な問題がある可能性が高くなります。

a)メソッドのコード行は平均30行を超えてはなりません(行スペースとコメントはカウントされません)。
b)クラスには平均30未満のメソッドが含まれている必要があり、結果として最大900行のコードになります。
c)パッケージには30を超えるクラスを含めることはできません。したがって、最大27,000のコード行で構成されます。
d)パッケージが30を超えるサブシステムは避けてください。このようなサブシステムは、最大810,000行のコードで最大900クラスをカウントします。
e)したがって、30のサブシステムを持つシステムは、27,000のクラスと2,430万のコード行を所有します。

1つのクラスにつきインスタンス変数は2つまでにすること

インスタンス変数が増えると、クラスの意図がだんだんぼやけてくる。
その結果、複数の目的に使われはじめ、巨大なクラスとなってしまう。

ファーストクラスコレクションを使用すること

配列やコレクション(ListやMap等)の操作は、コードが複雑になりがち。
その配列やコレクションをインスタンス変数に持つクラスとして抜き出す。

NG

class Profile {
    /** @var String */
    private $fullName;
    /** @var array */
    private $phoneNumbers;
}

OK

class Profile {
    /** @var String */
    private $fullName;
    /** @var PhoneNumbers */
    private $phoneNumbers;
}

class PhoneNumbers {
    /** @var array */
    private $phoneNumbers;
}

Getter, Setter, プロパティを使用しないこと

データクラスは、ただの入れ物ではなく何らかの判断/加工/計算を行うメソッドを持つ為、インスタンス変数をそのまま返すだけの getter は不要。
setter を用意すると自分以外の実装者または3ヶ月後の自分が別の処理で値を書き換えてしまう可能性があり、バグを生み出す原因となる。生成の時にインスタンス変数に詰める(完全コンストラクタパターン)などの方法を取り不変にすると良い。

まとめ

あくまでエクササイズなのでこれを絶対のルールにするという訳では無い。
一度エクササイズをしたら、緩めてガイドラインとして使うと良い。

参考

「ThoughtWorksアンソロジー ―アジャイルとオブジェクト指向によるソフトウェアイノベーション」「第5章 オブジェクト指向エクササイズ」
「現場で役立つシステム設計の原則: 変更を楽で安全にするオブジェクト指向の実践技法」「第10章 オブジェクト指向設計の学び方と教え方」

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

FuelPHP Input::post 第2引数

Input::post には第2引数を設定でき、目的の要素が見つからなかった場合に返す値を設定できる。
これによって空かどうかなどの条件式を書く必要がなくなる。

example.php
$number = \Input::post('number', 0);
//numberが見つからなかったら0を返す
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CentOS8 + nginx1.18 + php-fpm7.4 + MariaDB10.5 + CodeIgniter4の開発環境準備メモ

概要

2020年9月27日時点で、環境情報(バージョンなど)に記載の各環境を採用した開発環境を構築したので、その環境準備を行った際のメモとして残します。

環境情報(バージョンなど)

  • CentOS 8.2.2004 (Core)
  • Nginx 1.18.0
  • php-fpm 7.4
  • MariaDB Ver 15.1 Distrib 10.5.5-MariaDB
  • Composer version 1.10.13 2020-09-09 11:46:34
  • CodeIgniter 4.0.4

各種設定ファイルパスのメモ

環境 ファイルパス
nginx /etc/nginx/conf.d/default.conf
nginx /etc/nginx/nginx.conf
php /etc/php.ini
php-fpm /etc/php-fpm.d/www.conf
MariaDB /etc/my.cnf.d/server.cnf
MariaDB /etc/my.cnf.d/client.conf

CentOSのその他諸々の設定

# timedatectl set-timezone Asia/Tokyo
# localectl set-locale LANG=ja_JP.UTF-8
# source /etc/locale.conf

# dnf install -y langpacks-ja
# dnf install -y vim
# dnf install -y elfutils-libelf-devel
# dnf -y update

# vim /etc/selinux/config

SELINUX=disable ← disableに設定

Nginxのセットアップ

Nginxをdnfでインストール

# vim /etc/yum.repos.d/nginx.repo
/etc/yum.repos.d/nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1 
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

以下のサイトでは、次のような定義でインストール手順が載っていたが、上記の設定と異なり、若干古いバージョンがセットアップされるようでした。(上記:1.18 下記1.14 2020.09.26時点)
https://www.nginx.com/resources/wiki/start/topics/tutorials/install/

nginx.repo
[nginx]
name=nginx repo
baseurl=https://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1
# dnf -y install nginx

Nginxセットアップ完了の確認

# nginx -v

nginx version: nginx/1.18.0

# systemctl enable nginx
# systemctl start nginx

以下にアクセスしてnginxの画面(Welcome to nginx!)が表示されればOK
http://192.168.33.10

php-fpm7.4のセットアップ

リポジトリの追加

CentOS8の標準的なリポジトリには、2020.09.26時点で php-fpm 7.2.24 のバージョンとなっています。
7.4が最新なのでせっかくなら7.4を使いたい。
なので、追加でリポジトリを設定してそっちからセットアップ。

# dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
# dnf module enable php:remi-7.4

php-fpm7.4のインストール

開発要件に合わせて適宜修正が必要なので、下記サイトを参考に、PHP拡張モジュールで必要なものがあれば合わせてインストールする。
https://www.php.net/manual/ja/extensions.php

# dnf module install php:remi-7.4

メタデータの期限切れの最終確認: 0:32:40 時間前の 2020年09月26日 19時39分36秒 に実施しました。
依存関係が解決しました。
========================================================================================================================================
 パッケージ                    アーキテクチャー    バージョン                                           リポジトリー              サイズ
========================================================================================================================================
group/module パッケージをインストール中:
 php-cli                       x86_64              7.4.10-1.el8.remi                                    remi-modular              4.6 M
 php-common                    x86_64              7.4.10-1.el8.remi                                    remi-modular              1.2 M
 php-fpm                       x86_64              7.4.10-1.el8.remi                                    remi-modular              1.6 M
 php-mbstring                  x86_64              7.4.10-1.el8.remi                                    remi-modular              527 k
 php-xml                       x86_64              7.4.10-1.el8.remi                                    remi-modular              214 k
依存関係のインストール中:
 httpd-filesystem              noarch              2.4.37-21.module_el8.2.0+494+1df74eae                AppStream                  36 k
 oniguruma5php                 x86_64              6.9.5+rev1-2.el8.remi                                remi-safe                 206 k
 php-json                      x86_64              7.4.10-1.el8.remi                                    remi-modular               75 k
弱い依存関係のインストール中:
 nginx-filesystem              noarch              1:1.14.1-9.module_el8.0.0+184+e34fea82               AppStream                  24 k
モジュールプロファイルのインストール中:
 php/common                                                                                                                            
モジュールストリームの有効化中:
 httpd                                             2.4                                                                                 
 nginx                                             1.14                                                                                
 php                                               remi-7.4                                                                            

トランザクションの概要
========================================================================================================================================
インストール  9 パッケージ

php-fpmのセットアップ完了の確認

# php -v

PHP 7.4.10 (cli) (built: Sep  1 2020 13:58:08) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

# php-fpm -v
PHP 7.4.10 (fpm-fcgi) (built: Sep  1 2020 13:58:08)
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

php.iniファイルの場所を念のため確認

# php -i | grep php.ini
Configuration File (php.ini) Path => /etc
Loaded Configuration File => /etc/php.ini

php.iniの設定を変更

php.iniの中身を変更

/etc/php.ini
; デフォルト文字コード
default_charset = UTF-8
;HTTPのレスポンスにPHPのバージョン情報を出力するX-Powered-Byヘッダを含めない
expose_php = Off

;メモリ使用量の上限
memory_limit = 256M
;POSTリクエストのデータの最大サイズ
post_max_size = 128M
;ファイルをアップロードする際のアップロードファイルの最大サイズ
upload_max_filesize = 100M

; エラーログ
error_log = /var/log/php_error.log
; エラーを出力
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
; エラー表示(開発中は便利なので)
display_errors = On
display_startup_errors = On

[Date]
date.timezone = Asia/Tokyo

[mbstring]
; mbstringのデフォルトの言語
mbstring.language = Japanese
; HTTP入力文字のエンコーディングを内部文字のエンコーディングに自動変換しない
mbstring.encoding_translation = Off
; 文字コードを自動検出する際の優先順位
mbstring.detect_order = UTF-8,SJIS,EUC-JP,JIS,ASCII

php-fpmとnginxを連携する設定

# cp -p /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.org
# vim /etc/nginx/conf.d/default.conf
/etc/nginx/conf.d/default.conf
server {
  listen 80;
  server_name localhost;

  # ドキュメントルートの設定(apacheなどの標準的なドキュメントルートと同じ場所を設定)
  root /var/www/html;
  index index.php index.html index.htm;

  location / {
    # 指定された順序でfileやdirの存在を確認し、最初に見つかったものを返却する。
    # いずれも無かった場合は、最後に指定されたパスに遷移する。
    try_files $uri $uri/ /index.php$is_args$args;
  }

  location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    # 1台のサーバーでnginx+php-fpmを動作させる場合、Unixソケットの方が高速に動作する。
    # www.sockの場所は、/etc/nginx/conf.d/php-fpm.confの設定と合わせる。
    fastcgi_pass   unix:/run/php-fpm/www.sock;
    fastcgi_index  index.php;
    include        fastcgi_params;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param  PATH_INFO $fastcgi_path_info;
  }

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;
}

vagrantユーザーでnginxもphp-fpm関連も実行されたりするように設定を変更します。

/etc/nginx/nginx.conf
# 以下をvagrantに変更
user  vagrant;
/etc/php-fpm.d/www.conf
# 以下をvagrantに変更
user = vagrant
group = vagrant

# コメントを解除してvagrantに変更
listen.owner = vagrant
listen.group = vagrant
listen.mode = 0660

# listen.ownerとlisten.groupを指定するため以下をコメントアウト
;listen.acl_users = apache,nginx

設定を反映するためにサービスを再起動

# systemctl restart php-fpm
# systemctl restart nginx

phpinfo()で動作確認

phpinfoを実行するサンプルプログラムを用意してnginx+php-fpmの連携確認をする。

/var/www/html/index.php
<?php

phpinfo();

以下のURLにアクセスしてphpinfo()の内容が表示されればnginxとphp-fpmの連携設定まで完了です。

http://192.168.33.10/

MariaDBのセットアップ

リポジトリの追加

CentOS8のリポジトリでインストールされるMariaDBは10.3で、現時点で最新バージョンが10.5なので、これも最新バージョンをインストールしたいと思います。

# vim /etc/yum.repos.d/mariadb.repo

[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.5/centos8-amd64
module_hotfixes=1
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1


# dnf info MariaDB-server MariaDB-devel --disablerepo=* --enablerepo=mariadb

上記でリポジトリの追加が正常にできていることが確認できたので、実際にインストールします。

MariaDBをdnfでインストール

# dnf instal MariaDB-server MariaDB-devel --disablerepo=* --enablerepo=mariadb

エラー: 
 問題: cannot install the best candidate for the job
  - nothing provides libaio.so.1()(64bit) needed by MariaDB-server-10.5.5-1.el8.x86_64
  - nothing provides libaio.so.1(LIBAIO_0.1)(64bit) needed by MariaDB-server-10.5.5-1.el8.x86_64
  - nothing provides libaio.so.1(LIBAIO_0.4)(64bit) needed by MariaDB-server-10.5.5-1.el8.x86_64
  - nothing provides lsof needed by MariaDB-server-10.5.5-1.el8.x86_64
  - nothing provides perl(DBI) needed by MariaDB-server-10.5.5-1.el8.x86_64
(インストール不可のパッケージをスキップするには、'--skip-broken' を追加してみてください または、'--nobest' を追加して、最適候補のパッケージのみを使用しないでください)

なんかエラーになりますね。
エラーの原因としてはlibaio,lsof,perl-DBIがMariaDBインストールに必要だよということらしい。
そのため、該当の3つをインストールする。

# dnf install libaio
# dnf install lsof
# dnf install perl-DBI

指摘された3つはインストールしたので再チャレンジします。

# dnf install MariaDB-server MariaDB-devel --disablerepo=* --enablerepo=mariadb


メタデータの期限切れの最終確認: 0:10:24 時間前の 2020年09月27日 07時09分32秒 に実施しました。
エラー: 
 問題: package MariaDB-server-10.5.5-1.el8.x86_64 requires galera-4, but none of the providers can be installed
  - cannot install the best candidate for the job
  - nothing provides libboost_program_options.so.1.66.0()(64bit) needed by galera-4-26.4.3-1.rhel8.0.el8.x86_64
  - nothing provides libboost_program_options.so.1.66.0()(64bit) needed by galera-4-26.4.4-1.rhel8.0.el8.x86_64
  - nothing provides libboost_program_options.so.1.66.0()(64bit) needed by galera-4-26.4.5-1.el8.x86_64
  - nothing provides socat needed by galera-4-26.4.5-1.el8.x86_64
(インストール不可のパッケージをスキップするには、'--skip-broken' を追加してみてください または、'--nobest' を追加して、最適候補のパッケージのみを使用しないでください)

まだダメなんですね。
libboost_program_optionsというのが何か無いと怒られるので、boost-program-optionsをインストール。

# dnf install boost-program-options

3度目の正直にチャレンジ
うまくインストールが開始できました。

# dnf install MariaDB-server MariaDB-devel --disablerepo=* --enablerepo=mariadb

MariaDBインストールの確認

# mysql -V

mariadb  Ver 15.1 Distrib 10.5.5-MariaDB, for Linux (x86_64) using readline 5.1

MariaDBの自動起動の設定

# systemctl start mariadb
# systemctl enable mariadb
# systemctl status mariadb

mysql_secure_installationで初期設定

mysql_secure_installation
# mysql_secure_installation

# MariaDBのrootアカウントの現在のパスワードを入力して?
Enter current password for root (enter for none): Enter

# unix_socketを使った認証に変更する?
# unix_socketとは、CentOSのログインユーザー名と、MariaDB側のユーザー名が同名の場合、
# ID/PASSを用いずに認証しましょうという仕組み。(MariaDB 10.4から追加となった?)
Switch to unix_socket authentication [Y/n] : n

# MariaDBのrootアカウントのパスワードを変更する?
Change the root password? [Y/n] : Y
    New password: Hoge1234
    Re-enter new password: Hoge1234

# 匿名ユーザーを削除する?
Remove anonymous users? [Y/n] Y

# リモートからのMariaDBのrootアカウントでログインを禁止する?
Disallow root login remotely? [Y/n] Y

# だれでもアクセス可能な「test」データベースがあるけど消しとく?
Remove test database and access to it? [Y/n] Y

# 権限テーブルを再読み込みすると、ここまでのすべての変更をすぐに反映させることができるけどやる?
Reload privilege tables now? [Y/n] Y

MariaDB初期設定の確認

上記設定でMariaDBのrootアカウントのパスワードを設定しているので、そのパスワードでちゃんとアクセスできるか確認しておきます。

# mysql -u root -p
Enter password: Hoge1234

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 12
Server version: 10.5.5-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
3 rows in set (0.000 sec)

MariaDB [(none)]> show variables like "chara%";
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.001 sec)

OK.
※vagrantユーザーからsudo su -でrootになった状態だと、mysql -u root -pで求められるパスワードは何を入れてもログインできるんですね。(知らなかった。。。)

MariaDBの構築が完了したので、今度はMariaDBで利用するユーザーやデータベースを作成します。
(別に開発環境なので全部rootユーザー使ってもいいんですが、念のため。)

MaraiDBで日本語を問題なく扱うための設定

日本語を扱うため、文字コードをutf8mb4に変更します。

/etc/my.cnf.d/server.cnf
[mariadb]
character-set-server = utf8mb4
/etc/my.cnf.d/client.cnf
[client-mariadb]
default-character-set = utf8mb4

utf8mb4に設定されていることを確認。

# systemctl restart mariadb
# mysql -u root -p

MariaDB [(none)]> show variables like "chara%";
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.002 sec)

データベースとユーザーの作成

データベースの作成とユーザーの作成

# mysql -u root -p

MariaDB [(none)]> create database yudb;

全てのホストからアクセス可能なユーザー「yu」の作成
MariaDB [(none)]> create user yu;
MariaDB [(none)]> set password for yu@'%'=password('yupass');

MariaDB [(none)]> select user, host from mysql.user;
+-------------+-----------+
| User        | Host      |
+-------------+-----------+
| yu          | %         |
| mariadb.sys | localhost |
| mysql       | localhost |
| root        | localhost |
+-------------+-----------+
4 rows in set (0.004 sec)

全てのホストから「yu」ユーザーでアクセスした際、「yudb」データベースに対してcrudができるように権限を付与
MariaDB [(none)]> grant select,insert,update,delete ON yudb.* TO yu@'%';

権限を反映
MariaDB [(none)]> flush privileges;

PHPプログラムからMariaDBへの接続テスト

index.phpのプログラムをMySQLiを利用したDBアクセスの動作確認用に変更してphp -> MariaDBへの接続確認を行います。

/var/www/html/index.php
<?php

$server = 'localhost';
$user = 'yu';
$pass = 'yupass';
$dbname = 'yudb';

$my = new MySQLi($server, $user, $pass, $dbname);
$my->set_charset('utf8mb4');
$sql = 'select now() as n from dual';
$result = $my->query($sql);
// データベースの中身を取得
while($row = $result->fetch_assoc() ){
    var_dump($row);
}
// DB接続を閉じる
$my->close();

http://192.168.33.10/

Fatal error: Uncaught Error: Class 'MySQLi' not found in /var/www/html/index.php:8 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 8とエラーが発生してしまう。

これは、PHPの拡張モジュールでmysqli extensionがインストールされていないため、見つからないよというエラーです。
そのため、以下のコマンドでインストールしておきます。

# dnf -y install php-mysqlnd

http://192.168.33.10/
もう一度アクセスすると以下のように正常にMariaDBへの接続結果を確認できます。

array(1) { ["n"]=> string(19) "2020-09-27 15:11:27" }

CodeIgniterの環境準備

composerの準備から

CodeIgniterの開発環境を準備していきます。
composerを使っていきますので、まずはcomposerの準備から。

# cd /usr/local/src
# php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"

# 以下のハッシュは Latest: v1.10.13 のものです。
# ターゲットとなるバージョンが異なる場合には https://getcomposer.org/download/ を参考にして下さい。
# php -r "if (hash_file('sha384', 'composer-setup.php') === '795f976fe0ebd8b75f26a6dd68f78fd3453ce79f32ecb33e7fd087d39bfeb978342fb73ac986cd4f54edd0dc902601dc') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"

# php composer-setup.php
# php -r "unlink('composer-setup.php');"
# mv composer.phar /usr/local/bin/composer
# composer -V
Composer version 1.10.13 2020-09-09 11:46:34

composerでCodeIgniter4のプロジェクト新規作成

composerを使ってCodeIgniter4のプロジェクトを作成します。

# cd /var/www/html
# composer create-project codeigniter4/appstarter codeigniter4

以下の問題が発生したようなので、一度作成したcodeigniter4プロジェクトを削除して、必要な拡張モジュールを入れ直して、プロジェクトの再作成をします。

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - codeigniter4/framework v4.0.4 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - codeigniter4/framework v4.0.3 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - codeigniter4/framework v4.0.2 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - codeigniter4/framework v4.0.1 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - codeigniter4/framework 4.0.0 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - Installation request for codeigniter4/framework ^4 -> satisfiable by codeigniter4/framework[4.0.0, v4.0.1, v4.0.2, v4.0.3, v4.0.4].
# dnf install php-intl

再度composer create-projectを実行すると、警告は出ているが、Problemは無くなったのでとりあえず完了する。

http://192.168.33.10/codeigniter4/public/index.php

スクリーンショット 2020-09-27 15.52.39.png

codeigniter4のプロジェクトに合わせてnginxのドキュメントルートの設定を変更

{project-name}/public/ 配下が、composerでインストールしたcodeigniter4のドキュメントルートとなるようです。
なので、nginxのドキュメントルートを変更します。

/etc/nginx/conf.d/default.conf
root /var/www/html/codeigniter4/public;

上記変更後にnginxを再起動すれば、以下のURLでcodeigniter4のプロジェクトにアクセスできます。
http://192.168.33.10

CodeIgniterの環境設定

envという環境変数設定ファイルがあるのですが、このままでは利用できません。
.envという名前のファイルにコピーして使い始めます。

# cd /var/www/html/codeigniter4
# cp -p env .env
/var/www/html/codeigniter4/.env
# 開発中のエラー情報などを確認するための設定
CI_ENVIRONMENT = development

# config.phpのbase_urlで設定していたやつ
app.baseURL = 'http://192.168.33.10/'

# MySQLデータベースへの接続情報
database.default.hostname = localhost
database.default.database = yudb
database.default.username = yu
database.default.password = yupass
database.default.DBDriver = MySQLi
database.default.charset = utf8mb4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPによる(複数)画像アップロード処理

PHPの画像処理は苦手とする方が多いみたいで、リクエストをいただいたので、解説していきます!
他にもリクエストがあったら是非お願いします!
期待にお応えできるかはわからないですが笑

フロントエンド側の処理は簡単なので省略します笑
一応補足しておくと
post送信でenctype='multipart/form-data'これを指定するだけです!

user_edit.php
<?php session_start(); ?>
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>ユーザー情報編集</title>
</head>
<body>
<h1>ユーザー情報編集</h1>
<form method='post' enctype='multipart/form-data' action='upload.php'>
<input type='hidden' name='member_id' value='<?= $_GET['member_id']; ?>'>
<p>アップロード画像</p>
<input type='file' name='photo'>
<input type='submit' name='upload' value='アップロード'>
</form>
<form method='post' action='user_detail.php'>
<p>一言メッセージ<p>
<input type='hidden' name='member_id' value='<?= $_GET['member_id']; ?>'>
<input type='text' name='message' value='<?php if ($_GET['message'] != '未入力') echo $_GET['message']; ?>'>
<input type='submit' name='submit' value='送信'>
</form>
<?php endif; ?>
</body>
</html>
upload.php
<?php
session_start();
if (isset($_POST['upload']) && isset($_FILES['photo']) && isset($_SESSION['id']) && isset($_POST['member_id']) && $_SESSION['id'] == $_POST['member_id']) {
    if (empty($_FILES['photo']['name'])) {
        $msg = 'ファイルを入れてください';
    } else {
        $photo = Date('Ymdhis');
        $photo .= uniqid(mt_rand(), true);
        $tempfile = $_FILES['photo']['tmp_name'];
        switch (@exif_imagetype($tempfile)) {
            case 1:
                $photo .= '.gif';
                break;
            case 2:
                $photo .= '.jpg';
                break;
            case 3:
                $photo .= '.png';
                break;
            default:
                header('Location: upload.php');
                exit();
        }
        $filemove = './photo/' . $photo;
        try {
            $dbh = new PDO('mysql:host=localhost; dbname=dbname;', 'username', 'password');
        } catch (PDOException $e) {
            echo '接続失敗:' . $e->getMesssage();
            exit();
        }
        $upload = 'UPDATE members SET photo = :photo WHERE id = :id';
        $stmt = $dbh->prepare($upload);
        $params = array(
            ':photo' => $photo,
            ':id' => $_SESSION['id'],
        );
        if (move_uploaded_file($tempfile, $filemove)) {
            $msg = '画像アップロード完了';
            $stmt->execute($params);
        } else {
            $msg = '画像ファイルをアップロードできませんでした';
        }
    }
} else {
    $msg = '画像ファイルを入れてください';
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>画像アップロード</title>
</head>
<body>
<?php echo $msg; ?>
<p><a href='board.php'>ホームに戻る</a></p>
</body>
</html>

処理の流れはざっとこんな感じ

  1. アップロード写真が存在するかどうか
  2. 存在した場合は拡張子のチェックをする
  3. ユニークな名前に変更して、
  4. ファイル名をDBに保存し、
  5. ファイルを指定のディレクトリに保存する

こんな感じ!

まず最初から作るときはバリデーションはガン無視でいいです。
とりあえず最低限動くように作ってデバッグしながら追加していくのがおすすめー
自分でテストするときは悪意のある操作はしないのでね笑

では一つずつ解説していきましょう
まずアップロードされたファイルは$_FILESというグローバル変数に格納されて、サーバーに仮アップロードされます上で一時的に保存される

連想配列の中身 解説
name inputのname属性
type ファイル形式が保存されるが、ユーザー任意のMINEタイプになるので、多分$_SERVERと同じ感じで信用できない
tmp_name サーバーへ仮アップロードされたディレクトリとファイル名
error エラー情報
size ファイルサイズ(単位はバイト)

まず拡張子を確認するために、$_FILES['input属性']['tmp_name']を変数に格納して
exif_imagetypeを使って拡張子を判断していきます
exif_imagetypeが返す値は

定数
1 IMAGETYPE_GIF
2 IMAGETYPE_JPEG
3 IMAGETYPE_PNG
4 IMAGETYPE_SWF
5 IMAGETYPE_PSD
6 IMAGETYPE_BMP
7 IMAGETYPE_TIFF_II (intel byte order)
8 IMAGETYPE_TIFF_MM (motorola byte order)
9 IMAGETYPE_JPC
10 IMAGETYPE_JP2
11 IMAGETYPE_JPX
12 IMAGETYPE_JB2
13 IMAGETYPE_SWC
14 IMAGETYPE_IFF
15 IMAGETYPE_WBMP
16 IMAGETYPE_XBM
17 IMAGETYPE_ICO
18 IMAGETYPE_WEBP

以上のような値を返すので、それをswitch文で判別しているだけです

switch (@exif_imagetype($tempfile)) {
    case 1:
        $photo .= '.gif';
        break;
    case 2:
        $photo .= '.jpg';
        break;
    case 3:
        $photo .= '.png';
        break;
    default:
        header('Location: upload.php');
        exit();
}

これは以下のようにも書き換えられます

switch (@exif_imagetype($tempfile)) {
    case IMAGETYPE_GIF:
        $photo .= '.gif';
        break;
    case IMAGETYPE_JPEG:
        $photo .= '.jpg';
        break;
    case IMAGETYPE_PNG:
        $photo .= '.png';
        break;
    default:
        header('Location: upload.php');
        exit();
}

このように.=をつけると文字列の末尾に文字列を追加することができるので結果として拡張子をアップデートすることができますねー
拡張子がこの3タイプ以外の場合はリダイレクトされるようになってます

ここで@を付けているのはテキストファイルの拡張子を.jpgにするとWarning級のエラーが発生するのでそれを表示させないためです
エラー制御演算子@で画像の形式を判別するために必要なだけのバイト数を読み込めない場合にエラーを発生するのを抑制してます
この前にファイルサイズでバリデーションをかけるのもいいと思います
やろうと思えばどこまででもできてしまうのでもっと詳しく知りたい方はこちらも参考にしてみてください
https://qiita.com/mpyw/items/939964377766a54d4682

拡張子の判断が済んだら、DBにファイル名を保存していきましょう
DBに保存する際には
ファイル名を保存する方法と、
ファイルをそのままDBに保存する方法があります。
ファイルをそのままDBに保存する方法をとってしまうと重たくなってしまうので、ファイル名をDBに保存する方法をとります
詳しくは以下の記事を参考にしてください
https://qiita.com/7coco/items/1706a561131363d2c418

DBの接続は負荷がかかるため最低限にしたいのでここで接続しています
PDOには例外が発生するので、try, catchを使ってエラーハンドリングしています
ここら辺は以下の記事を参考にするといいと思います
https://qiita.com/mpyw/items/b00b72c5c95aac573b71
https://qiita.com/7968/items/6f089fec8dde676abb5b

ユーザーが入力した情報はエスケープする必要があるので、プリペアドステートメントを使ってください
注意して欲しいのはexecute()しているのはファイルを移動したのが確認できてからですかね。

move_uploaded_file()を使って、一時的に保存されたファイルを自分が保存したいディレクトリに移します

ユーザー任意のファイル名を使用するとバグを生む可能性があるのでuniqid(mt_rand(), true)を使ってランダムな文字列でファイル名を作成します
date()関数を使っているのはファイル名の重複を防止するためとデバッグ時の判別をしやすくするためです。
これには以前解説したことのあるmd5(uniqid(mt_rand(), true))を使ってもいいし、user_idをファイル名にすることもあります
そちらの解説はこちらから
https://qiita.com/satorunooshie/items/de494a25e7da4da16dcf

書き忘れていましたが移動先のディレクトリの権限を変更しておいてください!

chmod 755 ./photo

詳しくは以下のサイトを参考にしてください
https://qiita.com/shisama/items/5f4c4fa768642aad9e06
https://www.php.net/manual/ja/function.chmod.php

では続いて複数ファイルをアップロードする方法についてみていきましょう
今回は少し違うアプローチで崩していきましょう

example.php
<?php
$dir = __DIR__ . './photo';
const MAX_SIZE = 102400;
//php.iniのpost_max_sizeを超えたデータが送信されていないか確認
if (!checkPostMaxSize()) echo 'ファイルサイズは100MB以下にしてください';
if (isset($_FILES['uploadfile']) {
    for ($i = 0; $i < count($_FILES['uploadfile']['name']); $i++) {
        list($result, $ext, $error_msg) = checkFile($i);
        if ($result) {
            $name = $_FILES['uploadfile']['name'][$i];
            $tmp_name = $_FILES['uploadfile']['tmp_name'][$i];
            //"upfile_" + 現在のタイムスタンプ + 連番 + "_" + マイクロ秒 + 元のファイル名 + アクセス元IPアドレスに基づくMD5 + 拡張子
            $move_to = $dir . '/upfile_' . time() . $i . '_'
                . md5(microtime() . $name . $_SERVER['REMOTE_ADDR']) . '.' . $ext;
            if (!move_upload_file($tmp_name, $move_to)) {
                $error_msg[] = '画像のアップロードに失敗しました';
            } else {
                echo '<img src="' . $move_to . '>';
            }
        }
        if (count($error_msg) > 0) {
            foreach ($error_msg as $msg) {
                echo $msg;
            }
        }
    }
}

function checkFile($i)
{
    $error_msg = [];
    $ext = '';
    $size = $_FILES['uploadfile']['size'][$i];
    $error = $_FILES['uploadfile']['error'][$i];
    $img_type = $_FILES['uploadfile']['type'][$i];
    $tmp_name = $_FILES['uploadfile']['tmp_name'][$i];
    if ($error != UPLOAD_ERR_OK) {
        switch ($error) {
            case UPLOAD_ERR_NO_FILE:
                //アップロードされなかった場合はエラー処理はなし
                break;
            case UPLOAD_ERR_INI_SIZE:
            case UPLOAD_ERR_FORM_SIZE:
                $error_msg = 'ファイルサイズは100KB以下にしてください';
                break;
            default:
                $error_msg = 'アップロードエラーです';
        }
        return [false, $ext, $error_msg];
    } else {
        switch ($img_type) {
            case 'image/gif':
                $ext = 'gif';
                break;
            case 'image/jpeg':
            case 'image/pjpeg':
                $ext = 'jpg';
                break;
            case 'image/png':
            case 'image/x-png':
                $ext = 'png';
                break;
        }
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $finfo_type = $finfo->file($tmp_name);
        if ($size == 0) {
            $error_msg[] = 'ファイルが存在しないか空のファイルです';
        } elseif ($size > MAX_SIZE) {
            $error_msg[] = 'ファイルサイズは100KB以下にしてください';
        } elseif ($img_type != $finfo_type) {
            $error_msg[] = 'MIMEタイプが一致しません';
        } elseif ($ext != 'gif' && $ext != 'jpg' && $ext != 'png') {
            $error_msg[] = 'アップロード可能なファイルはgif, jpg, pngのみです';
        } else {
            return [true, $ext, $error_msg];
        }
    }
    return [false, $ext, $error_msg];
}
//php.iniのpost_max_sizeを超えたデータが送信されていないかチェックする関数
function checkPostMaxSize()
{
    $max_size = ini_get('post_max_size');
    //post_max_sizeにMが入っていた場合に整数にする
    $unit = substr($max_size, -1);
    switch ($unit) {
        case 'M':
            $multiple = 1024 ** 2; //pow(1024, 2);
            break;
        case 'K':
            $multiple = 1024;
            break;
        case 'G':
            $multiple = 1024 ** 3; //pow(1024, 3);
            break;
        default:
            $multiple = 1;
    }
    $max_size = substr($max_size, 0, strlen($max_size) - 1) * $multiple;
    //post_max_sizeを超えたデータがPOSTされたかをチェック
    if ($_SERVER['REQUEST_METHOD'] == 'POST'
        && $_SERVER['CONTENT_LENGTH'] > $max_size) {
        return false;
    }
    return true;
}

こんな感じですかねー
拡張子を判断するにはFileInfo拡張モジュールを使うこともできます
ファイル内の特定の位置からマジックバイトシーケンスを見つけることでファイルのMIMEタイプを推測しているみたいなので、確実な方法ではないようです。

ただしWindows環境のPHPではデフォルトで拡張モジュールが有効になっていないので、

php.ini
extension=php_fileinfo.dll #;extension=php_fileinfo.dll

のようにphp.iniを変更しApacheを再起動してください

補足ですが累乗の計算はpow()関数を使うことも**を使うこともできるみたいですー

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

[Laravel] プロジェクト開始までの忘備録

概要

Laravel プロジェクトを開始するまでのコマンドをまとめた個人メモ。

前提条件

プロジェクトの開始

Version6系を使用し、tasklistというプロジェクト名で作成する

$ composer create-project --prefer-dist laravel/laravel tasklist ^6.0

DB作成

Dockerを使用し、mysql コンテナを立ち上げる。

$ docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysql -d -p 3306:3306 mysql
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
4ded8e15debd        mysql               "docker-entrypoint.s…"   6 days ago          Up 6 days           0.0.0.0:3306->3306/tcp, 33060/tcp   mysql

プロセスで表示されたCONTAINER IDを使用し、コンテナに接続後DBの作成を行う
mysqlのパスワードはMYSQL_ROOT_PASSWORD引数で指定した値。

$ docker exec -it 4ded8e15debd86c73e637b0c612bf30f0cd9de0aec04b4c09a0f53a6fa16f35d bash
root@4ded8e15debd:/# mysql -u root -p -h 127.0.0.1
Enter password: mysql
mysql> DATABASE tasklist;
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| tasklist           |
+--------------------+
5 rows in set (0.03 sec)

DB 接続情報の定義

プロジェクト配下の .env ファイルで接続情報を定義する。
接続情報は個々の環境に合わせて変更すること。

$ cd tasklist 
$ vim .env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=tasklist
DB_USERNAME=root
DB_PASSWORD=mysql

DB接続の確認

Webアプリが正常にDB接続できているか確認を行う。
以下のように表示されている場合接続できている。

$ php artisan tinker
Psy Shell v0.10.4 (PHP 7.4.10 — cli) by Justin Hileman
>>> DB::reconnect();
=> Illuminate\Database\MySqlConnection {#3233}

タイムゾーンの変更

デフォルトではUTCのため、Asia/Tokyoに変更する。

$ vim config/app.php

'timezone' => 'Asia/Tokyo',

アプリの起動

内部サーバを起動させる。
デフォルトで用意されている welcome.blade.php のviewが表示される

$ php artisan serve --host=127.0.0.1 --port=8080 

備考

コンテナを停止する際は以下コマンドを実行すること。

$ docker stop { CONTAINER ID }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Laravel] プロジェクト開始までのコマンド忘備録

概要

Laravel プロジェクトを開始するまでのコマンドをまとめた個人メモ。

前提条件

プロジェクトの開始

Version6系を使用し、tasklistというプロジェクト名で作成する

$ composer create-project --prefer-dist laravel/laravel tasklist ^6.0

DB作成

Dockerを使用し、mysql コンテナを立ち上げる。

$ docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysql -d -p 3306:3306 mysql
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
4ded8e15debd        mysql               "docker-entrypoint.s…"   6 days ago          Up 6 days           0.0.0.0:3306->3306/tcp, 33060/tcp   mysql

プロセスで表示されたCONTAINER IDを使用し、コンテナに接続後DBの作成を行う
mysqlのパスワードはMYSQL_ROOT_PASSWORD引数で指定した値。

$ docker exec -it 4ded8e15debd86c73e637b0c612bf30f0cd9de0aec04b4c09a0f53a6fa16f35d bash
root@4ded8e15debd:/# mysql -u root -p -h 127.0.0.1
Enter password: mysql
mysql> CREATE DATABASE tasklist;
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| tasklist           |
+--------------------+
5 rows in set (0.03 sec)

DB 接続情報の定義

プロジェクト配下の .env ファイルで接続情報を定義する。
接続情報は個々の環境に合わせて変更すること。

$ cd tasklist 
$ vim .env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=tasklist
DB_USERNAME=root
DB_PASSWORD=mysql

DB接続の確認

Webアプリが正常にDB接続できているか確認を行う。
以下のように表示されている場合接続できている。

$ php artisan tinker
Psy Shell v0.10.4 (PHP 7.4.10 — cli) by Justin Hileman
>>> DB::reconnect();
=> Illuminate\Database\MySqlConnection {#3233}

タイムゾーンの変更

デフォルトではUTCのため、Asia/Tokyoに変更する。

$ vim config/app.php

'timezone' => 'Asia/Tokyo',

アプリの起動

内部サーバを起動させる。
デフォルトで用意されている welcome.blade.php のviewが表示される

$ php artisan serve --host=127.0.0.1 --port=8080 

備考

コンテナを停止する際は以下コマンドを実行すること。

$ docker stop { CONTAINER ID }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PHP] selfとstaticの違いメモ

要点

self: 定義時のクラスを指す
static: 実行時のクラスを指す

class Foo
{
    public function helloGateway()
    {
        self::hello();
    }

    public static function hello()
    {
        echo __CLASS__ . 'hello' . PHP_EOL;
    }
}

class Bar extends Foo {
    public static function hello()
    {
        echo __CLASS__ . 'hello' . PHP_EOL;
    }
}

$bar = new Bar();
echo $bar->helloGateway();
//この場合はFoohelloが出力される
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

勉強メモ5_どこかの記事を元にgatling構築

gatlingを使うため、ApacheとPHP導入

以下↓↓の記事を参考
Apache導入
https://qiita.com/sango/items/b045a1da17606eda6f82
PHP導入
https://qiita.com/sango/items/a08c5b04df7125aaaad3
https://techacademy.jp/magazine/23054

---------------Apache導入↓↓-------------------------
#ルートに変更
su - root
#apacheインストール
yum -y install httpd
#apache起動
systemctl start httpd
#apacheが動いてるかブラウザで確認
http://127.0.0.1/
---------------PHP導入↓↓-------------------------
#PHPインストール
yum -y install php
#phpファイル作成するため、移動
cd /var/www/html/

/var/www/html/にPHPファイルを3つ作成

api1.php
<?php
// 連想配列用意
$array = [
    'tokyo' => [
        '品川',
        '五反田',
        '三軒茶屋',
        '四谷'
    ]
];

// Origin null is not allowed by Access-Control-Allow-Origin.とかのエラー回避の 為、ヘッダー付与
header("Access-Control-Allow-Origin: *");

#echo json_encode($array);
echo json_encode($array,JSON_UNESCAPED_UNICODE);
?>
api2.php
<?php
// 連想配列用意
$array = [
    'kanagawa' => [
        '横浜',
        '相模原',
        '湘南',
        '鎌倉'
    ]
];

// Origin null is not allowed by Access-Control-Allow-Origin.とかのエラー回避の 為、ヘッダー付与
header("Access-Control-Allow-Origin: *");

#echo json_encode($array);
echo json_encode($array,JSON_UNESCAPED_UNICODE);
?>
api3.php
?php
// 連想配列用意
$array = [
    'saitama' => [
        '所沢',
        '狭山',
        '川口',
        '浦和',
        '小手指',
        '飯能'
    ]
];

// Origin null is not allowed by Access-Control-Allow-Origin.とかのエラー回避の 為、ヘッダー付与
header("Access-Control-Allow-Origin: *");

#echo json_encode($array);
echo json_encode($array,JSON_UNESCAPED_UNICODE);
?>

PHP実行

#まずはApache再起動
systemctl restart httpd
#ブラウザでアクセス(JSON形式で市の名称がでればOK)
http://127.0.0.1/api1.php
http://127.0.0.1/api2.php
http://127.0.0.1/api3.php

gatling 構築

以下の記事を参考に構築
https://medium.com/eureka-engineering/gatring-stress-test-14ac5efdfcbc

#rootフォルダ移動
su - root
cd /root
#gatlingのライブラリーをダウンロード
curl -O https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/3.3.1/gatling-charts-highcharts-bundle-3.3.1-bundle.zip
#gatlingのライブラリーを解凍
unzip gatling-charts-highcharts-bundle-3.3.1-bundle.zip
#javaがインストールされてるか確認(java8や11がないと動かないらしい)
java -version
#gatlingの実行ソース作成(BasicSimulation.scalaのベースの実行ファイルをコピー)
cd gatling-charts-highcharts-bundle-3.3.1/user-files/simulations/
mkdir api
cp -p computerdatabase/BasicSimulation.scala api/ApiSimulation.scala

上記でコピーした(ApiSimulation.scala)を作成

package api //パッケージ名指定

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration.
_
class ApiSimulation extends Simulation {

val httpProtocol = http
 .baseUrl("http://127.0.0.1")//ベースURL
 .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
 .doNotTrackHeader("1")
 .acceptLanguageHeader("en-US,en;q=0.5")
 .acceptEncodingHeader("gzip, deflate")
 .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")

//7秒おきに /api1.php /api2.php /api3.phpにGETするだけの簡単なテスト
val scn = scenario("api")
 .exec(
 http("request_1")
 .get("/api1.php")//作成したPHPファイル指定
 )
 .pause(7)
 .exec(
 http("request_2")
 .get("/api2.php")//作成したPHPファイル指定
 )
 .pause(7)
 .exec(
 http("request_3")
 .get("/api3.php")//作成したPHPファイル指定
 )
setUp(scn.inject(atOnceUsers(1)).protocols(httpProtocol)) //1Userで並列実行
}

gatling実行

#gatling実行シェルファイルに移動
cd /root/gatling-charts-highcharts-bundle-3.3.1/bin/
#gatling実行
./gatling.sh
GATLING_HOME is set to /root/gatling-charts-highcharts-bundle-3.3.1
OpenJDK 64-Bit Server VM warning: If the number of processors is expected to increase from one, then you should configure the number of parallel GC threads appropriately using -XX:ParallelGCThreads=N
Choose a simulation number:
     [0] api.ApiSimulation
     [1] computerdatabase.BasicSimulation
     [2] computerdatabase.advanced.AdvancedSimulationStep01
     [3] computerdatabase.advanced.AdvancedSimulationStep02
     [4] computerdatabase.advanced.AdvancedSimulationStep03
     [5] computerdatabase.advanced.AdvancedSimulationStep04
     [6] computerdatabase.advanced.AdvancedSimulationStep05
0 0を選択する
Select run description (optional)
api: ←api:って入力する
Simulation api.ApiSimulation started...

================================================================================
2020-09-27 16:20:23                                           5s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=1      KO=0     )
> request_1                                                (OK=1      KO=0     )

---- api -----------------------------------------------------------------------
[--------------------------------------------------------------------------]  0%
          waiting: 0      / active: 1      / done: 0
================================================================================


================================================================================
2020-09-27 16:20:28                                          10s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=2      KO=0     )
> request_1                                                (OK=1      KO=0     )
> request_2                                                (OK=1      KO=0     )

---- api -----------------------------------------------------------------------
[--------------------------------------------------------------------------]  0%
          waiting: 0      / active: 1      / done: 0
================================================================================


================================================================================
2020-09-27 16:20:32                                          14s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=3      KO=0     )
> request_1                                                (OK=1      KO=0     )
> request_2                                                (OK=1      KO=0     )
> request_3                                                (OK=1      KO=0     )

---- api -----------------------------------------------------------------------
[##########################################################################]100%
          waiting: 0      / active: 0      / done: 1
================================================================================

Simulation api.ApiSimulation completed in 14 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...

================================================================================
---- Global Information --------------------------------------------------------
> request count                                          3 (OK=3      KO=0     )
> min response time                                      4 (OK=4      KO=-     )
> max response time                                     60 (OK=60     KO=-     )
> mean response time                                    26 (OK=26     KO=-     )
> std deviation                                         24 (OK=24     KO=-     )
> response time 50th percentile                         14 (OK=14     KO=-     )
> response time 75th percentile                         37 (OK=37     KO=-     )
> response time 95th percentile                         55 (OK=55     KO=-     )
> response time 99th percentile                         59 (OK=59     KO=-     )
> mean requests/sec                                    0.2 (OK=0.2    KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                             3 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Reports generated in 0s.
Please open the following file: /root/gatling-charts-highcharts-bundle-3.3.1/results/apisimulation-20200927072016312/index.html

上記の最後に「file: /root/gatling-charts-highcharts-bundle-3.3.1/results/apisimulation-20200927072016312/index.html」
と記載されているので、ブラウザでアクセスしてみる。
アクセスすると、下記みたいなのが表示されたよ。
キャプチャ.JPG

一旦、ここまでで終了

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

勉強メモ5_アップされてる記事を元にgatling構築

gatlingを使うため、ApacheとPHP導入

以下↓↓の記事を参考
Apache導入
https://qiita.com/sango/items/b045a1da17606eda6f82
PHP導入
https://qiita.com/sango/items/a08c5b04df7125aaaad3
https://techacademy.jp/magazine/23054

---------------Apache導入↓↓-------------------------
#ルートに変更
su - root
#apacheインストール
yum -y install httpd
#apache起動
systemctl start httpd
#apacheが動いてるかブラウザで確認
http://127.0.0.1/
---------------PHP導入↓↓-------------------------
#PHPインストール
yum -y install php
#phpファイル作成するため、移動
cd /var/www/html/

/var/www/html/にPHPファイルを3つ作成

api1.php
<?php
// 連想配列用意
$array = [
    'tokyo' => [
        '品川',
        '五反田',
        '三軒茶屋',
        '四谷'
    ]
];

// Origin null is not allowed by Access-Control-Allow-Origin.とかのエラー回避の 為、ヘッダー付与
header("Access-Control-Allow-Origin: *");

#echo json_encode($array);
echo json_encode($array,JSON_UNESCAPED_UNICODE);
?>
api2.php
<?php
// 連想配列用意
$array = [
    'kanagawa' => [
        '横浜',
        '相模原',
        '湘南',
        '鎌倉'
    ]
];

// Origin null is not allowed by Access-Control-Allow-Origin.とかのエラー回避の 為、ヘッダー付与
header("Access-Control-Allow-Origin: *");

#echo json_encode($array);
echo json_encode($array,JSON_UNESCAPED_UNICODE);
?>
api3.php
?php
// 連想配列用意
$array = [
    'saitama' => [
        '所沢',
        '狭山',
        '川口',
        '浦和',
        '小手指',
        '飯能'
    ]
];

// Origin null is not allowed by Access-Control-Allow-Origin.とかのエラー回避の 為、ヘッダー付与
header("Access-Control-Allow-Origin: *");

#echo json_encode($array);
echo json_encode($array,JSON_UNESCAPED_UNICODE);
?>

PHP実行

#まずはApache再起動
systemctl restart httpd
#ブラウザでアクセス(JSON形式で市の名称がでればOK)
http://127.0.0.1/api1.php
http://127.0.0.1/api2.php
http://127.0.0.1/api3.php

gatling 構築

以下の記事を参考に構築
https://medium.com/eureka-engineering/gatring-stress-test-14ac5efdfcbc

#rootフォルダ移動
su - root
cd /root
#gatlingのライブラリーをダウンロード
curl -O https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/3.3.1/gatling-charts-highcharts-bundle-3.3.1-bundle.zip
#gatlingのライブラリーを解凍
unzip gatling-charts-highcharts-bundle-3.3.1-bundle.zip
#javaがインストールされてるか確認(java8や11がないと動かないらしい)
java -version
#gatlingの実行ソース作成(BasicSimulation.scalaのベースの実行ファイルをコピー)
cd gatling-charts-highcharts-bundle-3.3.1/user-files/simulations/
mkdir api
cp -p computerdatabase/BasicSimulation.scala api/ApiSimulation.scala

上記でコピーした(ApiSimulation.scala)を作成

package api //パッケージ名指定

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration.
_
class ApiSimulation extends Simulation {

val httpProtocol = http
 .baseUrl("http://127.0.0.1")//ベースURL
 .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
 .doNotTrackHeader("1")
 .acceptLanguageHeader("en-US,en;q=0.5")
 .acceptEncodingHeader("gzip, deflate")
 .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")

//7秒おきに /api1.php /api2.php /api3.phpにGETするだけの簡単なテスト
val scn = scenario("api")
 .exec(
 http("request_1")
 .get("/api1.php")//作成したPHPファイル指定
 )
 .pause(7)
 .exec(
 http("request_2")
 .get("/api2.php")//作成したPHPファイル指定
 )
 .pause(7)
 .exec(
 http("request_3")
 .get("/api3.php")//作成したPHPファイル指定
 )
setUp(scn.inject(atOnceUsers(1)).protocols(httpProtocol)) //1Userで並列実行
}

gatling実行

#gatling実行シェルファイルに移動
cd /root/gatling-charts-highcharts-bundle-3.3.1/bin/
#gatling実行
./gatling.sh
GATLING_HOME is set to /root/gatling-charts-highcharts-bundle-3.3.1
OpenJDK 64-Bit Server VM warning: If the number of processors is expected to increase from one, then you should configure the number of parallel GC threads appropriately using -XX:ParallelGCThreads=N
Choose a simulation number:
     [0] api.ApiSimulation
     [1] computerdatabase.BasicSimulation
     [2] computerdatabase.advanced.AdvancedSimulationStep01
     [3] computerdatabase.advanced.AdvancedSimulationStep02
     [4] computerdatabase.advanced.AdvancedSimulationStep03
     [5] computerdatabase.advanced.AdvancedSimulationStep04
     [6] computerdatabase.advanced.AdvancedSimulationStep05
0 0を選択する
Select run description (optional)
api: ←api:って入力する
Simulation api.ApiSimulation started...

================================================================================
2020-09-27 16:20:23                                           5s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=1      KO=0     )
> request_1                                                (OK=1      KO=0     )

---- api -----------------------------------------------------------------------
[--------------------------------------------------------------------------]  0%
          waiting: 0      / active: 1      / done: 0
================================================================================


================================================================================
2020-09-27 16:20:28                                          10s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=2      KO=0     )
> request_1                                                (OK=1      KO=0     )
> request_2                                                (OK=1      KO=0     )

---- api -----------------------------------------------------------------------
[--------------------------------------------------------------------------]  0%
          waiting: 0      / active: 1      / done: 0
================================================================================


================================================================================
2020-09-27 16:20:32                                          14s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=3      KO=0     )
> request_1                                                (OK=1      KO=0     )
> request_2                                                (OK=1      KO=0     )
> request_3                                                (OK=1      KO=0     )

---- api -----------------------------------------------------------------------
[##########################################################################]100%
          waiting: 0      / active: 0      / done: 1
================================================================================

Simulation api.ApiSimulation completed in 14 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...

================================================================================
---- Global Information --------------------------------------------------------
> request count                                          3 (OK=3      KO=0     )
> min response time                                      4 (OK=4      KO=-     )
> max response time                                     60 (OK=60     KO=-     )
> mean response time                                    26 (OK=26     KO=-     )
> std deviation                                         24 (OK=24     KO=-     )
> response time 50th percentile                         14 (OK=14     KO=-     )
> response time 75th percentile                         37 (OK=37     KO=-     )
> response time 95th percentile                         55 (OK=55     KO=-     )
> response time 99th percentile                         59 (OK=59     KO=-     )
> mean requests/sec                                    0.2 (OK=0.2    KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                             3 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Reports generated in 0s.
Please open the following file: /root/gatling-charts-highcharts-bundle-3.3.1/results/apisimulation-20200927072016312/index.html

上記の最後に「file: /root/gatling-charts-highcharts-bundle-3.3.1/results/apisimulation-20200927072016312/index.html」
と記載されているので、ブラウザでアクセスしてみる。
アクセスすると、下記みたいなのが表示されたよ。
キャプチャ.JPG

一旦、ここまでで終了

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

【PHP】endroid/qr-codeを使ってQRコード画像を出力してみた話

はじめに

タイトルの通りですが、
endroid/qr-codeを使ってQRコード画像を出力してみました。

以下で作成した環境を引き続き利用していますので、
今回もコマンドラインから実行となります。

本編

環境構築

【PHP】TCPDF + FPDIでPDFを出力してみた話 > Dockerを元に
今回新たに必要となった、gd拡張モジュールの導入設定を追加します。

# GD
RUN apt-get install -y libjpeg-dev libpng-dev libfreetype6-dev
RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
    docker-php-ext-install gd

追加後、再度ビルドします。

# docker-compose build --no-cache

Composer

Composerを使って、"endroid/qr-code"をインストールしていきましょう。

# cd /home/work/src/
# composer require endroid/qr-code
Using version ^3.9 for endroid/qr-code
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 15 installs, 0 updates, 0 removals
  - Installing myclabs/php-enum (1.7.6): Downloading (100%)         
  - Installing symfony/polyfill-ctype (v1.18.1): Downloading (100%)         
  - Installing symfony/polyfill-php80 (v1.18.1): Downloading (100%)         
  - Installing symfony/polyfill-mbstring (v1.18.1): Downloading (100%)         
  - Installing symfony/polyfill-intl-normalizer (v1.18.1): Downloading (100%)         
  - Installing symfony/polyfill-intl-grapheme (v1.18.1): Downloading (100%)         
  - Installing symfony/string (v5.1.5): Downloading (100%)         
  - Installing symfony/property-info (v5.1.5): Downloading (100%)         
  - Installing symfony/property-access (v5.1.5): Downloading (100%)         
  - Installing symfony/deprecation-contracts (v2.2.0): Downloading (100%)         
  - Installing symfony/options-resolver (v5.1.5): Downloading (100%)         
  - Installing khanamiryan/qrcode-detector-decoder (1.0.3): Downloading (100%)         
  - Installing dasprid/enum (1.0.2): Downloading (100%)         
  - Installing bacon/bacon-qr-code (2.0.2): Downloading (100%)         
  - Installing endroid/qr-code (3.9.1): Downloading (100%)         
symfony/polyfill-intl-normalizer suggests installing ext-intl (For best performance)
symfony/polyfill-intl-grapheme suggests installing ext-intl (For best performance)
symfony/property-info suggests installing psr/cache-implementation (To cache results)
symfony/property-info suggests installing symfony/doctrine-bridge (To use Doctrine metadata)
symfony/property-info suggests installing phpdocumentor/reflection-docblock (To use the PHPDoc)
symfony/property-info suggests installing symfony/serializer (To use Serializer metadata)
symfony/property-access suggests installing psr/cache-implementation (To cache access methods.)
bacon/bacon-qr-code suggests installing ext-imagick (to generate QR code images)
endroid/qr-code suggests installing roave/security-advisories (Avoids installation of package versions with vulnerabilities)
endroid/qr-code suggests installing symfony/security-checker (Checks your composer.lock for vulnerabilities)
endroid/qr-code suggests installing setasign/fpdf (Required to use the FPDF writer.)
Writing lock file
Generating autoload files
12 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

実装

”Google.com”へのリンクをQRコードにしてみます。

ファイル構成

# cd /home/work/

# ls /home/work/output/img/qr/qrYYYYmmdd-HHiiss #PDF出力ファイル
# ls /home/work/src/resources/img/png/qr-logo.png #QRコード中心に表示されるロゴマーク
# ls /home/work/bin/make-qr.php #コマンドラインで実行するソース
# ls /home/work/src/classes/MyQrCode.php # TCPDF利用クラス
# ls /home/work/src/classes/QrCode/FontColorWithAlpha.php.php # フォントカラークラス(割愛)

ソース

/home/work/bin/make-qr.php
<?php

require_once __DIR__ . '/../src/bootstrap.php';

use Classes\MyQrCode;
use Classes\QrCode\FontColorWithAlpha;

// QRコードの画像ファイルを出力
$myQrCode = new MyQrCode('https://www.google.com/');
$myQrCode->setForegroundColor(new FontColorWithAlpha(255, 0, 0));
$myQrCode->setBackgroundColor(new FontColorWithAlpha(255, 255, 0));
$myQrCode->outputQrCodeFile();
/home/work/src/classes/MyQrCode.php
<?php

namespace Classes;

use Endroid\QrCode\LabelAlignment;
use Endroid\QrCode\QrCode;
use Classes\QrCode\FontColorWithAlpha;

class MyQrCode
{
    //デフォルト設定
    private $size = 300;//QRコードサイズ
    private $margin = 10;//余白

    //バーコードの色(デフォルト:黒)
    private $foregroundColor = [
        'r' => 0,
        'g' => 0,
        'b' => 0,
        'a' => 0
    ];
    //背景色(デフォルト:白)

    private $backgroundColor = [
        'r' => 255,
        'g' => 255,
        'b' => 255,
        'a' => 0
    ];

    // 文字エンコーディング
    const ENCODING = 'utf-8';

    // ラベル
    const LABEL_TEXT = 'QRコードを読み込む';
    const LABEL_FONT_SIZE = 15;

    // // 出力ファイル
    const OUTPUT_FILE_DIR = __DIR__.'/../../output/img/qr/';
    const OUTPUT_FILE_NAME = 'qr';

    // ロゴ
    const LOGO_IMG = __DIR__.'/../resources/img/png/qr-logo.png';
    const LOGO_SIZE_WIDTH = 50; //幅
    const LOGO_SIZE_HEIGHT = 50;//高さ

    // フォントファイル
    const FONT_FILE_NAME = __DIR__.'/../resources/font/ipam.ttf';

    /**
     * コンストラクタ
     */
    public function __construct(string $_text)
    {
        $this->qrCode = new QrCode($_text);
    }

    /**
     * QRCodeの画像出力前の準備処理
     */
    private function prepareOutput()
    {
        // 可変パラメータ
        $this->qrCode->setSize($this->size); // QRコードのサイズ
        $this->qrCode->setMargin($this->margin); //QRコードと画像の余白
        $this->qrCode->setForegroundColor($this->foregroundColor);//QRコードの色
        $this->qrCode->setBackgroundColor($this->backgroundColor);//背景色
        // 固定パラメータ
        $this->qrCode->setEncoding(self::ENCODING);//文字エンコード
        //QRコードの下に表示されるラベル
        $this->qrCode->setLabel(
            self::LABEL_TEXT,
            self::LABEL_FONT_SIZE,
            self::FONT_FILE_NAME,
            LabelAlignment::CENTER()
        );
        //QRコードの中央に表示されるロゴ
        //ロゴのファイル
        $this->qrCode->setLogoPath(self::LOGO_IMG);
        //ロゴのサイズ
        $this->qrCode->setLogoSize(self::LOGO_SIZE_WIDTH, self::LOGO_SIZE_HEIGHT);
    }

    /**
     * QRCodeの画像出力
     */
    public function outputQrCodeFile()
    {
        // 出力準備
        $this->prepareOutput();
        // ファイル出力
        $this->qrCode->writeFile($this->getOutputFilename());
    }

    /**
     * 出力するファイル名を取得する
     * @return string ファイル名
     */
    private function getOutputFilename()
    {
        return self::OUTPUT_FILE_DIR . self::OUTPUT_FILE_NAME . date("Ymd-His"). '.png';
    }

    /**
     *
     * QRコードの色を設定する
     * @param $_fontColor 文字色クラス
     */
    public function setForegroundColor(FontColorWithAlpha $_fontColor)
    {
        $this->foregroundColor = $_fontColor->toArray();
    }

    /**
     *
     * 背景色の色を設定する
     * @param $_fontColor 文字色クラス
     */
    public function setBackgroundColor(FontColorWithAlpha $_fontColor)
    {
        $this->backgroundColor = $_fontColor->toArray();
    }

    /**
     *
     * QRコードのサイズを設定する
     * @param $_fontColor 文字色クラス
     */
    public function setSize(int $_size)
    {
        $this->size = $_size;
    }

    /**
     *
     * QRコードの余白を設定する
     * @param $_fontColor 文字色クラス
     */
    public function setMargin(int $_margin)
    {
        $this->margin = $_margin;
    }
}

実行コマンド

php /home/work/bin/make-qr.php

実行結果

以下のようにファイルが出力されます。
qr20200927-154038.png

終わりに

QRコードを作成してくれるサービスは既にいくつもあるかと思いますが、
大量に必要な場合や動的に作成したい場合は、簡単に自前で用意できそうですね。

参考

  1. GitHub - endroid/qr-code
  2. Qiita -【PHP】endroid/qr-codeを使ってQRコードを生成してみる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php・可変変数に配列を渡す

環境
初書:2020/09/27
PC:macOS 10.15.7
php 7.4.10

前置き

PHPで可変変数を扱う時があったのだが、その時にかなり手こずったのでメモ。

前提

可変変数の知識

コード(失敗例)

index.php
class cls {
    public string $str1;
    public string $str2;
    public function __construct(){
        $this->str1 = "aa";
        $this->str2 = "bb";
    }
}

$ouc = new cls();

とりあえずクラスを用意して、インスタンスした。
そしてこの後、str変数にアクセスしたいのだが、この時に少し変わったことをする。

index.php
$arr[] = "str1";
$arr[] = "str2";

$ouc->$arr[0] = "cc";

何をしたいかというと、$arr[0] = "str1"、つまり$ouc->str1 = "cc";を行おうとした。
一見問題なく実行できそうだが、実はnoticeが発生する。

PHP Notice: Array to string conversion in php

これは配列を文字列として扱おうとした際に起こるエラーである。
だが、パッと見では特に不具合がある場所はない。

エラーの原因

原因自体は単純で、構文の解析方法が思っているのと違うということ。
PHP7以降の構文解析は、

$ouc->$arr[0] = "cc";
      ¯¯¯¯           // 1
¯¯¯¯¯¯¯¯¯¯¯¯¯        // 2

という順番になる。
つまり、$arr (=Array。配列なので)を先に返し、
$ouc->Array[0]ccを代入しようとしたのである。(そして実際に代入した)
そのため、var_dump($ouc)を行うと、

object(cls)#1 (3) {
  ["str1"]=>
  string(2) "aa"
  ["str2"]=>
  string(2) "bb"
  ["Array"]=>
  array(1) {
    [0]=>
    string(2) "cc"
  }
}

という形になる。

対処法

可変変数の別の記述方法である

$ouc->{$arr[0]} = "cc";

という風に、全体を{}で括って正しい順番で解析してもらうか、一旦別の変数に置く。

終わりに

このバグに遭遇した場合、(構文解析の順番を知らない場合は)エラー文をみてもこれが原因だということに気付けない上に、エラー箇所より前の時点でログを出しても特に見つからない1ので(もちろん、あとでvar_dumpするとすぐにわかるのだが)、頭の片隅に入れておいてもいいかもしれない。

参考サイト


  1. $arr[0]単体でechoしても、clsをvar_dumpしても気付かないし、issetしても…falseになるから気付くかもしれない。自分はエラーに気を取られて戻り値が来ている事に気付いてなかった。 

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

[ WordPress ] プラグイン Custom Field Suite の関連ポストの情報を、投稿ページに表示させる

WordPress のプラグイン Custom Field Suite には「関連ポスト」というフィールドがあります。今回、記事に投稿者を紐づけたいと思いましたが、WordPress にそもそも備わっている author は以下の理由から避けました。

【避けた理由1】セキュリティ

何も対策せず投稿者を作ると、下記のURLが生成されます。

https://example.com/author/tanaka/
https://example.com/author/sato/

authorのユーザー名は、そのままWordPressのログインIDとなります。わざわざ晒したくないところです。

【避けた理由2】メールアドレスが必須

ログインIDとなるということは、大抵のサービスと同様、ユーザーを作る際にメールアドレスが求められます。今回、記事に紐付けたい投稿者は50名を超えていました。

Custom Field Suite の関連ポストの情報を、投稿ページに表示させるソースコード

以上の2つの理由から、まずカスタム投稿タイプの機能を使って「投稿者」を作り、投稿者名や所属、略歴などを入力するようにしました。そして、その投稿者の情報を、投稿に紐づけるため、 Custom Field Suite で「関連ポスト」のフィールドを投稿に用意し、投稿者を紐付けました。

その関連ポストの情報を投稿に表示させるソースコードが下記です。特に get_post_meta や wp_get_attachment_image が工夫した点です。

<?php
$values = CFS()->get('kanrenpost');
foreach ($values as $post_id):
setup_postdata( $post_id );
  $rslink = get_permalink($post_id);
  $rstitle = get_the_title($post_id);
  $rsoriginalname = get_post_meta($post_id,'kanrenpost__originalname',true);
  $rsoriginalimage = wp_get_attachment_image(get_post_meta($post_id,'kanrenpost__originalimage',true));
?>
    <a href="<?php echo $rslink ?>">
        <?php echo $rstitle ?>
        <?php echo $rsoriginalname ?>
        <?php echo $rsoriginalimage ?>
    </a>
<?php endforeach; ?>

参考URL

WordPress:Custom Field Suiteの使い方からカスタマイズまで | 新宿のホームページ制作会社 ITTI(イッティ)
関数リファレンス/get post meta - WordPress Codex 日本語版

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

【Laravel】Collectionのrejectメソッド

はじめに

今回もLaravelのCollectionのメソッドについてまとめたいと思います。

rejectメソッドで条件に合わないものを除外してデータ取得する

Collectionの中から条件に合わないものを除外してデータ取得することができます。

$collection = collect([
    ['id' => 1, 'name' => '山田', 'age' => 18],
    ['id' => 2, 'name' => '佐藤', 'age' => 38],
    ['id' => 3, 'name' => '小林', 'age' => 25],
    ['id' => 4, 'name' => '鈴木', 'age' => 34],
    ['id' => 5, 'name' => '田中', 'age' => 29],
]);

$filtered = $collection->reject(function($values, $key){

    return ($values['age'] < 30);

});
print_r($filtered->toArray());

実行結果は下記になります。

Array
(
    [1] => Array
        (
            [id] => 2
            [name] => 佐藤
            [age] => 38
        )

    [2] => Array
        (
            [id] => 4
            [name] => 鈴木
            [age] => 34
        )

)

※条件式がtrueになるものは除外さます。

おわりに

いかがでしたでしょうか。
rejectメソッドで条件に当てはまるものを除外できるので試してみてください。

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

PHP Laravel 6 画像のアップロード

前提条件

画像ファイルの保存先 = storage/app/public
データベースにはファイル名のみを保存。
ファイル名にはランダムな名前が自動で割り当てられる。

フォームの作成

フォームタグの記述

formタグに enctype='multipart/form-data' を記述しないとエラーを吐きます。
これを記述することで複数類(jpeg、png等)のデータ形式を扱う事でできるようになります。(jpeg、png等)

create.blade.php
<form method="POST" action="{{ route('recommends.store') }}" enctype='multipart/form-data'>
@csrf

</form>

コントローラーの設定

imgがない場合に$path = $request->file('img')->store('public/img');でエラーを吐くためif文で分岐させました。

recommendMovies/app/Http/Controllers/RecommendMovieController.php
public function store(Request $request)
    {
      if ($request->img === null) {
        RecommendMovie::create([
          'name' => $request->name,
          'description' => $request->description,
          'impression' => $request->impression,
        ]);
      }else {
        $path = $request->file('img')->store('public/img');
        RecommendMovie::create([
          'name' => $request->name,
          'description' => $request->description,
          'impression' => $request->impression,
          'img' => basename($path)
        ]);
      }
      return redirect('/recommends')->with('message', '作成しました');
    }

viewの設定

シンボリックリンクを使用します。

php artisan storage:link

storage\imgが作成され、この中にアップロードした画像が保存されます。
Screen Shot 2020-09-27 at 12.03.56.png

以下の記述でブラウザに表示させることができます。

recommendMovies/app/Http/Controllers/index.php
<img src="{{ asset('/storage/img/'.$recommend->img) }}">
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php復習、オブジェクト指向編(個人学習備忘録)

はじめに

筆者はずぶずぶの素人です。少しlaravelが書けるレベルです。
書けているけど僕自身がフレームワークに使われています。
laravelを使っていく打ちにもっとlaravelを理解したいと思いました。
フレームワークの本質を理解するにはphpを基礎力を向上させるほかないと考えphpの復習+脱初心者を目指します。

class

オブジェクト指向の構成要素の1つ。classはオブジェクトの設計書のようなもの
userの名前をコンストラクトし、user名を出力する簡単なクラスを作成

class Animal {
    p $name;

    //$user_nameの初期化
    function __construct($name) {
        $this->name = $name;
    }
    //getterの役割
    public function getName() {
        return $this->name; 
    }
}
$nnimal = new Animal('dog');
echo $user->getName();

-------------------------
結果
dog

継承を行う。フレームワークにおいても、Contorollerなどのclassをextendして使用する。

class Dog extends Animal {

    public function call() {
        echo 'wan!';
    }
}
$dog = new Dog('wanchan');
echo $dog->getName();
echo '<bar>';
echo $dog->call();
-------------------------------
結果
wanchan<bar>wan!


アクセス修飾子
public
どこからでもアクセス可能
private
どこからでもアクセス可能です。アクセス修飾子がない場合は、publicを指定したものと同じになります。
protected
そのクラス自身と継承クラスからアクセス可能です。つまり非公開ですが、継承は可能となります。


ポリモーフィズム

ポリモーフィズムとは同名のメソッドで異なる挙動を実現することをいいます。ポリモーフィズムのメソッドは、同じ目的の機能に同名のメソッドを割り当てる ことができるということです。
同名のメソッドで異なる動きを実現することを言います。

<?php
class Human {
    protected $name;
    protected $age;
    function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }

    protected function getInfo() {}
}

class HumanInfo extends Human {
    //オーバーライド
    public function getInfo() {
        echo $this->name.'は年齢が'.$this->age.'です';
    }
}

class Feature extends Human {
    protected $job;
    function __construct($name, $age, $job) {
        $this->name = $name;
        $this->age = $age;
        $this->job = $job;
    }
     //オーバーライド
     public function getInfo() {
        echo $this->name.'は年齢が'.$this->age.'です'.'職業は'.$this->job.'です';
     }

}
$user_info = new HumanInfo('たく', 29);
$user_info->getInfo();
$user_feature = new Feature('まみ', 30, '料理人');
$user_feature->getInfo();
------------------------------
結果:
たくは年齢が29です
まみは年齢が30です職業は料理人です

ポリモーフィズムのメリットは、同じ目的の機能に同じ名前のメソッドを指定できる点で、関連するクラスを利用する場合に、異なるメソッド名を覚える必要がなくなります。
但し、この例のように単なる継承とオーバーライドだけでは、それぞれのサブクラスが同名のメソッドを持つことが保証されないので、「抽象メソッド」という仕組みを利用する必要があります。

共通のメソッドを持たせる点であるがそもそも強制力はない。なのでabstractを用いて、表現をする。

抽象class

abstruct(抽象)をclassの前につけることで、抽象クラスを表現でき、またメソッドにもabstructを付与すると定義しているメソッドを使用を強制する。

<?php
//抽象クラス//設定するメソッドを強制できる。
abstract class Human {
    protected $name;
    protected $age;
    function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }
    //抽象メソッドになる。abstractを定義するとメソッド内容はかけない。
    abstract protected function getInfo();
}

class HumanInfo extends Human {
    //オーバーライド
    // public function getInfo() {
    //     echo $this->name.'は年齢が'.$this->age.'です';
    // }
}
--------------------------------------------------------
 Class HumanInfo contains 1 abstract method and must therefore be declared abstract or implement the remaining methods

コメントするとメソッド入れなさいと怒られちゃいました。

そもそも柔軟にメソッドだけ、強制的に共通的に使用したい場合はinterfaceを使用する。


Interface

PHP では1つのサブクラスが同時に複数のスーパークラスを継承すること(多重継承)ができません。
これはポリモーフィズムを実現したい場合、すべての機能(メソッド)をひとつの抽象クラスに含めなければいけないことを意味し、サブクラスがスーパークラスの特定の機能(メソッド)を必要としない場合でも、それらの機能をオーバーライドしなければならなくなるためコードが冗長になってしまいます。
PHP ではこのような場合、インターフェースを利用します。

このinterfaceを使用することで共通のメソッドを共有できる様になります。メソッドはそのままに振る舞いは意図して変化できる。
interfaceをするにはinterfaceの宣言とクラスにimplementsを付与すること。
またinterfaceの利点は複数のinterfaceを使用できること。

<?php
interface HumanInterface {
    function getInfo();
}

interface JobInterface{
    function setJob($params);
}
//抽象クラス//設定するメソッドを強制できる。
abstract class Human {
    protected $name;
    protected $age;
    function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }

}

class HumanInfo extends Human implements HumanInterface {
    //オーバーライド
    public function getInfo() {
        echo $this->name.'は年齢が'.$this->age.'です';
    }
}

class Feature extends Human implements HumanInterface, JobInterface {
    protected $job = [];
    function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }
    public function getInfo() {
        echo $this->name.'は年齢が'.$this->age.'です'.'職業は'.$this->job.'です';
    }
    public function setJob($params) {
        return $this->job = $params;
    }

}
$user_info = new HumanInfo('たく', 29);
$user_info->getInfo();

$user_feature = new Feature('まみ', 30);
$user_feature->setJob('料理人');
var_dump($user_feature->job);
$user_feature->getInfo();
?>
________________________________________
結果
たくは年齢が29です
string(9) "料理人"
まみは年齢が30です職業は料理人です

2つのインターフェイスがあり、implementsを定義することで、インターフェイスを使用できる。
もちろんimplementesで定義した、インターフェイスのメソッドを使用しないとエラーになる。


トレイト

コードの再利用性を高めるための機能
定義方法はtraitを宣言し、メソッドを記述していく。
classなどに組み込む際にはuseで宣言する。
traitは任意のクラスの機能(メソッド)を拡張できる仕組。そのため、traitに定義するメソッドは複数のクラスで利用されることを想定して、できるだけ汎用性が高い処理を定義するべきです。

複数のクラスで同様の処理を行うメソッドがある場合は、そのメソッドをtraitに移して各クラスではそのtraitをuseするといったリファクタリングを行うことがベター。

trait NewsTrait {
    public function getNews() {
        echo 'ニュース';
    }
}

trait ProductTrait {
    public function getProduct() {
        echo 'プロダクト';
    }
}

class Product {
    //作成したtraitをuseして使用できる。
    use ProductTrait;
    use NewsTrait;

    public function getInfo() {
        echo 'クラスです';
    }
}

$product = new Product();

$product->getInfo();
$product->getProduct();
$product->getNews();
---------------------------------
結果
クラスです
プロダクト
ニュース


ここからは番外編

型宣言 (タイプヒンティング)

コーディングをより堅牢なものとする!
関数の引数と戻り値に対して定義します → php7.4からは変数宣言にも使用できるようになったそうです。
メリット
本来期待している型と異なる型が引数に格納されることで起こる、思わぬバグを防げる
コードが読みやすくなる。
型の分類
スカラー型
複合型
特殊型
スカラー型
論理値 (boolean)、整数 (integer)、浮動小数点数 (float, double も同じ)
文字列 (string)

function addNuber(int $num1, int $num2) {
    echo $num1 + $num2;
}
addNuber(1,2);


function isArray(array $array) {
    if(is_array($array)) {
        echo 'これはarray';
    }
}
$array = [1,2,3];
isArray($array);

戻り値の型宣言もできます。

//戻り値の型宣言。
function sum($num1, $num2):int {
    return $num1 + $num2;
}

$a = sum(1,2);
echo $a;

まとめ

laravelを使っていると目にする内容が多いことに気付きました。
これらを理解して、フレームワークをより理解して行きたいです。

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

【PHP】TCPDF + FPDIでPDFを出力してみた話

はじめに

タイトルの通りですが
TCPDFFPDIのライブラリを使用して、PDFを出力してみました。
今回はコマンドラインから実行してみます。

本編

環境構築

お好みでPHP、Composerが使用できる環境を用意してください。

Docker

今回私が使用したDocker環境は以下です(一部割愛)。

./docker-compose.yml
version: '3'
services:
 php:
   build: ./php
   volumes:
     - ./work:/home/work
./php/dockerfile
FROM php:7.3-fpm
COPY php.ini /usr/local/etc/php/

# Composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php -r "if (hash_file('sha384', 'composer-setup.php') === '795f976fe0ebd8b75f26a6dd68f78fd3453ce79f32ecb33e7fd087d39bfeb978342fb73ac986cd4f54edd0dc902601dc') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
  && php composer-setup.php \
  && php -r "unlink('composer-setup.php');" \
  && mv composer.phar /usr/local/bin/composer
./php/php.ini
date.timezone = "Asia/Tokyo"

Composer

立ち上げが完了したらphpのコンテナへ入り、
Composerでライブラリのインストールを行います。

また、ライブラリの他にautoloadの記載も追加しています。

/home/work/src/composer.json
{
    "require": {
        "setasign/fpdi": "^2.3",
        "tecnickcom/tcpdf": "^6.3"
    },

    "autoload": {
        "psr-4": {
            "Classes\\" : "classes/"
        }
    }
}

インストールが終われば完了です。

# cd /home/work/src
# composer install

実装

ファイル構成

# cd /home/work/

# ls /home/work/output/pdf/fileYYYYmmdd-HHiiss #PDF出力ファイル
# ls /home/work/src/resources/font/ipam.ttf #フォントの格納先
# ls /home/work/src/resources/pdf/templete.pdf #PDFテンプレートの格納先
# ls /home/work/bin/make-pdf.php #コマンドラインで実行するソース
# ls /home/work/src/bootstrap.php # 起動用ファイル(割愛)
# ls /home/work/src/classes/MyTcpdf.php # TCPDF利用クラス
# ls /home/work/src/classes/common/DynamicProperty.php # 動的クラス(割愛)
# ls /home/work/src/classes/MyTcpdf/FontColor.php # フォントカラークラス(割愛)

テンプレート

至ってシンプルなPDFテンプレートファイルです。

image.png

ソース

/home/work/bin/make-pdf.php
<?php

require_once __DIR__ . '/../src/bootstrap.php';

use Classes\MyTcpdf;
use Classes\MyTcpdf\FontColor;

// テンプレートを使用せずに出力
$myTcpdf = new MyTcpdf();
$myTcpdf->addPage();
$myTcpdf->setText('テンプレートを使用せずに出力', 10, 20, new FontColor(0, 255, 0));
$myTcpdf->outputPdfFile();

// テンプレートを使用して出力
$myTcpdfTmp = new MyTcpdf();
$myTcpdfTmp->addPageWithTemplete(1);
$myTcpdfTmp->setText('テンプレートを使用して出力', 10, 20, new FontColor(255, 0, 0));
$myTcpdfTmp->outputPdfFile();
/home/work/src/classes/MyTcpdf.php
<?php

namespace Classes;

use TCPDF_FONTS;
use setasign\Fpdi\Tcpdf\Fpdi;
use Classes\MyTcpdf\FontColor;

class MyTcpdf
{
    // 出力ファイル
    const OUTPUT_FORMAT_CHAR = 'F';
    const OUTPUT_FILE_DIR = __DIR__.'/../../output/pdf/';
    const OUTPUT_FILE_NAME = 'file';

    // PDF
    const PDF_ORIENTATION = 'P';
    const PDF_UNIT = 'mm';
    const PDF_SIZE = 'A4';
    const PDF_TEMPLETE = __DIR__.'/../resources/pdf/templete.pdf';

    // フォントファイル
    const FONT_FILE_NAME = __DIR__.'/../resources/font/ipam.ttf';

    /**
     * コンストラクタ
     */
    public function __construct()
    {
        $this->fpdi = new Fpdi(self::PDF_ORIENTATION, self::PDF_UNIT, self::PDF_SIZE);
        $this->fpdi->SetMargins(0, 0, 0);
        $this->fpdi->SetFont($this->getFont(), '', 32);
    }

    /**
     * 出力文字を設定する
     * @param _text 文字列
     * @param _x X座標
     * @param _y Y座標
     * @param _h 高さ
     */
    public function setText(string $_text, int $_x, int $_y, FontColor $_fontColor, int $_h = 0)
    {
        $this->fpdi->SetTextColor($_fontColor->R, $_fontColor->G, $_fontColor->B);
        $this->fpdi->SetXY($_x, $_y);
        $this->fpdi->Write($_h, $_text);
    }

    /**
     * 追加するフォントを取得する
     */
    private function getFont()
    {
        return TCPDF_FONTS::addTTFfont(self::FONT_FILE_NAME);
    }

    /**
     * 出力するファイル名を取得する
     * @return string ファイル名
     */
    private function getOutputFilename()
    {
        return self::OUTPUT_FILE_DIR . self::OUTPUT_FILE_NAME . date("Ymd-His"). '.pdf';
    }

    /**
     * ページを追加する
     */
    public function addPage()
    {
        $this->fpdi->AddPage();
    }

    /**
     * テンプレートベースのページを追加する
     */
    public function addPageWithTemplete($_pageNum)
    {
        $this->fpdi->SetSourceFile(self::PDF_TEMPLETE);
        $page = $this->fpdi->importPage($_pageNum);
        $this->fpdi->AddPage();
        $this->fpdi->useTemplate($page, null, null, null, null, true);
    }

    /**
     * PDFファイル出力
     */
    public function outputPdfFile()
    {
        $this->fpdi->Output(
            $this->getOutputFilename(),
            self::OUTPUT_FORMAT_CHAR
        );
    }
}

実行コマンド

php /home/work/bin/make-pdf.php

実行結果

以下のようにファイルが出力された。

テンプレートなし テンプレートあり
image.png image.png

終わりに

PDFのテンプレートの上に文字を出力できるので、フォーマットを変えずに必要な情報だけを出力できますね。
勤務表や請求書・領収書など、特定の部分だけが変動するファイルに活用できそう。

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

PHPのTCPDF + FPDIでPDFを出力してみた話

はじめに

タイトルの通りですが
TCPDFFPDIのライブラリを使用して、PDFを出力してみました。
今回はコマンドラインから実行してみます。

本編

環境構築

お好みでPHP、Composerが使用できる環境を用意してください。

Docker

今回私が使用したDocker環境は以下です(一部割愛)。

./docker-compose.yml
version: '3'
services:
 php:
   build: ./php
   volumes:
     - ./work:/home/work
./php/dockerfile
FROM php:7.3-fpm
COPY php.ini /usr/local/etc/php/

# Composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php -r "if (hash_file('sha384', 'composer-setup.php') === '795f976fe0ebd8b75f26a6dd68f78fd3453ce79f32ecb33e7fd087d39bfeb978342fb73ac986cd4f54edd0dc902601dc') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
  && php composer-setup.php \
  && php -r "unlink('composer-setup.php');" \
  && mv composer.phar /usr/local/bin/composer
./php/php.ini
date.timezone = "Asia/Tokyo"

Composer

立ち上げが完了したらphpのコンテナへ入り、
Composerでライブラリのインストールを行います。

また、ライブラリの他にautoloadの記載も追加しています。

/home/work/src/composer.json
{
    "require": {
        "setasign/fpdi": "^2.3",
        "tecnickcom/tcpdf": "^6.3"
    },

    "autoload": {
        "psr-4": {
            "Classes\\" : "classes/"
        }
    }
}

インストールが終われば完了です。

# cd /home/work/src
# composer install

実装

ファイル構成

# cd /home/work/

# ls /home/work/output/pdf/fileYYYYmmdd-HHiiss #PDF出力ファイル
# ls /home/work/src/resources/font/ipam.ttf #フォントの格納先
# ls /home/work/src/resources/pdf/templete.pdf #PDFテンプレートの格納先
# ls /home/work/bin/make-pdf.php #コマンドラインで実行するソース
# ls /home/work/src/bootstrap.php # 起動用ファイル(割愛)
# ls /home/work/src/classes/MyTcpdf.php # TCPDF利用クラス
# ls /home/work/src/classes/common/DynamicProperty.php # 動的クラス(割愛)
# ls /home/work/src/classes/MyTcpdf/FontColor.php # フォントカラークラス(割愛)

テンプレート

至ってシンプルなPDFテンプレートファイルです。

image.png

ソース

/home/work/bin/make-pdf.php
<?php

require_once __DIR__ . '/../src/bootstrap.php';

use Classes\MyTcpdf;
use Classes\MyTcpdf\FontColor;

// テンプレートを使用せずに出力
$myTcpdf = new MyTcpdf();
$myTcpdf->addPage();
$myTcpdf->setText('テンプレートを使用せずに出力', 10, 20, new FontColor(0, 255, 0));
$myTcpdf->outputPdfFile();

// テンプレートを使用して出力
$myTcpdfTmp = new MyTcpdf();
$myTcpdfTmp->addPageWithTemplete(1);
$myTcpdfTmp->setText('テンプレートを使用して出力', 10, 20, new FontColor(255, 0, 0));
$myTcpdfTmp->outputPdfFile();
/home/work/src/classes/MyTcpdf.php
<?php

namespace Classes;

use TCPDF_FONTS;
use setasign\Fpdi\Tcpdf\Fpdi;
use Classes\MyTcpdf\FontColor;

class MyTcpdf
{
    // 出力ファイル
    const OUTPUT_FORMAT_CHAR = 'F';
    const OUTPUT_FILE_DIR = __DIR__.'/../../output/pdf/';
    const OUTPUT_FILE_NAME = 'file';

    // PDF
    const PDF_ORIENTATION = 'P';
    const PDF_UNIT = 'mm';
    const PDF_SIZE = 'A4';
    const PDF_TEMPLETE = __DIR__.'/../resources/pdf/templete.pdf';

    // フォントファイル
    const FONT_FILE_NAME = __DIR__.'/../resources/font/ipam.ttf';

    /**
     * コンストラクタ
     */
    public function __construct()
    {
        $this->fpdi = new Fpdi(self::PDF_ORIENTATION, self::PDF_UNIT, self::PDF_SIZE);
        $this->fpdi->SetMargins(0, 0, 0);
        $this->fpdi->SetFont($this->getFont(), '', 32);
    }

    /**
     * 出力文字を設定する
     * @param _text 文字列
     * @param _x X座標
     * @param _y Y座標
     * @param _h 高さ
     */
    public function setText(string $_text, int $_x, int $_y, FontColor $_fontColor, int $_h = 0)
    {
        $this->fpdi->SetTextColor($_fontColor->R, $_fontColor->G, $_fontColor->B);
        $this->fpdi->SetXY($_x, $_y);
        $this->fpdi->Write($_h, $_text);
    }

    /**
     * 追加するフォントを取得する
     */
    private function getFont()
    {
        return TCPDF_FONTS::addTTFfont(self::FONT_FILE_NAME);
    }

    /**
     * 出力するファイル名を取得する
     * @return string ファイル名
     */
    private function getOutputFilename()
    {
        return self::OUTPUT_FILE_DIR . self::OUTPUT_FILE_NAME . date("Ymd-His"). '.pdf';
    }

    /**
     * ページを追加する
     */
    public function addPage()
    {
        $this->fpdi->AddPage();
    }

    /**
     * テンプレートベースのページを追加する
     */
    public function addPageWithTemplete($_pageNum)
    {
        $this->fpdi->SetSourceFile(self::PDF_TEMPLETE);
        $page = $this->fpdi->importPage($_pageNum);
        $this->fpdi->AddPage();
        $this->fpdi->useTemplate($page, null, null, null, null, true);
    }

    /**
     * PDFファイル出力
     */
    public function outputPdfFile()
    {
        $this->fpdi->Output(
            $this->getOutputFilename(),
            self::OUTPUT_FORMAT_CHAR
        );
    }
}

実行コマンド

php /home/work/bin/make-pdf.php

実行結果

以下のようにファイルが出力された。

テンプレートなし テンプレートあり
image.png image.png

終わりに

PDFのテンプレートの上に文字を出力できるので、フォーマットを変えずに必要な情報だけを出力できますね。
勤務表や請求書・領収書など、特定の部分だけが変動するファイルに活用できそう。

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

PHPUnit assertEquals アサーションについて

目的

  • PHPUnitのアサーションの1つであるassertEqualsについてかんたんにまとめる

すみません

  • 本記事は筆者の備忘録的要素が強いためメモ的な簡易的な記事となる。
  • 内容が薄い場合は参考文献先などを参考に各自て補填していただければ幸いである。

assertEquals

  • 検査対象の中に指定された文字列が含まれているかをチェックするアサーションである。
  • 具体例を下記に記載する。

    assertEquals('A', 'B');
    
  • 上記のようにテストコードが記載された場合、指定された文字列がAで検査対象がBとなる。

  • 先の説明でAが指定文字列、Bが検査対象としたがAとBが一致しているかを検査するアサーションなので実際には順不同である。

  • もう少し具体的な例を下記に記載する。

    • $strには「hello」の文字列が格納されている。

      $str = 'hello';
      
    • $strに「hello」という文字列が格納されているかをassertEqualsのアサーションを使ってテストする際は下記のようになる。

      assertEquals('hello', $str);
      
    • 上記のテストコードを実行すると「テストOK」となる。

まとめ

  • assertEqualsアサーションは「指定文字列と検査対象が一致しているか」をチェックするものである。
  • ※文字列以外でもチェック可能である。(コメントで教えていただきました!ありがとうございます!)

参考文献

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