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

本番運用のための理想的なDockerfileについての考察

はじめに

以前、私の携わっていた案件ではアプリケーションを動作させるDockerイメージが重いことが常に問題になっていました。

Dockerイメージが重いと、良いことは一つもありません。
私の経験上で主要な具体例を上げると…

  • 稼働アプリケーションのパフォーマンス低下 (無駄なリソース消費)
  • ビルド(CI)にかかる時間の増大
  • ローカル開発が重くてストレス (開発にかかる運用負荷の増大)

ということが実際に発生してきます。

どういうコンテナを作るべきかは、Googleがベストプラクティスを提唱しているものが有名で、以下のatmarkITの記事などでも紹介されています。

https://www.atmarkit.co.jp/ait/articles/1807/13/news054.html

どんな環境を想定しているか

具体的には、本項ではステートレスなWEBサービスのフロントエンドについて考察します。

先に進むほど具体的な内容について記しますが、最終的には Linux + Apache + PHP のような一般的なWEBサーバのフロントとなるコンテナを実現します。

DBやキャッシュ周りは、SaaSや他のコンテナに任せるものと想定します。

本番運用するにあたりレイヤーをどうするか

先ほどのGoogleのプラクティスにもある通り、公開されているイメージをそのまま自分の (自社の) サービスに適用できるケースは少ないでしょう。

何がしかの公開イメージをもとに、足りないライブラリ・パッケージを導入するDockerfileを書き、ビルドしてサービス投入することになるはずです。

ここでまず考えなくてはならないポイントは、ビルドするレイヤーは通常1つでは収まらないということです。

具体的には、CIについて考慮する必要があります。

  • OSやミドルウェアなど、基本的な環境を定義してビルドする (1層目)
  • CIを回して git clone を行い、最新のアプリ状態をビルドする (2層目)

という、最低2層の構成とすべきでしょう。

レイヤーは少なければ少ないほど、完成するイメージは軽くなります。
ただし、CIを回してビルドするイメージを毎回最初から作っていたのでは、どんなに軽くできるテクニックを駆使していても、毎回相応のビルド時間が発生するので現実的ではありません。

「1層目」について、同様の環境で動作するいくつかのアプリケーションは、同じイメージを共用する方が良いでしょう。

これは、セキュリティにとっても利点があります。
例えばOSに脆弱性が出たときに、アプリケーション側のDockerfileの FROM を書き換えることで、各アプリケーション一律にセキュリティ対応が行われた状態で、すぐデプロイし直せることがその理由です。

実際、私がかつて携わっていた案件でも「1層目」を共通化すること自体はネガティブな要素や意見はありませんでした。

場合によっては、アプリケーションごとの差を吸収するために3層としなければならないケースもあるかも知れません。詳しくは個別的な検討が必要でしょう。

また、これより先は、

  • 1層目のビルドイメージ … ベースイメージ
  • 2層目のビルドイメージ … CIイメージ

という呼び方で話を進めたいと思います。

アンチパターン

全くの私見ではありますが、私の過去の経験をもとに先に良くないベースイメージの構成の仕方を挙げていきます。

  1. VMで稼働してるインフラの作り方をDockerfileに適用する
  2. 言語のバージョンをもってイメージを場合分けする
  3. 公開されているイメージを生かさない
  4. OSのディストリビューションにこだわる
  5. Dockerfileとして軽量にできる記法/手法を使わない

主にこの5点だと考えます。
カテゴリごとというよりも、ハマりそうな順に書いてます。。。

一文ではよく分からないと思いますので、下記で個別的に詳細説明します。

1. VMで稼働してるインフラの作り方をDockerfileに適用する

これから本番でDockerを採用しようとする企業 (チーム) にとって、最もあると思われるパターン。例えば AWS EC2 のオートスケール環境で稼働しているアプリケーションをコンテナ化する…などの要件ではないかと思います。

一見すると、Dockerfileはシェルスクリプトとほぼ同様に読めるので、VMで『安定稼働』しているサーバインフラの作りをそのまま、あるいは似たものを移植したくなるかも知れません。

これはアンチパターンです。

本項では主にPHP環境を扱うのですが、PHPのインストールのやり方としては、基本的には以下のような3つの方法が考えられます。

  • ソースインストール (公式推奨)
  • yum や apt などOSのリポジトリ管理ツールからインストール
  • .rpm や .deb などのパッケージファイルを使用してインストール

VMで構築する場合は、おそらくこのいずれかを採用するはずですが、Dockerで同じことをするべきではありません。

いずれの方法を取っても、処理的には高コストなのでイメージを重くします。

詳しくは後ほどまた触れていきます。

2. 言語のバージョンをもってイメージを場合分けする

先ほど、「同様の環境で動作するいくつかのアプリケーションは、同じイメージを共用する方が良い」と書きました。

結論から書くと、これは、たとえばPHP5.6で開発されたアプリケーション群と7.0で開発されたアプリケーション群の差異…のことを指しているのではありません。

ここでのアンチパターンは、以下の内容を指しています。

同じPHP7.0のアプリケーションでも、フレームワークが異なると必要なライブラリは異なってきます。
そのため、複数のフレームワークに対応させたイメージは、多数のライブラリを盛り込む必要があり、より重量級になりがちです。

また、同言語の同バージョンを後から別のフレームワークに対応させようとした場合、追加で別のソフトウェアを導入する必要も生じます。
これは結果的に、他のアプリケーションに影響を与え安定性を損なう原因になったり、Dockerfileの複雑化・さらなる重量化をもたらします。

言語のバージョンよりも、

  • フレームワークごと
  • フレームワークのバージョンごと

という、2つの視点で分けながら共通化を図る方が自然と考えます。

ただし、フレームワークを利用しない言語やフレームワーク自体の開発が遅い (言語のバージョンのみ上げたい要件が頻発する) 場合などはこの限りではないでしょう。別のスキームの検討が必要と考えます。

3. 公開されているイメージを生かさない

  1. で書いた通り、言語環境を一から構成する処理は重いです。

Docker Hubには多数のイメージが用意されています。
軽いDockerイメージを作るコツは、実現したい環境に近い公式イメージを基にしてDockerfileを書くことです。

https://hub.docker.com/_/php

具体的には、上記のようにアプリケーションを動作させるための言語環境を基準にした公式イメージを使用するのがより良いと考えます。

「Dockerイメージをなぜ作るのですか?」

という問いに、「アプリケーションを動かすため」と私たちは明確に答えるはずです。目的に適合する最適なイメージを選定することが、私たちに必要な第一歩だと思います。

基となる軽量で最適なイメージをよく考慮しないこと、これがアンチパターンです。

Node.js, Python, Go, Ruby… いずれも言語に応じた公式がありますので、先にそちらを覗いてみるべきでしょう。

https://hub.docker.com/_/node/
https://hub.docker.com/_/python/
https://hub.docker.com/_/golang/
https://hub.docker.com/_/ruby/

話をPHPの環境構築に戻すと、PHPのエクステンションは歴史的に非常に複雑化しており、大まかには以下の3パターンに分かれます。

  1. PHPのソースに同梱されるもの
  2. 外部提供されるC言語で開発されたもの (pecl コマンドでインストール )
  3. 外部提供されるPHPで開発されたもの (composer コマンドでインストール )

PHPの本番運用を考えるにあたって、 composer に任せられない 1. 2. を扱うプラクティスは持っておく必要があります。

なお、https://hub.docker.com/_/php には以下のように書かれており、

We provide the helper scripts docker-php-ext-configure, docker-php-ext-install, and docker-php-ext-enable to more easily install PHP extensions.

また、例として以下のようなDockerfileが紹介されています。

FROM php:7.4-fpm
RUN apt-get update && apt-get install -y \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libpng-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd

ここではエクステンションとして、 php-gd のインストールが例として触れられているのですが、これだけ取ってもOSのディストリビューションやPHP自体のインストール時に行うなど、一般的には複数の方法があります。
https://www.php.net/manual/ja/image.installation.php

これを docker-php-ext-install などのコマンドだけで扱えるなら、よりメリットがありそう…と考えたのですが、結論としてはこの方法は上手くいきませんでした……詳しくは後述します。

4. OSのディストリビューションにこだわる

現サービスを安定運用しているエンジニアにとって (ことさらに運用系に深く関わるエンジニアにとって) 、今まで使用してきた愛着あるLinuxディストリビューションは無視できないポイントでしょう。

しかしながら、3. で書いたようにコンテナ環境を作る唯一の意味は「アプリケーションを動かす」ことのみにあります。OSの種類は問題ではありません

コンテナのような定義的 (宣言的) なインフラにおいては、ロジックとして正常に動作すると確認できたものは安定運用できると考えて差しつかえありません。
VM環境からコンテナへサービスを移行するときは、機能テストを網羅的に行うことにこそ特に注力すべきです。
(もちろん、適切なスケーリングあるいはオートスケールは当然必要ですが…)

本項では、軽量Linuxディストリビューションの一つ Alpine を利用します。

Alpineは、ここ数年でDocker界隈ではかなり注目されているとともに、Docker HubのPHP公式内を俯瞰してみたときに、 (標準であるDebianと比較すると) 非常に軽量なためです。

5. Dockerfileとして軽量にできる記法/手法を使わない

ここでは、WEB検索すると多く出てくる一連の情報をメインに紹介します。

現運用しているなどDockerfileの構成をすぐに大きく変更できない場合でも、もし以下のようなことをまだ行っていない場合は、相応の軽量化が期待できます。

  • インストールキャッシュを削除する (apt, yum, apk など利用する場合)
例:
 (※ 実際にはDockerfileの形式に合わせた最適化が必要)

# apt clean
 または
# rm -rf /var/cache/apt/*
  • ソースインストールを行う場合は、tarボールやビルドしたソースを削除する
    → 一般のサーバ運用であれば保持しておくべきですが、コンテナでは継続運用しないため。
例:
 (※ 実際にはDockerfileの形式に合わせた最適化が必要)

# cd /usr/local/src/httpd-2.4.9
# ./configure --prefix=/opt/httpd/httpd-2.4.9 --with-apr=/opt/apr/apr-1.5.1 --with-apr-util=/opt/apr-util/apr-util-1.5.3
# make
# make install

// この部分が該当
# make clean
# rm -rf /usr/local/src/httpd-2.4.9/
  • 場合によりマルチステージビルドを活用する
    → 一般に公式イメージは軽いのと、コンパイルのステージを分離して複数イメージを利用することが軽量化に効果を発揮する場合があります。

  • ADD より COPY を利用する
    → 詳細は公式に任せて割愛しますが、機能が違うので COPY を使いましょう。
    https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#add-or-copy

  • RUN, COPY などの処理はなるべくまとめる
    → 具体的には、 \&& で結合しRUNは1回にします。
      RUNを次々に並べて書くようなやり方はスマートでないだけでなく、複雑な処理になるとイメージサイズに影響する場合があるようです。

  • ビルドディレクトリ配下に余計なものを置かない
    → COPY の使い方しだいで運ばれてしまいます…
      何かの事情で避けられない場合は .dockerignore を書きましょう。

Laravel 7.x が稼働するベースイメージを作ってみる

手元に Laravel 7.x を利用して開発したテストアプリケーションがあるため、これを本番相当で動作可能とするユースケースを考えていきます。

まず、Docker HubのPHP公式から適当と思われるイメージを見繕います。

前提として PHP >= 7.2.5 という要件があるので、最低限に近いところでPHPのバージョンは 7.3 としたいと思います。
https://readouble.com/laravel/7.x/ja/installation.html

ここで、考えるべきポイントは4つ。

  • AlpineがベースOSとなっていること
  • 2.4系のApacheが最初から入っていると、なおベスト
  • ZTSは必要ない
  • worker系のMPMは使用しないので、FPMも必要ない

ざっくり合いそうなものは以下の感じでした。

  • php:7.3-alpine3.12
  • php:7.3-apache

ポイントに合わせた観点で行くと、AlpineかつApacheインストール済みのものはありませんでした。php:7.3-apache については、OSは Debian 10 (buster) となります。

一旦、これらを手元のPCでpullして素のイメージのサイズ比較を行います。

$ docker pull php:7.3-alpine3.12
$ docker pull php:7.3-apache
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
php                 7.3-alpine3.12      898191dd6734        8 days ago          74.3MB
php                 7.3-apache          4c443398a62c        8 days ago          410MB

正直なところ、全く比較になりません…

いくらApacheが入っていないとはいえ、Alpineが劇的に軽すぎるためかなりのパッケージを積み込んでもbusterのイメージに届くことは、まず絶対にないでしょう。

ここは、 php:7.3-alpine3.12 のイメージを採用することで進めます。

php:7.3-alpine3.12 の素のイメージに足りないものを確認

Laravel 7.x のインストール要件に戻ります。
https://readouble.com/laravel/7.x/ja/installation.html

以下のように記載されています。

  • PHP >= 7.2.5
  • BCMath PHP拡張
  • Ctype PHP拡張
  • Fileinfo PHP拡張
  • JSON PHP拡張
  • Mbstring PHP拡張
  • OpenSSL PHP拡張
  • PDO PHP拡張
  • Tokenizer PHP拡張
  • XML PHP拡張

素のイメージからコンテナを起動し、足りないものを確認していきます。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
php                 7.3-alpine3.12      898191dd6734        8 days ago          74.3MB
php                 7.3-apache          4c443398a62c        8 days ago          410MB
$ docker run -it -d -p 8000:80 898191dd6734        // http://127.0.0.1:8000 でPC目視確認できるよう設定
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
b7a35b6625d7        d6096778d65d        "docker-php-entrypoi…"   7 seconds ago       Up 5 seconds        0.0.0.0:8000->80/tcp   wizardly_meitner

ここまでは手元のPCで実行しました。
次にコンテナにログインし、ライブラリの状況を調査します。

$ docker exec -it b7a35b6625d7 /bin/sh

/ # php -m
[PHP Modules]
Core
ctype
curl
date
dom
fileinfo
filter
ftp
hash
iconv
json
libxml
mbstring
mysqlnd
openssl
pcre
PDO
pdo_sqlite
Phar
posix
readline
Reflection
session
SimpleXML
sodium
SPL
sqlite3
standard
tokenizer
xml
xmlreader
xmlwriter
zlib

[Zend Modules]

bcmath 以外は入っているようです… が、結論としてはこれだけでは情報が不足です。

yum や apt を利用する時と異なり、公式イメージはPHPがソースからビルドされているため、結局各ライブラリのインストールが必要となります。

このことを確認するためには、 php -i を利用するか phpinfo を設置しWEBブラウザで確認しながら考慮すると良いでしょう。

/var/www/localhost/htdocs/phpinfo.php
<?php
phpinfo();
?>

以下のように、ビルドされたエクステンションが enabled となっていることが必要です。
bcmath_support_enable.png

また、以下についても確認しました。

/ # date
Sat Jun 20 12:11:15 UTC 2020
/ # which composer        // 出力なし
/ # which npm             // 出力なし

タイムゾーンがUTCになっているのと、Laravel 7で必要なコマンドがないのでこの辺りを調整することにします。
またMySQL接続は必要なので、それらのライブラリも追加でインストールが必要です。

実際に書いたDockerfile

FROM php:7.3-alpine3.12

# Document Root
RUN mkdir /application

# Package Install
RUN apk update && apk add --no-cache git tzdata apache2 npm \
    php7-common php7-apache2 php7-bcmath php7-ctype  php7-fileinfo php7-json \
    php7-mbstring php7-openssl php7-pdo php7-mbstring php7-tokenizer php7-xml \
    php7-session php7-pdo_mysql php7-mysqli \
  && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
  && apk del tzdata

# Copy php.ini
COPY ./etc/php7/php.ini /etc/php7/

# Copy httpd.conf
COPY ./etc/apache2/httpd.conf /etc/apache2/

# Composer Install
RUN curl -sS https://getcomposer.org/installer | php -- \
  --install-dir=/usr/local/bin --filename=composer --version=1.10.6
  • --no-cache オプションについて :
    /var/cache/apk/ 配下のインストールキャッシュを自動で消してくれます。

ある程度は可読性に配慮しつつ、上記のようにまとめました。
git clone するアプリケーションは /application をドキュメントルートとして配置する想定です。

php.ini および httpd.conf は内容を検討し、Dockerfileからの相対パスに配置しておく必要があります。
また、 composer コマンドは常に最新のものが入るので、挙動を一致させ可読性を高める意味でバージョン指定を行うこととしました。

手元のPCでビルドして、内容を確認します。

$ docker build -t docker-image-base-laravel7x ./
$ docker images
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
docker-image-base-laravel7x   latest              b239a1771f04        7 seconds ago       177MB

177MBに収まりました!

私のかつての担当案件では、当初のアプリイメージ (CIイメージ) が 3GB 近くあったため、かなり重くなっていました。その後、基本的なつくりは変えずに軽量化を試みて 約半分1.5GB 弱までダイエット。

それでもローカル開発が重い…というあたりの感覚値です。
見込みとしては、CIイメージが 1GB を切ると色々と改善されてくる可能性が高いです。

その前提からすると、177MBはかなり良好な数値と言えます。

コンテナに入って状況を確認します。

$ docker run -it -d b239a1771f04
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
d87924fc9320        b239a1771f04        "docker-php-entrypoi…"   8 seconds ago       Up 6 seconds                            epic_mccarthy
$ docker exec -it d87924fc9320 /bin/sh

コンテナ内で変更点をチェックしていきます。

/ # date
Sun Jun 21 19:02:24 JST 2020
/ # apk list | grep "php" | grep "installed"
php7-openssl-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-tokenizer-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-pdo_mysql-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-common-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-mysqlnd-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-fileinfo-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-mbstring-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-session-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-json-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-xml-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-apache2-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-ctype-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-bcmath-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-pdo-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-mysqli-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
/ # which composer
/usr/local/bin/composer
/ # composer -V
Composer version 1.10.6 2020-05-06 10:28:10
/ # which npm
/usr/bin/npm
/ # npm -v
6.14.4

問題なく適用されています。

なお、 php7-session というのを含めていますが、これを入れないと以下のようなエラーログが出てLaravel環境のサービスの正常性が確認できませんでした。

/var/log/apache2/access.log
[Fri Jun 26 19:30:49.089145 2020] [php7:error] [pid 240] [client 172.17.0.1:42184] PHP Fatal error:  Interface 'SessionHandlerInterface' not found in /application/testapp/vendor/laravel/framework/src/Illuminate/Session/FileSessionHandler.php on line 10
[Fri Jun 26 19:30:49.394184 2020] [php7:error] [pid 236] [client 172.17.0.1:42204] PHP Fatal error:  Interface 'SessionHandlerInterface' not found in /application/testapp/vendor/laravel/framework/src/Illuminate/Session/FileSessionHandler.php on line 10

PECL でインストールするものを考える

まず memcached について下記のような方法を検討しました。

# Install Memcached
RUN apk add --no-cache autoconf \
  libmemcached-dev zlib-dev \
  gcc g++ make \
&& pecl install memcached \
&& docker-php-ext-enable memcached

先ほど docker-php-ext-install は結論としては使えないと書きましたが、その理由としてソースインストールされたPHPへのエクステンションの組込みの問題があります。

apk を使うことでこの部分をよきに計らってくれ、以下のようなiniファイルが生成されるのです。

/ # ls -la /etc/php7/conf.d/
total 72
drwxr-xr-x    1 root     root          4096 Jul  4 09:48 .
drwxr-xr-x    1 root     root          4096 Jul  3 10:49 ..
-rw-r--r--    1 root     root            20 Jun 12 21:27 00_bcmath.ini
-rw-r--r--    1 root     root            19 Jun 12 21:27 00_ctype.ini
-rw-r--r--    1 root     root            22 Jun 12 21:27 00_fileinfo.ini
-rw-r--r--    1 root     root            18 Jun 12 21:27 00_json.ini
-rw-r--r--    1 root     root            22 Jun 12 21:27 00_mbstring.ini
-rw-r--r--    1 root     root            21 Jun 12 21:27 00_openssl.ini
-rw-r--r--    1 root     root            17 Jun 12 21:27 00_pdo.ini
-rw-r--r--    1 root     root            21 Jun 12 21:27 00_session.ini
-rw-r--r--    1 root     root            23 Jun 12 21:27 00_tokenizer.ini
-rw-r--r--    1 root     root            17 Jun 12 21:27 00_xml.ini
-rw-r--r--    1 root     root            21 Jun 12 21:27 01_mysqlnd.ini
-rw-r--r--    1 root     root            20 Jun 12 21:27 02_mysqli.ini
-rw-r--r--    1 root     root            23 Jun 12 21:27 02_pdo_mysql.ini
-rw-r--r--    1 root     root            22 Jan 23 02:29 10_igbinary.ini

実際には pecl コマンドを使用してインストールするプラクティスを持つものも apk を経由しておよそインストール可能と見えます。

/ # apk search pecl
php7-pecl-imagick-dev-3.4.4-r3
php7-pecl-mailparse-3.1.0-r0
php7-pecl-imagick-3.4.4-r3
php7-pecl-uploadprogress-doc-1.1.3-r1
php7-pecl-amqp-1.10.2-r0
php7-pecl-yaml-2.1.0-r1
php7-pecl-memcache-4.0.5.2-r0
php7-pecl-psr-1.0.0-r0
php7-pecl-redis-5.2.2-r1
php7-pecl-ast-1.0.6-r0
php7-pecl-xdebug-2.9.6-r0
php7-pecl-couchbase-2.6.2-r0
php7-pecl-uploadprogress-1.1.3-r1
php7-pecl-apcu-5.1.18-r0
php7-pecl-timezonedb-2020.1-r0
php7-pecl-igbinary-3.1.2-r0
php7-pecl-uuid-1.1.0-r1
php7-pecl-gmagick-2.0.5_rc1-r5
php7-pecl-lzf-1.6.8-r0
php7-pecl-msgpack-2.1.0-r0
php7-pecl-vips-1.0.10-r1
php7-pecl-zmq-1.1.3-r6
php7-pear-7.3.19-r0
php7-pecl-mcrypt-1.0.3-r0
php7-pecl-xhprof-2.2.0-r0
php7-pecl-oauth-2.0.5-r0
php7-pecl-ssh2-1.2-r0
php7-pecl-memcached-3.1.5-r0
tesseract-ocr-4.1.1-r3
php7-pecl-protobuf-3.12.2-r0
php7-pecl-event-2.5.6-r0
php7-pecl-xhprof-assets-2.2.0-r0

総合的に考えると apk に寄せてしまい、 docker-php-ext-install/enable 系のコマンドは使わなくても良いと判断しました。

redis (phpredis) は特によく使われるので、memcached とあわせてこれらを含んだDockerfileにしてみたものが以下です。

FROM php:7.3-alpine3.12

# Document Root
RUN mkdir /application

# Package Install
RUN apk update && apk add --no-cache git tzdata apache2 npm \
    php7-common php7-apache2 php7-bcmath php7-ctype  php7-fileinfo php7-json \
    php7-mbstring php7-openssl php7-pdo php7-mbstring php7-tokenizer php7-xml \
    php7-session php7-pdo_mysql php7-mysqli \
    php7-pecl-memcached php7-pecl-memcache php7-pecl-redis \
  && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
  && apk del tzdata

# Copy php.ini
COPY ./etc/php7/php.ini /etc/php7/

# Copy httpd.conf
COPY ./etc/apache2/httpd.conf /etc/apache2/

# Composer Install
RUN curl -sS https://getcomposer.org/installer | php -- \
  --install-dir=/usr/local/bin --filename=composer --version=1.10.6

各コマンドはざっくり省略しますが、イメージサイズは以下の通りです。

$ docker images
REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
docker-image-base-laravel7x-cache                  latest              a928f55b49a6        40 seconds ago      180MB

180MB
全くのところ、劇的に軽いです。
Dockerfileはこの作り方が正解なのではないかと考察します。

より高速なWEBのために

以前の担当案件では、イメージにPHPを高速化するためのアクセラレータ APCu, OPcache などが入っていました。

このあたりも案件は物理サーバから移植した構成であったため、(当初に) ほとんどそのまま入れたのだと思います。こうしたツールがコンテナにどの程度なじむかは、ちょっと注意・検証が必要かな…と思います。

本項ではこうした所に突っ込むつもりはないので、環境や言語によりけりで確認する必要はあるでしょう。

そもそもWEBを高速化したいなら…

  • より一般的なキャッシュ (CDN, フレームワーク, DB, redisに寄せる… etc)
  • ボトルネックを見つけて対応 (モデル/SQLまわり, 処理ロジック… etc)
  • ネットワークやサーバキャパシティプランニングの問題 (インフラ系)

などのセオリーがあるので、まず内部のキャッシュツールを導入するより、個人的には先にそういった原因調査から行う方がいいのではないかな…と。。
調査の過程で有用性が認められればあるいは、というのが本来の順序と考えます。

PHPに関しては言語自体以前よりはるかに高速化しているので、言語のバージョンとフレームワーク如何の組み合わせにも着目する必要があるでしょう。

おわりに

Dockerは検証や開発の環境作りにとても便利です。
また、Kubernetesなどでこれを運用することで、安定した本番環境を得ることもできます。

ただし、本番環境はとにかく軽くしておかないとアプリケーションの運用年数が長くなってきた時に、冒頭に書いたようなさまざまな不利益を被ることになります。さらに悪いことに、Dockerfileはコードであるためにサービスイン後にこれを変更することは (特に調整面で…) 手数も多く、当然危険も伴います。

コンテナアプリケーションのサービスイン時には、

  • より軽くシンプルなDockerfileの検討
  • 機能テストを網羅的に行うこと
  • キャパシティプランニングとスケーラビリティの調整
  • 現実的なシナリオを想定した負荷試験

などをバランス良く実施しながら、一定の時間をかけ慎重に行った方が賢明でしょう。

Fin ❤︎

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

【FuelPHP】Html::anchorを使わずにページ遷移させる

問題点 : Html::anchorを使った時

//返り値 <a href="http://www.domain.com/example">Example</a>
echo Html::anchor('example', 'Example');

//返り値 <a href="http://www.otherdomain.com/example">Example</a>
echo Html::anchor('http://www.otherdomain.com/example', 'Example');

公式のドキュメントには上記のように書いていますが、
次のようにaタグの中にspanタグなどを入れたい場合の書き方が解説されていませんでした。

<a href="http://www.domain.com/example>
 <i class=""></i><span>Qiita</span>
</a>

解決策 : Uri::base()を使う

半日頭を悩ませて、Uri::base()を使う結論に至りました。

<a href="<?php echo Uri::base(); ?> example>
 <i class=""></i><span>Qiita</span>
</a>

事前にconfigファイルのbase_urlにベースのurlは書いておかないと使えません。

fuel/app/config/config.php
'base_url'  => 'http://www.domain.com/',

もっといい方法あれば教えていただけると嬉しいです。

ありがとうございました。

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

一歩進んだMVCフレームワークの使い方

まえ書き

私は現役バリバリでコードを書いてるエンジニアではないので、モダンな内容の記事は書けません。エンジニア1, 2年生がなんとなく理解しているけど、はっきりとは理解できていない、そんなことが、よりはっきりとした理解に変わる。そんな記事が書けたらなと思っています。

MVCパターンの限界

前回の投稿からだいぶ時間がたってしまっていますが、前回投稿した記事では、MVCパターンにおいて、ビジネスロジックとはどういうことなのか、それはどこに実装するのが望ましいのかについて書かせて頂きました。しかしながら、ビジネス要件が目まぐるしく変わる今となっては、これに対応できず限界に達しています。

MVCパターンが限界になるまでの変遷
MVCパターン(≒MVCフレームワーク)の登場

ビジネスロジックをControllerに書くため、Controllerが肥大化
※ そもそも間違ってる、でも当時の参考書がみんなそうなっていた

Skinny Controller, Fat Model
※ だんだんビジネスロジックをModelに書くようになってきた

ビジネスロジックが複雑化
※ 複雑に絡み合ったビジネスロジックがひとつのModelの中に混在。

ビジネスロジックの分離

Modelが肥大化することで、本来疎結合であるべきロジックが同一モデル内で複雑に絡み合い密結合となってゆき、可読性が悪く保守性が低くなってきました。前の記事でModelはDAOではないと書きました。しかし、それでも実装がテーブルの設計に引きずられることもありますし、本来そのテーブルに関係のない処理を書かざるを得ないというケースも発生してきます。ビジネスロジックをコントローラーに実装したくはない。どうしよう。。。そこで登場したのがServiceという概念です。

Serviceの役割と見つけ方

ここまでの話で、ビジネスロジックをServiceに実装するのはおわかりかと思います。でも、どのような単位でServiceを分ければ良いのか。ひとつの例として、”業務”単位でわけるというのがあります。例えば、小売店を思い浮かべてみてください。どんな”作業”があるでしょうか?

小売店の作業

  • 商品を仕入れる
  • 仕入れの代金を支払う
  • 仕入れた商品を入庫する
  • 注文を受ける
  • 商品の代金を受け取る
  • 商品を出荷する

実装するクラスはこんなイメージです。ビジネスロジックはModelにある状態ですね。
VPJ1IiD048RlVOezwg4lu47wAeBUr8FM1zWiDiPMYjLKWeAW5If51Ii8YMyptQrv2qVO_WLd8fGUydt3_BkJIUUuktFKFJdORuNhjv3bbCp9yU-Ct0kvBtBF_6zCkxrjek0UoANaPgrbKORvL2_uzIE7lwXoV_hKx4SEdul3E6ts8uVlxwQ-d3NxaSDl_JcGsJAhY0EohulgiKulAHb5pVOsDfMIPL7LWtSL-KaMRZzqb9tN.png

これを、"業務"という単位で分けてみます。

販売

  • 注文を受ける
  • 商品の代金を受け取る
  • 商品を出荷する

購買

  • 商品を仕入れる
  • 仕入れの代金を払う
  • 仕入れた商品を入庫する

実装するクラスはこんなイメージになります。ビジネスロジックはServiceに移しました。

bLJDIiD04BxtANBKGryW1-ch27fJ3reV85bih5PAhQq41S5MQ2ag94enwTjCDoPluHXs4kniDXP23llzp3UpoxJEslkdxVFZeqPozvvqH-268F2x1F4AOWLYZd_BQZRth3HSfS4ItC_yV0ZkL-FRZv9mfjLKITRnI5wyR2N14idOCqY2kPm4VoJoSIQZnLOINZXdgXR5CmWFnAAAfjggewaXrz5K_896SJTeQZ2SPdRJLRMf.png

なんとなく、イメージがつかめたでしょうか?

実際には、”在庫が少なくなってきたら購買にしらせる”というような作業もあったりするでしょう。こういった作業も、Serviceの責務を業務として明確にしたことによって、どこに実装すべきかわかりやすくなりました。大切なことは、責務を明確にしてその責務を壊さない、責務の範囲を広げすぎないことです。これをしてしまうと、Modelと同様に今度はServiceが肥大化してしまいます。ServiceはModelのようにテーブルという制約がありませんので、新しいServiceを設けるなど、柔軟に対応しましょう。

Serviceがもたらす恩恵

さて。Serviceを導入することで保守性が高くなり、コードの見通しもだいぶ良くなりました。しかし、Serviceがもたらしてくれる恩恵は、これだけではありません。

Model, View, Controllerの構成だった時は

  • Model : フレームワーク、DBへの依存度が高い
  • View : フレームワークへの依存度が高い
  • Controller : Model, View, フレームワークへの依存度が高い

Viewは置いといて、これを見てわかるとおり、ModelやControllerはフレームワークや他クラス、DBへの依存度が高いです。依存度が高いということは、テストしづらいということになります。これだけを読むと、初心者の方には何のことか理解できないかもしれませんが、今は”他クラスへの依存が多いとテストしづらい”ということだけ覚えておいてください。(テストのことは、また別の機会に)

ControllerとModelの間にServiceを入れると、Serviceの依存は基本的にModelだけですし、フレームワークが用意しているクラスを継承していませんから、他クラスへの依存度が低いため、テストがしやすくなります。さらに、ビジネスロジックはほとんどServiceに実装されているので、ビジネスロジックのテストをほとんどを自動化できます。

最後に

MVCフレームワークの変遷第2弾、いかがだったでしょうか?MVCにServiceのみを加えるのも、今となっては少し古いアーキテクチャになってしまいましたが、実際の開発現場ではまだまだMVCしか使っていないプロジェクトも多いです。もちろん、開発するWEBサービスの規模によってはそれで良いかもしれませんが、Serviceを加えるアーキテクチャにはメリットが沢山あります。是非一度、あなたのプロジェクトにも導入してみてください。

それではまた!

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

【Laravel】テンプレートの書き方

概要

LaravelのMVCのうちのビュー周辺について説明するよ
つまり、テンプレートの書き方だね
初心者向け程度の内容だよ
【Laravel】コントローラの書き方と一緒に読むといいよ

ビューの呼び出し

コントローラ側でview()が呼ばれると、引数に与えられたテンプレートがレンダリングされてブラウザに表示されるよ
例えば、view('welcome')の場合、resourcesフォルダ内のviewフォルダの中にあるwelcome.blade.phpまたはwelcome.phpというファイルが呼び出される
2つともファイルがあると拡張子がblade.phpの方が呼び出される

viewフォルダの中にさらにフォルダを作りその中にテンプレートを作ると、
  view('フォルダ名.テンプレート名')
のように呼び出せばいい

テンプレートの例

  • htmlファイルと同じように書く
resources/view/hello.blade.php
<html>
<head>
    <title>Hello</title>
</head>
<body>
    <h1>Hello</h1>
    <p>Hello World</p>
    <p>Hello Laravel</p>
</body>
</html>

これでもいいんだけど、フレームワークらしくないよね
そこで

  • Bladeを使う
resources/view/hello.blade.php
@extends('template')
@section('title', 'Hello')
@section('greeting')
    <p>Hello World</p>
    <p>Hello Laravel</p>
@endsection
resources/view/template.blade.php
<html>
<head>
    <title>@yield('title')</title>
</head>
<body>
    <h1>Hello</h1>
    @yield('greeting')
</body>
</html>

@で書かれたものはディレクティブと呼ばれているよ
extendsディレクティブで「template.blade.phpを使うよ」と宣言して、
yieldディレクティブのところに、sectionディレクティブで設定したものが入る感じだよ

ディレクティブには、

  • section
  • yield
  • component
  • include

のような組み込むものがあったり

  • if
  • for
  • foreach
  • while
  • switch

のような制御構文のものがあったり

  • csrf
  • method
  • error

のようなフォーム関連のものがあるよ
使い方や説明はReadoubleに書いてあるから見ておくといいよ(ここでは詳しくは説明しない)

値の埋め込み

resources/view/hello.blade.php
@extends('template')
@section('title', 'Hello')
@section('greeting')
    <p>Hello World</p>
    @if ($name != null)
    <p>Hello {{$name}}</p>
    @endif
@endsection

上のように{{ }}の中に変数や関数を書くことで値を埋め込めることができる
例えば、コントローラ側で
  view('hello',['name'=>'Laravel'])
とあったら、{{$name}}の部分にLaravelが表示されることになるよ

おわり

今回はここまで
ディレクティブの部分は全部説明すると長くなりそうだったのでReadoubleに丸投げ(手抜き)
要は

  • view()でテンプレートを呼び出してレンダリング
  • ディレクティブを使いこなそう
  • {{ }}内に変数や関数を書いて値を埋め込む

ということ
以上!

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

【Laravel】コントローラの書き方

概要

Laravelのコントローラ周辺について説明するよ
初心者向けの内容だよ
【Laravel】ルーティングの書き方と一緒に読むといいよ

コントローラ

コントローラとは、アプリケーションの処理を実装する部分だよ
クライアントからアクセスがあると、ルーティングによって、「○○というアドレスがきたら××という処理を行う」ということを決めるんだけど、この「××という処理」を書いているものがコントローラだよ
コントローラはクラスで表されていて、アクションというメソッドで書かれたものの中に具体的な処理を書いていく

基本

コントローラを作成するにはartisanコマンドを使う

% php artisan make:controller コントローラ名

コントローラ名は大文字で始まって、○○Controllerという名前にするのが一般的だね

% php artisan make:controller HelloController

例えば、上のようなコマンドを打つと以下のようなコントローラができる

app/Http/Controllers/HelloController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HelloController extends Controller
{
    //
}

?>

※以下namespaceやuseは省略

例えばindexというアクションを書いてみる

app/Http/Controllers/HelloController.php
class HelloController extends Controller
{
    public function index()
    {
        $param = ['msg' => 'Hello'];
        return view('welcome', $param);
    }
}

アクション内でreturnするのは、主にview()やredirect()だね
view()は引数にテンプレート名やパラメータをとって、ブラウザにビューを表示させる
リダイレクトは例えばredirect('/hello/index')って感じ

上の例でいうと、indexの引数は何もないけれど、ルートにパラメータがある場合は引数を設定する
ただし、'http://.../index?id=1'のように?より後ろのパラメータは引数設定しても取得できない
この場合は以下のように引数にRequestオブジェクトの\$requestを設定して、$request->idのように取得すればいい

app/Http/Controllers/HelloController.php
class HelloController extends Controller
{
    public function index(Request $request)
    {
        $param = ['msg' => 'Hello', 'id' => $request->id];
        return view('welcome', $param);
    }
}

おわり

今回はここまで
あんまり説明することがなかったね
それだけ単純だってこと
次はビューやテンプレートについて話していこうかな

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

LINEのトークをメールで転送するボットを作った

はじめに

プログラミング初心者の投稿者は完成に1ヶ月を要しました()
が、この記事を読んでいる皆さんは、もっと短期間で作れると思います(たぶん)。
ほかの記事、サイトを調べれば分かるところ、書くのが面倒くさいところは簡潔に説明します。
投稿者はQiitaも初心者ですのでお手柔らかに…
ネット上にはいろいろ記事がありますが、できるだけ新しいものを見つけるようにしてください。

仕組み

LINE botを作り、トークを転送したいグループに招待します。
誰かがグループに(テキストメッセージ、写真、動画、スタンプ、ファイルなど)を送ると、LINEは、それらを予め設定しておいたところ(Webhook URL)に送ってきます。
受け取り先(heroku)に、送られてきた内容をメールで送るプログラムを書いておけば、メッセージが送られてくるたびに自動でメール転送できるようになります。

LINE Developers 登録

LINE botを作るには、LINE Developersに登録する必要があります。
無料プランで大丈夫です。

登録方法は下で説明します

Messaging API

Messaging APIは、あなたのサービスとLINEユーザーの双方向コミュニケーションを可能にする機能です。
https://developers.line.biz/ja/services/messaging-api/

LINE Developers & Messaging API の設定

まず、LINE公式サイト Messaging APIを利用するにはを読んで、その通りにやってください

つぎに、LINE公式サイト ボットを作成する

1.チャンネルアクセストークンの発行
長期をおすすめします。

2.チャンネルシークレットキー
LINE Developersコンソールの[チャネル基本設定]ページで確認

3.WebHook URLの設定
「https://{HEROKU_APP_NAME}.herokuapp.com/callback」というURL形式注:{HEROKU_APP_NAME}はこれからHerokuで作るアプリ名です。

1,2,3のアプリ名は控えておいてください。

Heroku

Herokuは今回「受取先」になり、プログラムを実行するところです。
Herokuとは何か?、詳しくは割愛させていただきますが、簡単に言えばサーバです。
イメージ→herokuとPHPでオウム返しlinebotを作る(Windows10)

まずは、Herokuの登録をお願いします

Heroku

(参考サイト)※今回はクレジットカードの登録は必要ありません。
Herokuを使いこなすのに必要な術
https://webbibouroku.com/Blog/Article/heroku-getting-started

つぎに、Heroku CLIをインストールしてください

Heroku CLIは、お使いのパソコンのコマンドラインで、Herokuのアプリのログを見るのに使います。

heroku login //ログイン
heroku logs -a アプリ名 -t //ログ常時表示

あとは、Herokuを使いこなすのに必要な術をご覧ください

つぎに、HerokuでWeb Appを作ってください

Herokuを使いこなすのに必要な術 アプリケーションの作成
アプリの名前は、先ほどWebHook URLの設定のところで使ったものにしてください。

さいごに、HerokuでConfig_Varを設定してください

要するに環境変数です。コードにこれらの秘密情報を記載しないことで、安全性を高めます。
Heroku CLIから設定する↓
Herokuで環境変数を設定する方法

Dashboardから入力する↓ (こちらのほうが直感的)
HHeroku公式 Configuration and Config Vars

<入力する環境変数>
CHANNEL_ACCESS_TOKEN → さっき調べたもの
CHANNEL_SECRET → さっき調べたもの
FROM_ADRESS → 送信元アドレス(別途用意してください) 
TO_ADRESS → 送信先アドレス
BCC_ADRESS → 送信先BCCアドレス
APP_PASSWORD → Gmail ※

※は「Gmailから送る」で書きます。申し訳ないですが、いまその設定をするか、あとでもう一度環境変数を追加してください。

Git Hub

投稿者はHerokuにGithubのリポジトリを参照させるようにしました。
こうすることで、コードの変更が簡単になるからです。
Git Hub desktop もあると便利。

Visual Studio Code

投稿者はGit hub DesktopからコードをVisual Studio codeで開いていましたが、コードエディタは何でもいいと思います。

LINE SDK

line-bot-sdkというのを利用します(受取先のプログラムに丸ごと組み込みます。後述します)。これはLINEが、開発者がよく使う機能を実装しておいてくれたものです。例えば、開発者(私や読者の皆さん)は、難しいプログラムを一から書く必要はなく、そこに含まれている関数を自分のプログラムの中で呼び出すだけで、メッセージを受け取ったり送ったりできるのです。
Composerで入れます。

PHPmailer

PHPでメールを送信するときに使います。システム上これを使わなくてもメールを送れますが、Gmailは暗号化されているメールしか送信?受信?しないので、これが必要です。
Composerで入れます。

PHPとComposer

PHPをインストールするのは、調べてもらえればできると思います。

コードエディタで編集していて、エラーが出たときは、構成設定「php.ini」で使いたいものがコメントアウトされていないか調べてください。

あと、そもそもPHPのコードが使えないという場合は「パスが通っていない」可能性があります。PATHを設定する

ComposerはPHPを使う上では必需品らしいです。

これも調べていただければと思います。
簡単に言うと、SDKの更新を簡単にすることができます。投稿者はPHP初心者なので、ここで躓きました。もしどうしてもうまくいかない場合は、Composerを使うことをあきらめ、「line-bot-sdk」と「PHPmailer」を直接Githubからダウンロードして入れてしまってもいいと思います()

Composerの導入&使い方(初心者の復習用)

Composerを使える人は、パソコン上で↓のプログラム(Composer.json)があるところをコマンドプロンプトで開き、

composer install

してください

Heroku(参照しているGithub)に記載するプログラム

Composerする前は、callback.php と composer.json のみ必要です。

全体像

vender
composerしたあとで自動生成される
callback.php
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

// Composerでインストールしたライブラリを一括読み込み
require_once __DIR__ . '/vendor/autoload.php';

//エラーメッセージ用日本語言語ファイルを読み込む
require 'vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php';

//環境変数の読み込み
$CHANNEL_ACCESS_TOKEN=getenv('CHANNEL_ACCESS_TOKEN');
$CHANNEL_SECRET=getenv('CHANNEL_SECRET');
$FROM_ADRESS=getenv('FROM_ADRESS');
$TO_ADRESS=getenv('TO_ADRESS');
$BCC_ADRESS=getenv('BCC_ADRESS');//BCC要らない場合は消す
$APP_PASSWORD=getenv('APP_PASSWORD');

$contents = file_get_contents('php://input'); //POSTの生データを読み込む
$json = json_decode($contents); //生のJSONデータをデコードして変数jsonに格納
$event = $json->events[0]; //変数jsonの中のevent配列の0番目を取り出し、変数eventに格納
$httpClient = new \LINE\LINEBot\HTTPClient\CurlHTTPClient($CHANNEL_ACCESS_TOKEN); // アクセストークンを使いCurlHTTPClientをインスタンス化
$bot = new \LINE\LINEBot($httpClient, ['channelSecret' => $CHANNEL_SECRET]);    //CurlHTTPClientとシークレットを使いLINEBotをインスタンス化

$responseProfile = $bot->getProfile($event->source->userId);
if ($responseProfile->isSucceeded()) {
$profile = $responseProfile->getJSONDecodedBody();
      $user_display_name = $profile['displayName'];

}else{
  error_log("fail to get profile");
}

//タイムゾーン設定
date_default_timezone_set('Asia/Tokyo');

//mailを送っていく
//mbstring の日本語設定
mb_language("Japanese"); 
mb_internal_encoding("UTF-8");

// インスタンスを生成(引数に true を指定して例外 Exception を有効に)
$mail = new PHPMailer(true);

$mail->CharSet = "iso-2022-jp";
$mail->Encoding = "7bit";

//エラーメッセージ用言語ファイルを使用する
$mail->setLanguage('ja', 'vendor/phpmailer/phpmailer/language/');

try{
  //サーバの設定
  $mail->SMTPDebug = SMTP::DEBUG_SERVER;  // デバグの出力を有効に(テスト環境での検証用)
  $mail->isSMTP();   // SMTP を使用
  $mail->Host       = 'smtp.gmail.com';  // ★★★ Gmail SMTP サーバーを指定
  $mail->SMTPAuth   = true;   // SMTP authentication を有効に
  $mail->Username   = $FROM_ADRESS;  // ★★★ Gmail ユーザ名
  $mail->Password   = $APP_PASSWORD;  // ★★★ Gmail パスワード ←アプリパスワード
  $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // ★★★ 暗号化(TLS)を有効に 
  $mail->Port = 587;  //★★★ ポートは 587 

  //差出人アドレス, 差出人名 
  $FROM_NAME = "【自動転送】";
  $mail->setFrom($FROM_ADRESS, mb_encode_mimeheader($FROM_NAME)); 

  // 受信者アドレス, 受信者名(受信者名はオプション)
  $TO_NAME="名前";
  $mail->addAddress($TO_ADRESS, mb_encode_mimeheader($TO_NAME));  
  $mail->addBCC($BCC_ADRESS);//BCC要らない場合は消してください

  //コンテンツ設定
  $mail->isHTML(true);   // HTML形式を指定

  //タイムスタンプのミリ秒を時間に変換。 
  $SendTime = date("y/m/d H:i:s",($event->timestamp)/1000);

  //現在の日付
  $nowdate =  date("m/d");

switch($event->message->type) {
case 'text':
  //メール表題(タイトル)
  $mail->Subject = mb_encode_mimeheader($nowdate);

  //本文(HTML用)
  $bodytext = $SendTime.'//'.$user_display_name.'//'.$event->message->text;
  $mail->Body  = mb_convert_encoding($bodytext,"JIS","UTF-8"); 

  $mail->send();  //送信
  break;

case 'image':
  //メール表題(タイトル)
  $mail->Subject = mb_encode_mimeheader($nowdate);

  //本文(HTML用)
  $bodytext = $SendTime.'//'.$user_display_name.'//'."Image";
  $mail->Body  = mb_convert_encoding($bodytext,"JIS","UTF-8"); 

  $response = $bot->getMessageContent($event->message->id);

  if ($response->isSucceeded()) {
   //バイナリデータをjpgに変換
  $im=imagecreatefromstring($response->getRawBody());
  $filename = date("Ymd-His") . '-' . mt_rand() . '.jpg';
  imagejpeg($im, $filename,10);//10はクオリティ低
  }else {
    error_log($response->getHTTPStatus() . ' ' . $response->getRawBody());
  }

  //画像添付
  $mail->addAttachment($filename);

  $mail->send();  //送信
break;

case 'video':
//処理をここに書く
break;

case 'audio':
//処理をここに書く
break;

case 'file':
//処理をここに書く
break; 

case 'sticker': 
//処理をここに書く
break;

default:
}  

} catch (Exception $e) {
  error_log("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
}
?>
callback.json
{
    "require": {
        "linecorp/line-bot-sdk": "^4.3",
        "ext-gd": "*",
        "ext-mbstring": "*",
        "phpmailer/phpmailer": "^6.1"
    }
}

composer.lock
composerすると自動生成される

Gmailから送る

メールを送信するには、送信元アドレスが必要です。
Herokuを送信元アドレスにすることはできない(多分)ので、別のメールアドレスを使います。投稿者はGmailを使いました。↑のプログラムは、Gmail用です。
Gmail(SMTP)経由でメール送信するを見て、
ボットにメッセージを送って、一度↑のプログラムを実行させたあと、
アプリのパスワードを生成してください。
「Heroku」の「環境変数設定」のところへ戻って、アプリのパスワードを設定してください。

ちなみにメールは

差出人→【自動転送】
タイトル→ 日付
本文→送信時刻//送信者名//メッセージ

となります。送信者名は、送信者がボットと友だちになってないと取得できませんが、エラーはでません。

写真は転送できなかった

Herokuの無料プランを使った場合、写真の重い処理に耐えられずに、Heroku側がプログラムを途中中断してしまうみたいです。
ものすごくサイズが小さい写真(数KB)は転送できますw

動画やファイルはさらに重い処理が要求されると思われますので、今回プログラムは書きませんでしたが、必要でしたらプログラムに組み込めるようにコメントを入れておきました。

ここまでやればOKなはず…

エラーログは、↓で見れます。コマンドプロンプト上で実行してください。

heroku login //ログイン
heroku logs -a アプリ名 -t //ログ常時表示

さいごに

記述不足で本当に申し訳ありません。
どういう風にやったか思い出しながら書いたので、抜けている作業があるかもしれませんが、これで精いっぱいです。すみません!
皆さんのお役に立てば幸いです。

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

LINEのトークをメールで自動転送するボットを作った

はじめに

プログラミング初心者の投稿者は完成に1ヶ月を要しました()
が、この記事を読んでいる皆さんは、もっと短期間で作れると思います(たぶん)。
ほかの記事、サイトを調べれば分かるところ、書くのが面倒くさいところは簡潔に説明します。
投稿者はQiitaも初心者ですのでお手柔らかに…
ネット上にはいろいろ記事がありますが、できるだけ新しいものを見つけるようにしてください。

仕組み

LINE botを作り、トークを転送したいグループに招待します。
誰かがグループに(テキストメッセージ、写真、動画、スタンプ、ファイルなど)を送ると、LINEは、それらを予め設定しておいたところ(Webhook URL)に送ってきます。
受け取り先(heroku)に、送られてきた内容をメールで送るプログラムを書いておけば、メッセージが送られてくるたびに自動でメール転送できるようになります。

LINE Developers 登録

LINE botを作るには、LINE Developersに登録する必要があります。
無料プランで大丈夫です。

登録方法は下で説明します

Messaging API

Messaging APIは、あなたのサービスとLINEユーザーの双方向コミュニケーションを可能にする機能です。
https://developers.line.biz/ja/services/messaging-api/

LINE Developers & Messaging API の設定

まず、LINE公式サイト Messaging APIを利用するにはを読んで、その通りにやってください

つぎに、LINE公式サイト ボットを作成する

1.チャンネルアクセストークンの発行
長期をおすすめします。

2.チャンネルシークレットキー
LINE Developersコンソールの[チャネル基本設定]ページで確認

3.WebHook URLの設定
「https://{HEROKU_APP_NAME}.herokuapp.com/callback」というURL形式注:{HEROKU_APP_NAME}はこれからHerokuで作るアプリ名です。

1,2,3のアプリ名は控えておいてください。

Heroku

Herokuは今回「受取先」になり、プログラムを実行するところです。
Herokuとは何か?、詳しくは割愛させていただきますが、簡単に言えばサーバです。
イメージ→herokuとPHPでオウム返しlinebotを作る(Windows10)

まずは、Herokuの登録をお願いします

Heroku

(参考サイト)※今回はクレジットカードの登録は必要ありません。
Herokuを使いこなすのに必要な術
https://webbibouroku.com/Blog/Article/heroku-getting-started

つぎに、Heroku CLIをインストールしてください

Heroku CLIは、お使いのパソコンのコマンドラインで、Herokuのアプリのログを見るのに使います。

heroku login //ログイン
heroku logs -a アプリ名 -t //ログ常時表示

あとは、Herokuを使いこなすのに必要な術をご覧ください

つぎに、HerokuでWeb Appを作ってください

Herokuを使いこなすのに必要な術 アプリケーションの作成
アプリの名前は、先ほどWebHook URLの設定のところで使ったものにしてください。

さいごに、HerokuでConfig_Varを設定してください

要するに環境変数です。コードにこれらの秘密情報を記載しないことで、安全性を高めます。
Heroku CLIから設定する↓
Herokuで環境変数を設定する方法

Dashboardから入力する↓ (こちらのほうが直感的)
HHeroku公式 Configuration and Config Vars

<入力する環境変数>
CHANNEL_ACCESS_TOKEN → さっき調べたもの
CHANNEL_SECRET → さっき調べたもの
FROM_ADRESS → 送信元アドレス(別途用意してください) 
TO_ADRESS → 送信先アドレス
BCC_ADRESS → 送信先BCCアドレス
APP_PASSWORD → Gmail ※

※は「Gmailから送る」で書きます。申し訳ないですが、いまその設定をするか、あとでもう一度環境変数を追加してください。

Git Hub

投稿者はHerokuにGithubのリポジトリを参照させるようにしました。
こうすることで、コードの変更が簡単になるからです。
Git Hub desktop もあると便利。

Visual Studio Code

投稿者はGit hub DesktopからコードをVisual Studio codeで開いていましたが、コードエディタは何でもいいと思います。

LINE SDK

line-bot-sdkというのを利用します(受取先のプログラムに丸ごと組み込みます。後述します)。これはLINEが、開発者がよく使う機能を実装しておいてくれたものです。例えば、開発者(私や読者の皆さん)は、難しいプログラムを一から書く必要はなく、そこに含まれている関数を自分のプログラムの中で呼び出すだけで、メッセージを受け取ったり送ったりできるのです。
Composerで入れます。

PHPmailer

PHPでメールを送信するときに使います。システム上これを使わなくてもメールを送れますが、Gmailは暗号化されているメールしか送信?受信?しないので、これが必要です。
Composerで入れます。

PHPとComposer

PHPをインストールするのは、調べてもらえればできると思います。

コードエディタで編集していて、エラーが出たときは、構成設定「php.ini」で使いたいものがコメントアウトされていないか調べてください。

あと、そもそもPHPのコードが使えないという場合は「パスが通っていない」可能性があります。PATHを設定する

ComposerはPHPを使う上では必需品らしいです。

これも調べていただければと思います。
簡単に言うと、SDKの更新を簡単にすることができます。投稿者はPHP初心者なので、ここで躓きました。もしどうしてもうまくいかない場合は、Composerを使うことをあきらめ、「line-bot-sdk」と「PHPmailer」を直接Githubからダウンロードして入れてしまってもいいと思います()

Composerの導入&使い方(初心者の復習用)

Composerを使える人は、パソコン上で↓のプログラム(Composer.json)があるところをコマンドプロンプトで開き、

composer install

してください

Heroku(参照しているGithub)に記載するプログラム

Composerする前は、callback.php と composer.json のみ必要です。

全体像

vender
composerしたあとで自動生成される
callback.php
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

// Composerでインストールしたライブラリを一括読み込み
require_once __DIR__ . '/vendor/autoload.php';

//エラーメッセージ用日本語言語ファイルを読み込む
require 'vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php';

//環境変数の読み込み
$CHANNEL_ACCESS_TOKEN=getenv('CHANNEL_ACCESS_TOKEN');
$CHANNEL_SECRET=getenv('CHANNEL_SECRET');
$FROM_ADRESS=getenv('FROM_ADRESS');
$TO_ADRESS=getenv('TO_ADRESS');
$BCC_ADRESS=getenv('BCC_ADRESS');//BCC要らない場合は消す
$APP_PASSWORD=getenv('APP_PASSWORD');

$contents = file_get_contents('php://input'); //POSTの生データを読み込む
$json = json_decode($contents); //生のJSONデータをデコードして変数jsonに格納
$event = $json->events[0]; //変数jsonの中のevent配列の0番目を取り出し、変数eventに格納
$httpClient = new \LINE\LINEBot\HTTPClient\CurlHTTPClient($CHANNEL_ACCESS_TOKEN); // アクセストークンを使いCurlHTTPClientをインスタンス化
$bot = new \LINE\LINEBot($httpClient, ['channelSecret' => $CHANNEL_SECRET]);    //CurlHTTPClientとシークレットを使いLINEBotをインスタンス化

$responseProfile = $bot->getProfile($event->source->userId);
if ($responseProfile->isSucceeded()) {
$profile = $responseProfile->getJSONDecodedBody();
      $user_display_name = $profile['displayName'];

}else{
  error_log("fail to get profile");
}

//タイムゾーン設定
date_default_timezone_set('Asia/Tokyo');

//mailを送っていく
//mbstring の日本語設定
mb_language("Japanese"); 
mb_internal_encoding("UTF-8");

// インスタンスを生成(引数に true を指定して例外 Exception を有効に)
$mail = new PHPMailer(true);

$mail->CharSet = "iso-2022-jp";
$mail->Encoding = "7bit";

//エラーメッセージ用言語ファイルを使用する
$mail->setLanguage('ja', 'vendor/phpmailer/phpmailer/language/');

try{
  //サーバの設定
  $mail->SMTPDebug = SMTP::DEBUG_SERVER;  // デバグの出力を有効に(テスト環境での検証用)
  $mail->isSMTP();   // SMTP を使用
  $mail->Host       = 'smtp.gmail.com';  // ★★★ Gmail SMTP サーバーを指定
  $mail->SMTPAuth   = true;   // SMTP authentication を有効に
  $mail->Username   = $FROM_ADRESS;  // ★★★ Gmail ユーザ名
  $mail->Password   = $APP_PASSWORD;  // ★★★ Gmail パスワード ←アプリパスワード
  $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // ★★★ 暗号化(TLS)を有効に 
  $mail->Port = 587;  //★★★ ポートは 587 

  //差出人アドレス, 差出人名 
  $FROM_NAME = "【自動転送】";
  $mail->setFrom($FROM_ADRESS, mb_encode_mimeheader($FROM_NAME)); 

  // 受信者アドレス, 受信者名(受信者名はオプション)
  $TO_NAME="名前";
  $mail->addAddress($TO_ADRESS, mb_encode_mimeheader($TO_NAME));  
  $mail->addBCC($BCC_ADRESS);//BCC要らない場合は消してください

  //コンテンツ設定
  $mail->isHTML(true);   // HTML形式を指定

  //タイムスタンプのミリ秒を時間に変換。 
  $SendTime = date("y/m/d H:i:s",($event->timestamp)/1000);

  //現在の日付
  $nowdate =  date("m/d");

switch($event->message->type) {
case 'text':
  //メール表題(タイトル)
  $mail->Subject = mb_encode_mimeheader($nowdate);

  //本文(HTML用)
  $bodytext = $SendTime.'//'.$user_display_name.'//'.$event->message->text;
  $mail->Body  = mb_convert_encoding($bodytext,"JIS","UTF-8"); 

  $mail->send();  //送信
  break;

case 'image':
  //メール表題(タイトル)
  $mail->Subject = mb_encode_mimeheader($nowdate);

  //本文(HTML用)
  $bodytext = $SendTime.'//'.$user_display_name.'//'."Image";
  $mail->Body  = mb_convert_encoding($bodytext,"JIS","UTF-8"); 

  $response = $bot->getMessageContent($event->message->id);

  if ($response->isSucceeded()) {
   //バイナリデータをjpgに変換
  $im=imagecreatefromstring($response->getRawBody());
  $filename = date("Ymd-His") . '-' . mt_rand() . '.jpg';
  imagejpeg($im, $filename,10);//10はクオリティ低
  }else {
    error_log($response->getHTTPStatus() . ' ' . $response->getRawBody());
  }

  //画像添付
  $mail->addAttachment($filename);

  $mail->send();  //送信
break;

case 'video':
//処理をここに書く
break;

case 'audio':
//処理をここに書く
break;

case 'file':
//処理をここに書く
break; 

case 'sticker': 
//処理をここに書く
break;

default:
}  

} catch (Exception $e) {
  error_log("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
}
?>
callback.json
{
    "require": {
        "linecorp/line-bot-sdk": "^4.3",
        "ext-gd": "*",
        "ext-mbstring": "*",
        "phpmailer/phpmailer": "^6.1"
    }
}

composer.lock
composerすると自動生成される

Gmailから送る

メールを送信するには、送信元アドレスが必要です。
Herokuを送信元アドレスにすることはできない(多分)ので、別のメールアドレスを使います。投稿者はGmailを使いました。↑のプログラムは、Gmail用です。
Gmail(SMTP)経由でメール送信するを見て、
ボットにメッセージを送って、一度↑のプログラムを実行させたあと、
アプリのパスワードを生成してください。
「Heroku」の「環境変数設定」のところへ戻って、アプリのパスワードを設定してください。

ちなみにメールは

差出人→【自動転送】
タイトル→ 日付
本文→送信時刻//送信者名//メッセージ

となります。送信者名は、送信者がボットと友だちになってないと取得できませんが、エラーはでません。

写真は転送できなかった

Herokuの無料プランを使った場合、写真の重い処理に耐えられずに、Heroku側がプログラムを途中中断してしまうみたいです。
ものすごくサイズが小さい写真(数KB)は転送できますw

動画やファイルはさらに重い処理が要求されると思われますので、今回プログラムは書きませんでしたが、必要でしたらプログラムに組み込めるようにコメントを入れておきました。

ここまでやればOKなはず…

エラーログは、↓で見れます。コマンドプロンプト上で実行してください。

heroku login //ログイン
heroku logs -a アプリ名 -t //ログ常時表示

さいごに

記述不足で本当に申し訳ありません。
どういう風にやったか思い出しながら書いたので、抜けている作業があるかもしれませんが、これで精いっぱいです。すみません!
皆さんのお役に立てば幸いです。

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

【VS code】キーボードショートカット・keybindings.jsonの開き方

【VS code】キーボードショートカット・keybindings.jsonの開き方

PHPでカーソルを単語の外に出すショートカットキーを設定した際にkeybidings.jsonを利用したため、そのファイルの開き方をまとめます。
*動画の後半がjsonファイルの開き方です。

VScode_keyboard.gif

ちなみにtabキーで単語の外に出す設定は以下のリンクで紹介されています.
https://qiita.com/noanoa07/items/0e22d7115a35498e0e08

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

【PHP】POSTメソッドとHTMLエスケープをしよした割り勘の金額を求めるプログラムの作成

本日は、割り勘する時の一人当たりの金額を調べるプログラムを作成しようと思う。

始めに、HTMLエスケープを実行するためのes()を作成していく

util.php
<?php
function es($data){
    if(is_array($data)){
        return array_map(__METHOD__,$data);
    }else{
        return htmlspecialchars($data,ENT_QUOTES,'utf-8');
    }
}

これにより、引数のデータが配列になっている時は、array_mapによって要素一つ一つをエスケープさせ、
esメソッドの配列を作成する。

そして、それ以外は、通常通りエスケープを実行する。

次に、文字のエンコードのチェックを行うckenメソッドを作成する。

util.php
function cken(array $data){
    $result=true;
    foreach($data as $key=>$value){
        //foreach文で配列から値を順に取り出す
 if(is_array($value)){
     //引数が配列のとき
     $value=implode("",value);
      //配列を連結させる
 }
 if(!mb_check_encoding($value)){
     //文字エンコードをチェックしたときに違う値だったという条件
     $result=false;
 break;
 }
 }
 return $result;
}

次にデータの入力の画面を作成する

name_Check.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>フォーム入力</title>

</head>
<body>
    <div>
    <form action="warikan.php"method="post">
//ここではpostメソッドを指定
    <ul>
        <li><label>合計金額<input type="number" name="goukei"></label></li>
        <li><label>人数<input type="number"name="ninzu"></label></li>
        <li><input type="submit" value="割り勘する"></li>
    </ul>
    </form>
    </div>
</body>

これらのデータを処理するwarikan.php というphpファイルを作成する

warikan.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>フォーム入力</title>

</head>
<body>
    <div>
   <?php
   require_once('util.php');//util.php を読み込み

   if(!cken($_POST)){
       $encoding=mb_internal_encoding();
       $err="ENcoding error the expected encoding is".$encoding;
       exit($err);
   }
   $_POST=es($_POST);
   ?>
   <?php
   $errors=[];
   ?>
   <?php
   if(isset($_POST['goukei'])){
$goukei=$_POST['goukei'];
if(!ctype_digit($goukei)){
    $errors[]="合計金額を整数で入力してください";
}}
else{
    $errors[]="合計金額が未設定";
}
   ?>
<?php
if(isset($_POST['ninzu'])){
   $ninzu=$_POST['ninzu'];
if(!ctype_digit($ninzu)){
    $errors[]="人数を整数値で入力してください";
}
elseif($ninzu==0){
    $errors[]="0では割り切れません";
}
}
else{
    $errors[]="人数が未設定";
}
?>
 <?php
 if(count($errors)>0){
echo '<ol class="errror">';
foreach($errors as $value){
    echo "<li>","$value","</li>";
}
echo "</ol>";
?>
<form action="nameCheck.php" method="post">
<ul>
<li><input type="submit"value="戻る"></li>
</ul>
</form>
<?php
}else{
    $amari=$goukei%$ninzu;
    $price=($goukei-$amari)/$ninzu;
    $goukei_fmt=number_format($goukei);
    $price_fmt=number_format($price);
    echo "{$goukei_fmt}{$ninzu}で割ります","<br>";
    echo "1にん当たり{$price_fmt}円を払えば、{$amari}円です";
}

  ?>

    </div>
</body>
   <?php
   require_once('util.php');//util.php を読み込み

   if(!cken($_POST)){
       $encoding=mb_internal_encoding();
       $err="ENcoding error the expected encoding is".$encoding;
       exit($err);
   }
   $_POST=es($_POST);
   ?>

文字のエンコードに失敗した場合に、エラーメッセージを出力しており、
正常に出力しているときには、esメソッドを作成している。

  <?php
   if(isset($_POST['goukei'])){
$goukei=$_POST['goukei'];
if(!ctype_digit($goukei)){
    $errors[]="合計金額を整数で入力してください";
}}
else{
    $errors[]="合計金額が未設定";
}
   ?>
<?php
if(isset($_POST['ninzu'])){
   $ninzu=$_POST['ninzu'];
if(!ctype_digit($ninzu)){
    $errors[]="人数を整数値で入力してください";
}
elseif($ninzu==0){
    $errors[]="0では割り切れません";
}
}
else{
    $errors[]="人数が未設定";
}
?>

こちらでは、$_POSTメソッドがセットされている時のエラーのメッセージを出力するプログラムを作成。

warikan.php
 <?php
 if(count($errors)>0){
echo '<ol class="errror">';
foreach($errors as $value){
    echo "<li>","$value","</li>";
}
echo "</ol>";
?>
<form action="nameCheck.php" method="post">
<ul>
<li><input type="submit"value="戻る"></li>
</ul>
</form>
<?php
}else{
    $amari=$goukei%$ninzu;
    $price=($goukei-$amari)/$ninzu;
    $goukei_fmt=number_format($goukei);
    $price_fmt=number_format($price);
    echo "{$goukei_fmt}{$ninzu}で割ります","<br>";
    echo "1にん当たり{$price_fmt}円を払えば、{$amari}円です";
}
?>
</div>
</body>

割り勘を処理するプログラムは以下の通りである。

  $amari=$goukei%$ninzu;
    $price=($goukei-$amari)/$ninzu;
    $goukei_fmt=number_format($goukei);
    $price_fmt=number_format($price);

これで一人当たりの割り勘の金額を求めるプログラムが完成した。

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

PHP 切り上げ(ceil)、切り捨て(floor)に小数点の桁数を指定できるようにする

PHPには似通った組み込み関数として以下のようなものがあるのだが、

round() : 四捨五入(modeで多少いじれる)
ceil() : 切り上げ
floor() : 切り捨て

桁数を指定でいるのは round() だけで、
ceil(), floor()の二つの関数に関しては、桁指定できる宣言部がない。

そこで、切り上げ、切り捨て処理ができる関数を作ってみた。

切り上げ、切り捨て処理

切り上げ処理(ceil_round_up)

ceil_round_up ( float $value [, int $precision = 1 ] )

第一引数 : 切り上げしたい数字
第二引数 : 切り上げしたい小数点桁数(初期値1)

function ceil_round_up($value, $precision = 1) {
  return round($value + 0.5 * pow(0.1, $precision), $precision, PHP_ROUND_HALF_DOWN);
}

切り捨て処理(ceil_round_down)

floor_round_down ( float $value [, int $precision = 1 ] )

第一引数 : 切り捨てしたい数字
第二引数 : 切り捨てしたい小数点桁数(初期値1)

function floor_round_down($value, $precision = 1) {
  return round($value - 0.5 * pow(0.1, $precision), $precision, PHP_ROUND_HALF_UP);
}

おそらく正常に回るはず。。

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

PHP、MySQLで、Wordpressのような記事サイト制作。絞り込み、ページャー機能について

初めまして。
phpを独学で勉強中です。

phpの投稿フォームからMySQLへ保存し、一覧で書き出したり絞り込み検索ができるシステムを構築しています。
(Wordpressのブログのイメージです。ページャー機能も付けています)

絞り込み検索とページャーの機能はとりあえず動くようになったのですが、下記問題があります。
 ・複数項目の絞り込みに対応できない。
   →現状、キーワード検索のみ
 ・ページャーが、絞り込みをかけてもテーブル内すべてのデータを含んでしまう。
  2ページ目に移動すると絞り込みが解除されてしまう。

こちらどの様に改善するかお分かりになる方いらっしゃれば、ご教授いただけますと幸いです。
お手数おかけしますが、よろしくお願いいたします。

▼MySQL
テーブル名: shop
カラム1: ID(連番)
カラム2: s_name(テキスト)

▼HTML(検索フォーム)

<form action="search.php" method="post">
    <dl><dt>キーワード検索</dt>
        <dd><input type="text" name="sea_name"></dd></dl>
    <dl><dd><input id="send" type="submit" value="送信する"></dd></dl>
</form>

▼php(一覧表示)

<?php
$sea_name = $_POST['sea_name'];

$user = 'user';
$pass = 'pass';
$dsn = 'mysql:host=localhost;dbname=db';
$shop = new PDO($dsn, $user, $pass);

if (isset($_GET['page'])) {
$page = (int)$_GET['page'];
} else {
$page = 1;
}

if ($page > 1) {
// 例:2ページ目の場合は、『(2 × 10) - 10 = 10』
$start = ($page * 3) - 3;
} else {
$start = 0;
}

if(!empty($sea_name)) {
$sea_name = addslashes($sea_name);
$where = "s_name REGEXP '$sea_name' &";
}

if(!empty($where)) {
$where = substr($where, 0, -1);
$where = "WHERE " . $where;
}

$posts = $shop->prepare("
SELECT  *
FROM shop 
{$where}
ORDER BY id DESC
LIMIT {$start}, 3
");

$posts->execute();
$posts = $posts->fetchAll(PDO::FETCH_ASSOC);
?>

    <p>検索条件</p>
    <p>フリーワード:<?php echo $sea_name; ?></p>
            <p>検索結果</p>
    <?php 
    foreach ($posts as $post) {
        echo "
    <li>
    <h3>{$post['s_name']}</h3>
    </li>
    "
    ;}
    ?>
    </ul>
<?php 
$page_num = $shop->prepare("
    SELECT COUNT(*) id
    FROM shop
");
$page_num->execute();
$page_num = $page_num->fetchColumn();

$pagination = ceil($page_num / 3);

for ($x=1; $x <= $pagination ; $x++) { ?>
<a href="?page=<?php echo $x ?>"><?php echo $x; ?></a>
<?php } // End of for ?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelログイン機能の実装について

はじめに

草野と申します。
今回の投稿は、プログラミングスクールを卒業後、同期生に声をかけチームを作り、行っている開発についてアウトプットを行います。自分用のメモのため、文章は拙いですが、少しでも初学者の助けになればと考えています
内容は、表題にもあるとおり、チーム開発によるアプリのログイン機能実装についてです。未熟な点も多いと思います。不備等ありましたらご指摘ください。随時改善して行こうと思います。ちなみに私は、Railsを学びLaravelを次に学んでいるという状況です。

Laravelの導入方法について

Laravelの導入方法については以下の記事を参考にさせて頂きました。

完成品

Image from Gyazo

Image from Gyazo

ログイン機能実装手順

※今回は% php artisan migrateまで行ったあとからの手順になります。DBはmysqlを使用しています。

1.環境設定

  • laravel/uiライブラリのインストール
  • ログイン、新規登録周りファイルのインストール(ここでvueを取り入れています)
  • Node.jsのインストール
  • npmのインストール

2.日本語化

  • config.phpファイル内の設定変更
  • 日本語フォルダインストール
  • ビューの日本語化を行うため、ja.jsonファイル作成

3.ユーザー新規登録の編集

  • usersテーブルへカラムの追加
  • ビューへinputを追加
  • コントローラー編集
  • モデル編集

環境設定

ターミナルで以下のコマンドを実行します。
まずlaravel/uiライブラリをインストールします。

ターミナル
% composer require laravel/ui
  • 変更されるファイル
    • composer.json
    • composer.lock

ログイン、新規登録周りファイルのインストールします。(ここでvueを取り入れています)

ターミナル
% php artisan ui vue --auth
  • 追加されるファイル
    • app/Http/Controllers/HomeController.php
    • resources/js/components/ExampleComponent.vue
    • resources/sass/_variables.scss
    • resources/views/auth/login.blade.php
    • resources/views/auth/passwords/email.blade.php
    • resources/views/auth/passwords/reset.blade.php
    • resources/views/auth/register.blade.php
    • resources/views/auth/verify.blade.php
    • resources/views/home.blade.php
    • resources/views/layouts/app.blade.php
  • 変更されるファイル
    • package.json
    • resources/js/app.js
    • resources/js/bootstrap.js
    • resources/sass/app.scss
    • routes/web.php
    • webpack.mix.js

Node.jsを導入するに当たり、サイトからダウンロードするやり方をしている人(これはYouTubeで見ました)もいましたが、バージョン管理などを行うことを考えるとnodebrewをインストールした方が良いです。

ターミナル
% brew install nodebrew

Nodebrewのインストールを確認します。

ターミナル
% nodebrew -v
ターミナル
nodebrew 1.0.1

Usage:
    nodebrew help                         Show this message
    nodebrew install <version>            Download and install <version> (from binary)
    nodebrew compile <version>            Download and install <version> (from source)
    nodebrew install-binary <version>     Alias of `install` (For backword compatibility)
    nodebrew uninstall <version>          Uninstall <version>
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for `list`
    nodebrew ls-remote                    List remote versions
    nodebrew ls-all                       List remote and installed versions
    nodebrew alias <key> <value>          Set alias
    nodebrew unalias <key>                Remove alias
    nodebrew clean <version> | all        Remove source file
    nodebrew selfupdate                   Update nodebrew
    nodebrew migrate-package <version>    Install global NPM packages contained in <version> to current version
    nodebrew exec <version> -- <command>  Execute <command> using specified <version>

Example:
    # install
    nodebrew install v8.9.4

    # use a specific version number
    nodebrew use v8.9.4

次にNode.jsのインストールします。

ターミナル
% nodebrew install-binary stable

すると下記のようなエラーが出ました。

ターミナル
Fetching: https://nodejs.org/dist/v14.4.0/node-v14.4.0-darwin-x64.tar.gz
Warning: Failed to create the file 
Warning: /Users/kyousuke_kusano/.nodebrew/src/v14.4.0/node-v14.4.0-darwin-x64.t
Warning: ar.gz: No such file or directory

インストール用のディレクトリが内容でしたので下記コマンドで作成し、再度% nodebrew install-binary stableコマンドを実行するとインストールに成功します。

ターミナル
% mkdir -p ~/.nodebrew/src

Node.jsのインストールを確認します。

ターミナル
% nodebrew --version
ターミナル
#上部記述は省略

Example:
    # install
    nodebrew install v8.9.4

    # use a specific version number
    nodebrew use v8.9.4 ←

% nodebrew --versionコマンドで出力された『# use a specific version number』の下部に記載されているコマンドを実行します。するとnot installedと出るのでnodebrew install v8.9.4コマンドを実行し、再度下記コマンドを実行し、not installedと出なければOKです。

ターミナル
% nodebrew use v8.9.4
>use v8.9.4

Node.jsのバージョンが有効化されている事を確認します。
currentに上記と同じバージョンが記載されていれば完了です。

ターミナル
% nodebrew ls

#中略

>current: v8.9.4

下記コマンドでパスを通します。

ターミナル
% echo 'export PATH=$PATH:~/.nodebrew/current/bin/' >> ~/.bashrc

「.bashrc」ファイルがログイン時に読み込まれるように設定していない場合は下記の手順を行います。
「.bash_profile」ファイルを開く

ターミナル
% vi ~/.bash_profile

下記内容を追記し保存します。

ターミナル
source ~/.bashrc

保存後に下記コマンドで「.bashrc」ファイルを読み込みます。

ターミナル
% source ~/.bashrc

パスが通っているか確認します。
command not foundにならなければOKです。
これでNode.jsのインストール完了です。

ターミナル
% npm

次にnpmをインストールします。

ターミナル
% npm install
% npm run dev

下記のようにDONEと表示されれば完了です。

ターミナル
 DONE  Compiled successfully in 5385ms

       Asset     Size   Chunks             Chunk Names
/css/app.css  178 KiB  /js/app  [emitted]  /js/app
  /js/app.js  1.4 MiB  /js/app  [emitted]  /js/app

これでまだ日本語化されていませんが、ログイン機能を実装できています。

日本語化

config.phpファイル内の設定を日本に変更します。

config/app.php 
'timezone' => 'Asia/Tokyo',
#中略
'locale' => 'ja',
#中略
'fallback_locale' => 'ja',
#中略
'faker_locale' => 'ja_JP',

次にインストーラーをダウンロードします。
Laravel 公式が日本語フォルダのインストールを記載してくれているので、利用します。

ターミナル
% php -r "copy('https://readouble.com/laravel/6.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');"

インストーラーを起動します。

ターミナル
% php -f install-ja-lang.php

これでバリデーションエラーなどが日本語化されました。

次にビューの日本語化を行います。
下記のファイルは、% php artisan ui vue --authコマンドを実行した時に生成されたデフォルトのログイン画面のビューファイルです。

resources/views/auth/login.blade.php 
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Login') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('login') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>

                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

#省略

上記ファイル内に__('Login')という記述があります。
__(  )というい記述は多言語化というもので中の英語を変換し表示することができます。
何も触らない場合はそのままLoginと表示されます。
ちなみにhome.blade.phpやwelcome.blade.phpファイル内にも__(  )を追加して日本語化しています。

次に__(  )の英語を日本語化するため、resources/lang内にja.jsonファイルを作成し、下記の通り記述します。

resources/lang/ja.json 
}
  "Login": "ログイン",
  "Register": "新規登録",
  "Forgot Your Password?": "パスワードを忘れた場合",
  "Reset Password": "パスワード再設定",
  "Send Password Reset Link":  "パスワード再設定URLを送信",

  "Name": "お名前",
  "E-Mail Address": "メールアドレス",
  "Password": "パスワード",
  "Confirm Password": "パスワード(確認用)",
  "Remember Me": "ログイン状態を保存",

  "Hello!": "ご利用ありがとうございます。",
  "Reset Password Notification":  "パスワード再設定のお知らせ",
  "You are receiving this email because we received a password reset request for your account.": "あなたのアカウントでパスワード再発行のリクエストがありました。",
  "This password reset link will expire in :count minutes.": "再設定URLの有効期限は :count 分です。",
  "If you did not request a password reset, no further action is required.": "もしパスワード再発行をリクエストしていない場合、操作は不要です。",
  "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser: [:actionURL](:actionURL)": "\":actionText\"ボタンを押しても何も起きない場合、以下URLをコピーしてWebブラウザに貼り付けてください。\n[:actionURL](:actionURL)",
  "Regards": "よろしくお願いいたします",

  "Home": "マイページ",
  "Logout": "ログアウト",
  "Dashboard": "お知らせ",
  "You are logged in!":"ログインしました!"
}

これでビューに日本語が表示されます。

ユーザー新規登録の編集

usersテーブルへカラムの追加を行います。
今回開発するアプリではデフォルトに所属(belongs)というカラムを追加します。
制約等少々デフォルトから編集しております。
※今回% php artisan migrateを行ったあとに環境設定や日本語化を行っていましたので一度% php artisan migrate:rollbackを行った後に下記ファイルにカラム追加の記述を行います。

resources/lang/ja.json 
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name')->unique();
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->string('belongs'); #これを追加
            $table->timestamps();
        });
    }

そしてmigrateを実行し、カラムを追加します。

ターミナル
% php artisan migrate

Railsのgem deviseではなかった\$table->timestamp('email_verified_at')->nullable();と$table->rememberToken();が気になり、少々調べました。
email_verified_atは、新規登録時にメールが送られ、二段階認証を行うためのもののようです。
ローカル環境でも実験できるのかなどは確認できておらず、動作には問題なかったのでこのまま進めます。
rememberTokenは、ログインを保持するためのもので保持期間がデフォルトでは5年に設定されているそうです。今回はその設定は触らず進めます。このカラムは、このチェックを入れてログインすると保存されるようです。
ログイン画面Qiita-2.png

次にビューファイルに所属情報を入力する欄を追加します。
今回は、デフォルトのパスワード(確認用)の部分をコピペしてlabelタグやinputタグ等のオプションを少々触った程度です。

resources/views/auth/register.php 
#省略

                        <div class="form-group row">
                            <label for="belongs" class="col-md-4 col-form-label text-md-right">所属</label>

                            <div class="col-md-6">
                                <input id="berongs" type="text" class="form-control" name="belongs" required autocomplete="organization" value="{{ old('belongs') }}">

                                @error('belongs')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

#省略

次にコントローラー編集します。
バリデーションを追加します。(必須、文字列、最大文字数255文字となります。)

app/Http/Controllers/Auth/RegisterController.php 
#省略

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
            'belongs' => ['required', 'string', 'max:255'],  #ここを追加
        ]);
    }

#省略

DBに保存するための記述をします。

app/Http/Controllers/Auth/RegisterController.php 
#省略

    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'belongs' => $data['belongs'],  #ここを追加
        ]);
    }

#省略

次にモデル編集します。
$fillableは定義した値しかモデルへ入らないようにするものです。

app/User.php 
#省略

    protected $fillable = [
        'name', 'email', 'password', 'belongs',  #belongsを追加
    ];

#省略

これで新規登録に追加したカラムの入力情報がDBへ保存されるようになります。
ここまで読んで頂きありがとうございます。

参考にさせて頂いた記事

環境設定ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
Laravel6 ログイン機能を実装する @ucan-lab
Mac HomebrewでNode.jsをインストールする @miriwo

Node.jsとはーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
初心者向け!3分で理解するNode.jsとは何か?

日本語化ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
Laravelの言語を日本語に変更qiita @mitashun
Laravel の言語を日本語に変更
ログイン認証機能の日本語化(laravel6)

vue.jsの使い方ーーーーーーーーーーーーーーーーーーーーーーーーーーーー
合わせてvue.jsについて調べた際に今後参考になりそうだと思い、閲覧させて頂いた記事をご紹介させて頂きます。
Vue.jsでjQueryと共存させる方法 @g-taguchi
jQuery から Vue.js へのステップアップ @mio3io
1週間でVue.jsをマスターしようと思った時に参考にしたサイト @mimoe

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

YoutubeAPI $ composer require google/apiclient:~2.0で詰まった件について

初投稿になります。
見辛い記事かと思いますがご容赦ください。。

今回、オリジナルポートフォリオを作るにあたって、LaravelからYoutubeAPIを使い動画のアップロードをしたいと考えました。
その際そもそものインストールや認証などでかなり詰まったので備忘録として記事にしておきたいと思った次第です。

環境
AWS EC2 Micro無料枠
Docker Laradockにて構築
PHP7.3
Laravel 6.10.0

まずは公式からサンプルコードをコピーしてきました。
https://developers.google.com/youtube/v3/code_samples/php?hl=ja#resumable_uploads

レジューム可能なアップロード
次のコード サンプルは API の videos.insert メソッドを呼び出して、ユーザーのチャンネルに動画を追加します。また、このコードは Google_MediaFileUpload クラスに true に設定した resumable upload パラメータを付けて使用して、動画を分割してアップロードできるようにしています。

<コードは長いので割愛>

詰まりポイント① composerがUpdateできない。
原因:AWS無料枠のメモリ不足

まずは$ composer require google/apiclient:~2.0をインストールしろと言われるのですが、そのためにはComposerをUpdateする必要があるらしいので、$ composer update を実行すると、下記エラーが出ました。
mmap() failed: [12] Cannot allocate memory

https://tsukada.sumito.jp/2019/11/26/mmap-failed-12-cannot-allocate-memory/
こちらの方法でswapすることで無事解決。
今回は2048GB回しました。
(ちなみにどうやら保存領域から削ってメモリに充てているっぽい?ので、やりすぎると次はInstall容量を確保できなくなる疑惑。。)

これでComposerがUpdateできました。

詰まりポイント② $ composer require google/apiclient:~2.0が実行できない。
原因:composer.json上でリクエストしているバージョンが誤っていた。

満を辞して
$ composer require google/apiclient:~2.0を実行すると、

./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

Problem 1
- The requested package laravel/framework (locked at v6.10.1, required as ^6.18) is satisfiable by laravel/framework[v6.10.1] but these conflict with your requirements or minimum-stability.
Problem 2
- Conclusion: don't install laravel/framework v6.18.19
- Conclusion: don't install laravel/framework v6.18.18
- Conclusion: don't install laravel/framework v6.18.17
- Conclusion: don't install laravel/framework v6.18.16
- Conclusion: don't install laravel/framework v6.18.15
- Conclusion: don't install laravel/framework v6.18.14
- Conclusion: don't install laravel/framework v6.18.13
- Conclusion: don't install laravel/framework v6.18.12

〜〜〜〜〜〜
〜〜〜〜〜〜
〜〜〜〜〜〜

  • Installation request for laravel/framework ^6.18 -> satisfiable by laravel/framework[6.x-dev, v6.18.0, v6.18.1, v6.18.10, v6.18.11, v6.18.12, v6.18.13, v6.18.14, v6.18.15, v6.18.16, v6.18.17, v6.18.18, v6.18.19, v6.18.2, v6.18.3, v6.18.4, v6.18.5, v6.18.6, v6.18.7, v6.18.8, v6.18.9].
  • Installation request for laravelcollective/html ~5.0 -> satisfiable by laravelcollective/html[5.0.x-dev, 5.1.x-dev, 5.2.x-dev, 5.3.x-dev, 5.4.x-dev, 5.5.x-dev, 5.6.x-dev, 5.7.x-dev, 5.8.x-dev, v5.0.0, v5.0.1, v5.0.2, v5.0.3, v5.0.4, v5.0.5, v5.1.0, v5.1.1, v5.1.10, v5.1.11, v5.1.2, v5.1.3, v5.1.4, v5.1.5, v5.1.6, v5.1.7, v5.1.8, v5.1.9, v5.2, v5.2.1, v5.2.2, v5.2.3, v5.2.4, v5.2.5, v5.2.6, v5.3.0, v5.3.1, v5.3.2, v5.4, v5.4.1, v5.4.2, v5.4.3, v5.4.4, v5.4.5, v5.4.6, v5.4.7, v5.4.8, v5.4.9, v5.5, v5.5.1, v5.5.2, v5.5.3, v5.5.4, v5.6, v5.6.1, v5.6.10, v5.6.2, v5.6.3, v5.6.4, v5.6.5, v5.6.6, v5.6.7, v5.6.8, v5.6.9, v5.7, v5.7.1, v5.8.0, v5.8.1].

ズラーっとエラーが吐き出されます。。

問題は2つあって、composer.json上で、
・laravel/frameworkが現在6.10.1なのに、6.18というバージョンをリクエストしていた。
.laravelcollective/htmlを5.0以下でリクエストしているが、安定稼働?なのはこのバージョンじゃなかった。

となり、以下のように修正をすることでインストールが完了しました。

(composer.json)

"require": {
"php": "^7.2",
"aws/aws-sdk-php": "^3.133",
"fideloper/proxy": "^4.0",
"google/apiclient": "~2.0",
"guzzlehttp/guzzle": "^6.5",
"laravel/framework": "^6.10.1",
"laravel/helpers": "^1.1",
"laravel/tinker": "^2.0",
"laravel/ui": "^1.1",
"laravelcollective/html": "~6.0"
},

この記事を書いている段階ではまだYoutubeAPIが使えていないのですが、
一応備忘録として。。またできたら更新します。

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

PHPの環境構築(パスを通すとは)

パスを通すって何?
環境構築って何をすればいいの?

この記事は、PHPの環境構築に関するメモです。
MAMP内のPHPのバージョンを変更する手順を解説します。

PHPのバージョン確認

ターミナルから、php -vコマンドで現在のバージョン情報がわかります。
私の場合、userディレクトリから確認してみると以下の情報が出力されました。

$ php -v
PHP 7.4.7 (cli) (built: Jun 12 2020 00:01:12) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.7, Copyright (c), by Zend Technologies

続いて、MAMPのディレクトリから確認してみると...

$ php -v
php -v
PHP 7.1.33 (cli) (built: Jan 18 2020 13:49:07) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies

少し前のバージョンが入っていました。
プログラミング学習にはMAMPを使っていたので、普段使用しているphpのバージョンは7.1.33 (cli)ということがわかりました。

phpのパスの確認

パスとは「ショートカット」とか、「エイリアス」といったの意味です。
ターミナルから、which phpコマンドで現在のパス情報がわかります。
MAMPで実行してみると、以下の情報が出力されました。

$ which php
/usr/bin/php

「パスを通す」とは、phpを格納する場所を指定して、使用するphpを選択することになります。
Screen Shot 0032-06-21 at 5.09.30.png

Finderからたどってみても、phpが格納されているディレクトリを確認することができます。

.bash_profileの変更

homeディレクトリ(finderで家のマークがある場所)にある.bash_profileの設定を変更します。
Screen Shot 0032-06-21 at 5.42.13.png
.から始まるファイルは隠しファイルなので、ls -aコマンドで確認できます。

iMac:~ tetsu$ cd
iMac:~ tetsu$ ls -a
.bash_profile       .sh_history     Movies
.bash_sessions      .ssh            Music

viコマンドでviエディタを立ち上げます。

$ vi .bash_profile

viエディタは、最初に「コマンドモード」で立ち上がります。
文字キーをタイプすると、「インサートモード」になり、キー入力が可能になります。
(※一番下に-- INSERT --と表示される)

export PATH=$HOME/.nodebrew/current/bin:$PATH
eval "$(rbenv init -)"
 export PATH=~/.npm-global/bin:$PATH
 export PATH=/Applications/MAMP/bin/php/php7.4.2/bin:$PATH (※この行を追記してバージョンを指定)

編集が終わったらescボタンで「コマンドモード」に変更して、:wqコマンド(保存 & 終了)で編集を終えます。

パス変更の確認

きちんと.bash_profileの変更が反映されているかを確認したい場合はcatコマンドを使用します。

$ cat .bash_profile
export PATH=$HOME/.nodebrew/current/bin:$PATH
eval "$(rbenv init -)"
 export PATH=~/.npm-global/bin:$PATH 
 export PATH=/Applications/MAMP/bin/php/php7.4.2/bin:$PATH

最後に、もう一度MAMPのディレクトリからphp -vで確認するとバージョンが変更されていることが分かります。

$ php -v
PHP 7.4.7 (cli) (built: Jun 12 2020 00:01:12) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.7, Copyright (c), by Zend Technologies

無事、PHPのバージョンを変更することができました。

参考

viエディタの使い方

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

PhpStormのInspectCode

概要

PhpStormにあるコードチェック機能の「コード・インスペクション」を触ったので備忘録。

※PhpStorm : JetBrainsが提供しているPHP用のIDE(統合開発環境)

利用方法

  • 上部メニューバーの「Code」 > 「Inspect Code...」
  • ファイルやディレクトリを選択して右クリック > 「Inspect Code...」

設定内容

  • Inspection scope
    • チェック範囲の指定
    • 下記の3項目から選択
      • Whole project (プロジェクト全体)
      • Directory '...XXX' (指定されているディレクトリ配下)
      • Custom scope (自身で登録したカスタム設定を使用)
  • Inspection profile
    • チェック項目を指定
    • 特定言語やサービスのチェックを選択やカスタマイズ可能

Custom scope

  • ダイアログ左上部の「+」「-」でスコープ(チェック範囲)の設定を作成/削除できる
    • Name: スコープ名 (先述の Custom scope でプルダウン表示される)
    • Patten: スコープのパターンを記述 (下記のファイル表示でも指定可)
    • ファイル表示: ディレクトリ構成が表示されており、「Include」などのボタンで指定(上記のPattenにも連動される)

Patten 設定
簡単な指定方法を紹介

  • 拡張子を指定
    • file[プロジェクト名]:xxx/*.php
    • プロジェクトのxxxディレクトリ配下にある.phpファイルを指定 (ディレクトリ直下以外も再帰的に指定)
  • 再帰的にディレクトリ配下を全て指定
    • file[プロジェクト名]:xxx//*
    • / (ディレクトリ直下のみ指定)
    • // (ディレクトリ内を再帰的に指定)
    • 拡張子指定した場合は "/" で再帰的に指定するようです
  • 除外指定
    • !file[プロジェクト名]:xxx//*.html
    • プロジェクトのxxxディレクトリ配下にあるHTMLファイルを除外
  • 組み合わせ
    • AND指定 : &&
    • OR指定 : ||

おまけ

プロジェクト全体のチェックではPCのメモリ使用や負荷が大きかっため機能毎に分割してチェックしました。
問題ないコーディングでも構文的なチェックでWarning出る可能性があるため、「Inspection profile」でチェック項目を変更するか、「インスペクションの抑止」のコメント追加で対処が可能です。

参照

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

PhpStormでのコード解析 ~InspectCode設定~

概要

PhpStormにあるコードチェック機能の「コード・インスペクション」を触ったので備忘録。

※PhpStorm : JetBrainsが提供しているPHP用のIDE(統合開発環境)

利用方法

  • 上部メニューバーの「Code」 > 「Inspect Code...」
  • ファイルやディレクトリを選択して右クリック > 「Inspect Code...」

設定内容

  • Inspection scope
    • チェック範囲の指定
    • 下記の3項目から選択
      • Whole project (プロジェクト全体)
      • Directory '...XXX' (指定されているディレクトリ配下)
      • Custom scope (自身で登録したカスタム設定を使用)
  • Inspection profile
    • チェック項目を指定
    • 特定言語やサービスのチェックを選択やカスタマイズ可能

Custom scope

  • ダイアログ左上部の「+」「-」でスコープ(チェック範囲)の設定を作成/削除できる
    • Name: スコープ名 (先述の Custom scope でプルダウン表示される)
    • Patten: スコープのパターンを記述 (下記のファイル表示でも指定可)
    • ファイル表示: ディレクトリ構成が表示されており、「Include」などのボタンで指定(上記のPattenにも連動される)

Patten 設定
簡単な指定方法を紹介

  • 拡張子を指定
    • file[プロジェクト名]:xxx/*.php
    • プロジェクトのxxxディレクトリ配下にある.phpファイルを指定 (ディレクトリ直下以外も再帰的に指定)
  • 再帰的にディレクトリ配下を全て指定
    • file[プロジェクト名]:xxx//*
    • / (ディレクトリ直下のみ指定)
    • // (ディレクトリ内を再帰的に指定)
    • 拡張子指定した場合は "/" で再帰的に指定するようです
  • 除外指定
    • !file[プロジェクト名]:xxx//*.html
    • プロジェクトのxxxディレクトリ配下にあるHTMLファイルを除外
  • 組み合わせ
    • AND指定 : &&
    • OR指定 : ||

おまけ

プロジェクト全体のチェックではPCのメモリ使用や負荷が大きかっため機能毎に分割してチェックしました。
問題ないコーディングでも構文的なチェックでWarning出る可能性があるため、「Inspection profile」でチェック項目を変更するか、「インスペクションの抑止」のコメント追加で対処が可能です。

参照

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

PHP for文を用いて配列に配列を確認する

目的

  • for文を用いて全く同じ配列データをさらに配列に格納する方法を下記に記載する

実施環境

  • ハードウェア環境
項目 情報
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

コードの例

  • ["test01", "test02", "test03"]という配列さらに配列$main_arrayに10回格納するコードを下記に記載する。

    $i = 0;
    
    for ($i = 1; $i <= 10; $i++){                
        $main_array[] = [
            "test01",
            "test02",
            "test03",
        ];
    }
    
    var_dump($main_array);
    
  • 上記コードの実行結果を下記に記載する。

    array(10) {
      [0]=>
      array(3) {
        [0]=>
        string(6) "test01"
        [1]=>
        string(6) "test02"
        [2]=>
        string(6) "test03"
      }
      [1]=>
      array(3) {
        [0]=>
        string(6) "test01"
        [1]=>
        string(6) "test02"
        [2]=>
        string(6) "test03"
      }
      [2]=>
      array(3) {
        [0]=>
        string(6) "test01"
        [1]=>
        string(6) "test02"
        [2]=>
        string(6) "test03"
      }
      [3]=>
      array(3) {
        [0]=>
        string(6) "test01"
        [1]=>
        string(6) "test02"
        [2]=>
        string(6) "test03"
      }
      [4]=>
      array(3) {
        [0]=>
        string(6) "test01"
        [1]=>
        string(6) "test02"
        [2]=>
        string(6) "test03"
      }
      [5]=>
      array(3) {
        [0]=>
        string(6) "test01"
        [1]=>
        string(6) "test02"
        [2]=>
        string(6) "test03"
      }
      [6]=>
      array(3) {
        [0]=>
        string(6) "test01"
        [1]=>
        string(6) "test02"
        [2]=>
        string(6) "test03"
      }
      [7]=>
      array(3) {
        [0]=>
        string(6) "test01"
        [1]=>
        string(6) "test02"
        [2]=>
        string(6) "test03"
      }
      [8]=>
      array(3) {
        [0]=>
        string(6) "test01"
        [1]=>
        string(6) "test02"
        [2]=>
        string(6) "test03"
      }
      [9]=>
      array(3) {
        [0]=>
        string(6) "test01"
        [1]=>
        string(6) "test02"
        [2]=>
        string(6) "test03"
      }
    }
    
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む