20200814のPHPに関する記事は16件です。

foreachで連想配列の要素を変更したい場合は参照渡しする

参照渡しとは

この内容については、独習PHP 第3版で以下のように説明されています。

変数に値を格納→厳密にはコンピュータ上に用意されたメモリに格納

メモリにはそれぞれの場所を表す番号(アドレス)が振られている。

変数とは、値の格納先(アドレス)に対してつけられた名札みたいなもの

「=」演算子で変数を代入する=メモリ上の値を別のアドレスにコピーする

ref_val.php
<?php
$x = 1;
$y = $x;   // $xの値を$yにコピー
$x = 5;    // $xの値を変更
print $y;  // 結果:1($xの変更に影響しない)
?>

参照渡し_概念1.png

一方、参照(リファレンス)による代入は、メモリ上のアドレスそのものを引き渡す代入のこと

ref_ref.php
<?php
$x = 1;
$y = &$x;   // $xのアドレスを$yにコピー
$x = 5;    // $xの値を変更
print $y;  // 結果:5($xの変更に影響する)
?>

参照渡し_概念2.png

foreach命令→デフォルトでは配列要素を値渡し

foreach_val.php
<?php
$data = ['渋谷区','豊島区','品川区','新宿区'];
foreach($data as $value){
   $value = '東京都'.$value;
}
print_r($data) // 結果:Array([0] => 渋谷区、[1] => 豊島区、[2] => 品川区、[3] => 新宿区
?>

したがって、連想配列の要素を変更したい場合は「&」をつけて参照渡しする

foreach_ref.php
<?php
$data = ['渋谷区','豊島区','品川区','新宿区'];
foreach($data as &$value){
   $value = '東京都'.$value;
}
print_r($data) // 結果:Array([0] => 東京都渋谷区、[1] => 東京都豊島区、[2] => 東京都品川区、[3] => 東京都新宿区
?>

参考文献

この記事は以下の情報を参考にして執筆しました。

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

Carbonを用いて期間検索機能を作る

はじめに

日付検索する時にDBには年月日時分秒まで登録されているけど期間(年月日)で検索したい時ありませんか。
そんな時はCarbonを使って検索機能を作りましょう。

Carbonを用いて期間検索機能を作る

ある事柄が始まった日時$period[start_at]の検索で、検索期間(年月日)のはじめを$request['start_at_from']、検索期間(年月日)の終わりを$request['start_at_to']として$requestの配列で受け取ったとします。

$period['start_at_from']$period['start_at_to']は(2020-08-13のように)年月日で受け取っているとしてCarbonを用いて00:00:00と日時分を仮置きします。

//2020-08-13
$period['start_at_from']
//2020-08-13 00:00:00
Carbon::parse($period['start_at_from'])

startOfDay()メソッドで1日の中の一番初めの時刻(00:00:00)、endOfDay()メソッドで1日の中の一番終わりの時刻(23:59:59)を取得できます。

//2020-08-13 00:00:00
Carbon::parse($period['start_at_from'])->startOfDay());
//2020-08-13 23:59:59
Carbon::parse($request['start_at_to'])->endOfDay()); 

whereは第一引数にカラム名、第二引数に比較演算子、またはSQLで使うオペレータ、第三引数に比較する値を指定します。
上記までのものも合わせて下記に実装をまとめます。

Controller.php
use Carbon\Carbon;

public function periodSearch(Request $request)
{
    $inputs = $request->validatedValues();

    $this->where('start_at', '>=', Carbon::parse($request['start_at_from'])->startOfDay()); 
    $this->where('start_at', '<=', Carbon::parse($request['start_at_to'])->endOfDay()); 
         } 

return view('periodSearch', ['period'=> $period]);

おわりに

Carbonは日時を操作するのに大変便利なので使いこなせるようにCarbonはどんなことができるか調べておくと良いかもしれません。

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

php.ini ディレクティブ モードと設定ファイル

PHP でファイルアップロード処理を実装する場合
雑に .htaccesspost_max_sizeupload_max_filesize を変更したり
メモリでコケたら ini_set()memory_limit を変更することがある
あるよね?
(環境構築時に要件に応じた設定ができていれば、それがベストなんだろうけど)

今回、upload_tmp_dir ディレクティブを変更する際、恥ずかしながら初めてモードの存在を知ったので
自分への戒め

PHP ディレクティブ モード

PHP ディレクティブには
以下のいずれかのモードが定義されており、モード毎に変更を指定可能な方法が異なる
https://www.php.net/manual/ja/configuration.changes.modes.php

例えば upload_tmp_dir ディレクティブは PHP_INI_SYSTEM モードであるため
.htaccessini_set() では変更できない
https://www.php.net/manual/ja/ini.list.php

設定ファイル

設定ファイル 区分 影響範囲 備考
php.ini PHP 全体
httpd.conf Apache 全体
.htaccess Apache ディレクトリ単位
web.config IIS ディレクトリ単位
.user.ini PHP ディレクトリ単位 PHP 5.3.0 以降
ini_set() PHP スクリプトレベル

ただし、Web サーバ(Apache、IIS)により使用できる設定ファイルが異なる

例えば PHP_INI_PERDIR モードのディレクティブを変更する場合
よくある Apache サーバの場合は .htaccesshttpd.conf を使用できるが
Windows 上で稼働している IIS サーバの場合は web.config を使用して設定を行う必要がある
または .user.ini を使用した PHP 側での変更も可能

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

MySQLで複数のDBをまたいで使用する

概要

サービスが大きくなったりしてDBが増えたときに、複数のDBをまたいで使用する場合の書き方です。

環境

PHP 5.3.29
FuelPHP 1.7.3
MySQL 5.5.61

本題

方法1

まず、FuelPHPのfuel/app/config以下にあるdb.phpに設定を書きます。書くファイルはそれぞれの環境に応じたものにします。(開発環境であればdevelopment/db.php
ここに複数の設定を書けばクエリを発行する際に各DBを指定して使用できます。

db.php
<?php
return array{
  'db01' => array(
    'type'        => 'pdo',
    'connection'  => array(
      'dsn'        => 'mysql:host=localhost;dbname=db01',
      'username'   => '',
      'password'   => '',
    ),
  ),
  'db02' => array(
    'type'        => 'pdo',
    'connection'  => array(
      'dsn'        => 'mysql:host=localhost;dbname=db02',
      'username'   => '',
      'password'   => '',
    ),
  ),
  'db03' => array(
    'type'        => 'pdo',
    'connection'  => array(
      'dsn'        => 'mysql:host=localhost;dbname=db03',
      'username'   => '',
      'password'   => '',
    ),
  ),
);
?>

各パラメータは各々の環境に合わせます。

次に、モデルにクエリを発行する文を書きます。

examplemodel.php
<?php
namespace Example;
class Model_Examplemodel extends \Model
{
  public static function get_example()
  {
    $get = \DB::select()
    ->from('tablename')
    ->execute('db01')
    return $get;
  }
}
?>

このようにexecute()の引数にdb.phpで定義した文字を入れればそのDBを参照してくれます。

方法2

セレクトするテーブルの情報にDB名を加える形でも可能です。

examplemodel.php
<?php
namespace Example;
class Model_Examplemodel extends \Model
{
  public static function get_example()
  {
    $get = \DB::select()
    ->from('db01.tablename')
    ->execute()
    return $get;
  }
}
?>

joinする場合

joinする場合は、方法1をとるとすべてそのDBにあるテーブルが参照されます。
ですので、db01・db02それぞれからテーブルを参照する必要がある場合は方法2、もしくは両方を合わせて使う必要があります。

examplemodel.php
<?php
namespace Example;
class Model_Examplemodel extends \Model
{
  public static function get_example()
  {
    $get = \DB::select()
    ->from('db01.table01')
    ->join('db02.table02')
    ->on('table01.column01', '=', 'table02.column01')
    ->execute()
    return $get;
  }
}
?>

カラム名が被っている場合はその点にも注意します。

終わりに

FuelPHPを書いていて、発行されたクエリが簡単にわかる方法はないかなーと思って探しています。知っている方いましたらコメントいただけるとすごく助かります。

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

Laravel の Eloquent モデルでは where に like が使えないという話

コロナから Laravel 入門,3日目で盛大にコケる

コロナでフルリモート勤務になり,平日・休日問わず,自宅警備員として絶賛激務中のおじさんです。

通勤時間がなくなったことをきっかけに,「いつか勉強しよう」と思っていた Laravel を触ってみることに……

Laravel 習得の第一関門(?)とも言える Eloquent で,さっそく盛大にコケましたので,記事にしてみました。

マサカリ歓迎案件です。

Eloquent でコケるストーリー

おじさんの作業した条件です。

利用環境

  • データベース:SQLite
  • Laravel バージョン:7.24.0

テーブルとモデル

以下のような employee テーブルを作りました。

苗字と名前を,半角スペース1文字で区切ったものを,ひとまとめに連結して name カラムに入れています(そんな定義のテーブルを作ること自体,雑すぎるというざわつきが聞こえてきそうです)。

id name
1 山田 太郎
2 山田 花子
3 鈴木 一郎

対応するモデルも作成します。

Employee.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Employee extends Model
{
    protected $table = 'employee';
    protected $guarded = array('id');
}

実際のコード

HTML 画面に入力された「名前」を取得し,LIKE であいまい検索をしようとしたのですが,ここでトラブル発生……

EmployeeController.php
<?php

namespace App\Http\Controllers;

use App\Employee;
use Illuminate\Http\Request;

class EmployeeController extends Controller
{
    public function index(Request $request)
    {
        $employee = Employee::all();

        // 「名前」による絞り込み
        $name = $request->query('name');
        if (!is_null($name)) {
            $employee = $employee->where('name', 'like', '%' . $name . '%');
        }

        return response()->json([
            'responseJSON' => $employee->toArray(),
        ], 200);
    }
}

遭遇したトラブル

前述のとおり employee テーブルには,二人の「山田」さんを登録しておいたのですが,HTML 画面で「山田」と入力しても,検索結果がゼロ件になってしまいます。

ちょうど,以下のコードでは,変数 $name に「山田」が入った状態。

前方と後方に付加した '%' 文字によって,ワイルドカード検索が起きるという期待は,見事,裏切られてしまいます orz

EmployeeController.php(抜粋)
    // 「名前」による絞り込み
    $name = $request->query('name');
    if (!is_null($name)) {
        $employee = $employee->where('name', 'like', '%' . $name . '%');
    }

調べて分かったこと

結論から言うと,Illuminate\Database\Eloquent\Collection での where 利用は,like が使えないようです。

API 仕様書では,第二パラメータ $operator で指定できるような思わせぶりなので,すっかり,ハマってしまいました。

api.png

どう対応したか

結局,like 利用をあきらめて,完全一致検索に仕様変更しました。

そもそも……練習用だからと横着して,「苗字 + 半角スペース1文字 + 名前」をひとまとめにして,name カラムにするというテーブルの作り方がマズイですね。

きちんと「苗字」と「名前」は別カラムに分けたうえで,完全一致検索という仕様にするのが,いちばんスッキリするかと。

EmployeeController.php(変更前)
    // 「名前」による絞り込み
    $name = $request->query('name');
    if (!is_null($name)) {
        $employee = $employee->where('name', 'like', '%' . $name . '%');
    }
EmployeeController.php(変更後)
    // 「名前」による絞り込み
    $name = $request->query('name');
    if (!is_null($name)) {
        $employee = $employee->where('name', $name);
    }

他のアプローチ

どうしても,あいまい検索が必要ならば,Eloquent モデルで組むのをあきらめて,クエリビルダを使う方式にすると,like 指定での検索ができそうです(未検証)。

公式ドキュメントを見ても,like 指定ができないという記述は発見できずじまいでした。

https://laravel.com/docs/7.x/eloquent

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

Laravel の Eloquent モデルでは where に like が使えないとハマったときの話

コロナから Laravel 入門,3日目で盛大にコケる

コロナでフルリモート勤務になり,平日・休日問わず,自宅警備員として絶賛激務中のおじさんです。

通勤時間がなくなったことをきっかけに,「いつか勉強しよう」と思っていた Laravel を触ってみることに……

Laravel 習得の第一関門(?)とも言える Eloquent で,さっそく盛大にコケましたので,記事にしてみました。

マサカリ歓迎案件です。

Eloquent でコケるストーリー

おじさんの作業した条件です。

利用環境

  • データベース:SQLite
  • Laravel バージョン:7.24.0

テーブルとモデル

以下のような employee テーブルを作りました。

苗字と名前を,半角スペース1文字で区切ったものを,ひとまとめに連結して name カラムに入れています(そんな定義のテーブルを作ること自体,雑すぎるというざわつきが聞こえてきそうです)。

id name
1 山田 太郎
2 山田 花子
3 鈴木 一郎

対応するモデルも作成します。

Employee.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Employee extends Model
{
    protected $table = 'employee';
    protected $guarded = array('id');
}

実際のコード

HTML 画面に入力された「名前」を取得し,like であいまい検索をしようとしたのですが,ここでトラブル発生……

EmployeeController.php
<?php

namespace App\Http\Controllers;

use App\Employee;
use Illuminate\Http\Request;

class EmployeeController extends Controller
{
    public function index(Request $request)
    {
        $employee = Employee::all();

        // 「名前」による絞り込み
        $name = $request->query('name');
        if (!is_null($name)) {
            $employee = $employee->where('name', 'like', '%' . $name . '%');
        }

        return response()->json([
            'responseJSON' => $employee->toArray(),
        ], 200);
    }
}

遭遇したトラブル

前述のとおり employee テーブルには,二人の「山田」さんを登録しておいたのですが,HTML 画面で「山田」と入力しても,検索結果がゼロ件になってしまいます。

ちょうど,以下のコードでは,変数 $name に「山田」が入った状態。

前方と後方に付加した '%' 文字によって,ワイルドカード検索が起きるという期待は,見事,裏切られてしまいます orz

EmployeeController.php(抜粋)
    // 「名前」による絞り込み
    $name = $request->query('name');
    if (!is_null($name)) {
        $employee = $employee->where('name', 'like', '%' . $name . '%');
    }

調べて分かったこと

結論から言うと,Illuminate\Database\Eloquent\Collection での where 利用は,like が使えないようです。

結論から言うと,おじさんの試みたモデルの作成方法がマズかったというのが,原因でした(@nunulk さんのアドバイスで解決できました)。

EmployeeController.php(変更前)
    $employee = Employee::all();

    return response()->json([
        'responseJSON' => $employee->toArray(),
    ], 200);
EmployeeController.php(変更後)
    $employee = Employee::query();

    return response()->json([
        'responseJSON' => $employee->get(),
    ], 200);

まとめ

モデルクラスに対して,all() を呼ぶと Illuminate\Database\Eloquent\Collection インスタンスが返って来るのに対し,query() を呼ぶと,Illuminate\Database\Eloquent\Builder インスタンスが返るという差異があり,それぞれのインスタンスでは,挙動が異なるということへの気づきを得られました。

これからもしっかり,API 仕様書を読み込んで勉強していこうと思う出来事でした。

@nunulk さん,貴重なアドバイス,感謝いたします。

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

Laravel の Eloquent モデルでは where に like が使えない!?とハマったときの話

コロナから Laravel 入門,3日目で盛大にコケる

コロナでフルリモート勤務になり,平日・休日問わず,自宅警備員として絶賛激務中のおじさんです。

通勤時間がなくなったことをきっかけに,「いつか勉強しよう」と思っていた Laravel を触ってみることに……

Laravel 習得の第一関門(?)とも言える Eloquent で,さっそく盛大にコケましたので,記事にしてみました。

マサカリ歓迎案件です。

Eloquent でコケるストーリー

おじさんの作業した条件です。

利用環境

  • データベース:SQLite
  • Laravel バージョン:7.24.0

テーブルとモデル

以下のような employee テーブルを作りました。

苗字と名前を,半角スペース1文字で区切ったものを,ひとまとめに連結して name カラムに入れています(そんな定義のテーブルを作ること自体,雑すぎるというざわつきが聞こえてきそうです)。

id name
1 山田 太郎
2 山田 花子
3 鈴木 一郎

対応するモデルも作成します。

Employee.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Employee extends Model
{
    protected $table = 'employee';
    protected $guarded = array('id');
}

実際のコード

HTML 画面に入力された「名前」を取得し,like であいまい検索をしようとしたのですが,ここでトラブル発生……

EmployeeController.php
<?php

namespace App\Http\Controllers;

use App\Employee;
use Illuminate\Http\Request;

class EmployeeController extends Controller
{
    public function index(Request $request)
    {
        $employee = Employee::all();

        // 「名前」による絞り込み
        $name = $request->query('name');
        if (!is_null($name)) {
            $employee = $employee->where('name', 'like', '%' . $name . '%');
        }

        return response()->json([
            'responseJSON' => $employee->toArray(),
        ], 200);
    }
}

遭遇したトラブル

前述のとおり employee テーブルには,二人の「山田」さんを登録しておいたのですが,HTML 画面で「山田」と入力しても,検索結果がゼロ件になってしまいます。

ちょうど,以下のコードでは,変数 $name に「山田」が入った状態。

前方と後方に付加した '%' 文字によって,ワイルドカード検索が起きるという期待は,見事,裏切られてしまいます orz

EmployeeController.php(抜粋)
    // 「名前」による絞り込み
    $name = $request->query('name');
    if (!is_null($name)) {
        $employee = $employee->where('name', 'like', '%' . $name . '%');
    }

調べて分かったこと

結論から言うと,Illuminate\Database\Eloquent\Collection での where 利用は,like が使えないようです。

結論から言うと,おじさんの試みたモデルの作成方法がマズかったというのが,原因でした(@nunulk さんのアドバイスで解決できました)。

EmployeeController.php(変更前)
    $employee = Employee::all();

    return response()->json([
        'responseJSON' => $employee->toArray(),
    ], 200);
EmployeeController.php(変更後)
    $employee = Employee::query();

    return response()->json([
        'responseJSON' => $employee->get(),
    ], 200);

まとめ

モデルクラスに対して,all() を呼ぶと Illuminate\Database\Eloquent\Collection インスタンスが返って来るのに対し,query() を呼ぶと,Illuminate\Database\Eloquent\Builder インスタンスが返るという差異があり,それぞれのインスタンスでは,挙動が異なるということへの気づきを得られました。

これからもしっかり,API 仕様書を読み込んで勉強していこうと思う出来事でした。

@nunulk さん,貴重なアドバイス,感謝いたします。

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

Laravel の Eloquent モデルでは where に like が使えない!?とハマった話

コロナから Laravel 入門,3日目で盛大にコケる

コロナでフルリモート勤務になり,平日・休日問わず,自宅警備員として絶賛激務中のおじさんです。

通勤時間がなくなったことをきっかけに,「いつか勉強しよう」と思っていた Laravel を触ってみることに……

Laravel 習得の第一関門(?)とも言える Eloquent で,さっそく盛大にコケましたので,記事にしてみました。

マサカリ歓迎案件です。

Eloquent でコケるストーリー

おじさんの作業した条件です。

利用環境

  • データベース:SQLite
  • Laravel バージョン:7.24.0

テーブルとモデル

以下のような employee テーブルを作りました。

苗字と名前を,半角スペース1文字で区切ったものを,ひとまとめに連結して name カラムに入れています(そんな定義のテーブルを作ること自体,雑すぎるというざわつきが聞こえてきそうです)。

id name
1 山田 太郎
2 山田 花子
3 鈴木 一郎

対応するモデルも作成します。

Employee.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Employee extends Model
{
    protected $table = 'employee';
    protected $guarded = array('id');
}

実際のコード

HTML 画面に入力された「名前」を取得し,like 演算子であいまい検索をしようとしたのですが,ここでトラブル発生……

EmployeeController.php
<?php

namespace App\Http\Controllers;

use App\Employee;
use Illuminate\Http\Request;

class EmployeeController extends Controller
{
    public function index(Request $request)
    {
        $employee = Employee::all();

        // 「名前」による絞り込み
        $name = $request->query('name');
        if (!is_null($name)) {
            $employee = $employee->where('name', 'like', '%' . $name . '%');
        }

        return response()->json([
            'responseJSON' => $employee->toArray(),
        ], 200);
    }
}

遭遇したトラブル

前述のとおり employee テーブルには,二人の「山田」さんを登録しておいたのですが,HTML 画面で「山田」と入力しても,検索結果がゼロ件になってしまいます。

ちょうど,以下のコードでは,変数 $name に「山田」が入った状態。

前方と後方に付加した '%' 文字によって,ワイルドカード検索が起きるという期待は,見事,裏切られてしまいます orz

EmployeeController.php(抜粋)
    // 「名前」による絞り込み
    $name = $request->query('name');
    if (!is_null($name)) {
        $employee = $employee->where('name', 'like', '%' . $name . '%');
    }

調べて分かったこと

結論から言うと,Illuminate\Database\Eloquent\Collection での where 利用は,like 演算子が使えないようです。

結論から言うと,おじさんの試みたモデルの作成方法がマズかったというのが,原因でした(@nunulk さんのアドバイスで解決できました)。

EmployeeController.php(変更前)
    $employee = Employee::all();

    return response()->json([
        'responseJSON' => $employee->toArray(),
    ], 200);
EmployeeController.php(変更後)
    $employee = Employee::query();

    return response()->json([
        'responseJSON' => $employee->get(),
    ], 200);

まとめ

モデルクラスに対して,all() を呼ぶと Illuminate\Database\Eloquent\Collection インスタンスが返って来るのに対し,query() を呼ぶと,Illuminate\Database\Eloquent\Builder インスタンスが返るという差異があり,それぞれのインスタンスでは,where 関数の挙動が異なるということへの気づきを得られました。

  • Illuminate\Database\Eloquent\Collection::wherelike 演算子が使えない(動かない。そういう仕様?
  • Illuminate\Database\Eloquent\Builder::wherelike 演算子が使える

これからもしっかり,API 仕様書を読み込んで勉強していこうと思う出来事でした。

@nunulk さん,貴重なアドバイス,感謝いたします。

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

phpのあとにapacheをインストールした場合ソースが表示されるのを修正

本来はapacheをインストール後にphpを入れればいいのだが、先にphpを入れてしまった場合の対処方法

php -> apache2 をインストール

sudo apt install php
sudo apt install apache2

apache用のphpモジュールのインストール

sudo apt-get install libapache2-mod-php

一旦apacheのphp関係の動作を止める

sudo a2enmod mpm_prefork

or

sudo a2dismod mpm_event

※動作方法を mpm_prefork か mpm_event にするかはチューニングする際に考慮します。php-cgi を使用するときなど。

phpを再度apacheに認識させる

sudo a2enmod php7.3

apacheサービスの再起動

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

ポートフォリオ 「FOOTBALL SHIRTS」  FWを使用せず素のPHPで制作しました。

初めに

フロントエンドエンジニアを目指してプログラミングを学習しています。
長田と申します。
プログラミング学習のアウトプットとして自作のWebサービス「FOOTBALL SHIRTS」のポートフォリオを制作しました。
この記事では「FOOTBALL SHIRTS」の概要や制作過程について説明します。
ソースコード↓
https://github.com/satoruosada/uniform

スクリーンショット 2020-08-13 13.10.07.png

目的

・フルスクラッチ開発を行うことでWebアプリの基本的な構成、動作を知る.
・自作のWebアプリで同じ初学者の方の役に立つサービスを提供したい.

スペック

使用言語 / HTML5/ CSS3 / Javascript / PHP

DBMS / MySQL

開発環境 / MacOS Catalina 10.15.6

バージョン管理 / SourceTree(3.0.15)

主な機能

ユーザー管理機能
 ・ユーザー登録機能
 ・ユーザーログイン機能
 ・ユーザー編集機能
 ・ユーザー削除機能

出品する商品登録管理機能
 ・商品登録
 ・商品編集
 ・商品詳細
 ・商品一覧(ページネーション)
 ・商品検索機能
 

商品詳細機能
 ・商品詳細表示
 ・商品へのリンク
 ・お気に入り機能
 ・掲示板機能(メッセージ投稿)

概要

「FOOTBALL SHIRTS」は、サッカーのユニフォームを出品するWebサービスです。

サッカーのユニフォームマニアが様々な世界中のサッカーのユニフォームを集めたり、転売できる専門のwebサービスを制作してみました。

開発手順

実装させたい主な機能から必要な項目を洗い出し、サンプルとしてExcelに必要なDB情報を書き出していきました。
洗い出した情報を元にテーブルを作成します。

スクリーンショット 2020-08-13 14.12.41.png

AdobeXDデザインカンプ作成

コーディング

デザインカンプを元に画面モックを作成
その後裏側の機能を実装していきます

セキュリティ対策

バリデーションチェック
サーバーサイド(PHP)側
 ☑︎未入力チェック
 ☑︎最大、最小文字数チェック
 ☑︎半角英数字チェック
 ☑︎正規表現を使用したemail型式チェック
 ☑︎正規表現を使用したURL型式チェック
 ☑︎同値チェック
 それぞれ関数を作成し各フォームで判定を行なっています。
以下一部抜粋です
スクリーンショット 2020-08-13 14.33.01.png

スクリーンショット 2020-08-13 14.33.36.png

フロントエンド(HTML)側

なりすまし対策について

セッションハイジャックによるなりすまし対策としてsession_regenerate_id関数を使用しています。
スクリーンショット 2020-08-13 14.41.26.png
session_regenerate_id関数は、現在のセッションのデータを保持したまま、セッションIDを新しく生成してくれます。

パスワードのハッシュ化について

パスワードをDBで登録する際は開発環境から見えてしまうのでセキュリティ上よくありません。
「FOOTBALL SHIRTS」ではpassword_hash関数でパスワードをハッシュ化してDB登録しています。
スクリーンショット 2020-08-13 14.45.45.png

ログイン時には、
password_verifiを使用し、ハッシュ化されたパスワードを確認しています。

スクリーンショット 2020-08-13 14.47.03.png
このとき$passはフォームからpostされたパスワード
DBから配列形式で取り出した情報を$resultに詰め
array_shiftを使って先頭から要素を一つ取り出し第二引数としています。

SQLインジェクション対策

DB接続時は、プレースホルダーを利用しSQL文を作成。
プリペアードステートメントを使うことでSQLインジェクション対策を行なっています。
スクリーンショット 2020-08-13 14.48.17.png

XSS(クロスサイトスクリプティング)対策

画面へ文字列や数値を出力する際は、htmlspecialchars関数を使いエスケープ処理を行なっています。

エスケープ処理とは特殊な文字を無害な文字に強制的に置き換える方法です
スクリーンショット 2020-08-13 14.50.00.png
第二引数のエスケープにはいくつか種類がありますが最もエスケープ文字数の多いENT_QUOTESを使用しています。

「FOOTBALL SHIRTS」で出来ること

①「FOOTBALL SHIRTS」へのユーザー登録、ログイン、ログアウト

スクリーンショット 2020-08-14 17.20.50.png

②ログイン後「FOOTBALL SHIRTS」の商品登録(出品)・編集・マイページ機能

ezgif.com-video-to-gif (5).gif

「FOOTBALL SHIRTS」の商品登録ページでは、「FOOTBALL SHIRTS」の商品名、カテゴリー、詳細コメント、商品の画像の登録が可能です。

画像登録にはjQueryを利用しドラック&ドロップでInputされるよう設定しています。

登録完了後はマイページに遷移し、きちんと登録できたことが確認できるようメッセージが表示されます。

マイページでは自身が登録した「FOOTBALL SHIRTS」の商品の閲覧、編集、自身のプロフィール編集、パスワード変更、退会、お気に入り登録した「FOOTBALL SHIRTS」の閲覧が可能です。

③「FOOTBALL SHIRTS」詳細画面に、Ajax処理によるお気に入り登録機能、掲示板機能を実装

お気に入り機能について
「FOOTBALL SHIRTS」の商品詳細画面では、商品画像右上のハートアイコンを押すことで
マイページのお気に入り一覧に商品を登録できます。
こちらはAjaxを用いて実装しています。

ezgif.com-video-to-gif (1).gif

掲示板機能について

「FOOTBALL SHIRTS」のやり取りについて気軽に質問できるように
掲示板機能を各詳細ページに実装しています。

まず掲示板自体が存在するかDB情報を確認。
無ければ新規作成します。
スクリーンショット 2020-08-14 14.55.03.png
もし既に掲示板情報があればメッセージのみ追加できるよう条件分岐させています。

スクリーンショット 2020-08-14 14.55.44.png

ezgif.com-video-to-gif (2).gif

④商品一覧・検索機能

 商品一覧ページにはページネーション、カテゴリ選択による表示順選択機能を実装しました。
ezgif.com-video-to-gif (3).gif

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

Zend Server Basic for IBM i のOSへのバンドル停止と無償利用の終了。有償エディションかCommunity PHPへの移行を

Zend Server Basic for IBM i のOSへのバンドルは2020-06-30で終了しました。無償利用は 2021-06-30 で終了します。

IBM から 2020/04/14 にこんな発表がありました。

ソフトウェアの営業活動終了およびサポート終了: IBM Rational Developer for AIX and Linux V9.1.x

タイトルからは IBM i には関係があると感じられませんが、こんなことが書いてありました。

2020 年 6 月 30 日付で、 IBM は、 IBM i オペレーティング・システム (5770-SS1) を使用した Zend Server Basic エディションの提供を終了します。

2021 年 6 月 30 日付で、 IBM i を使用した Zend Server Basic エディションを入手したすべての IBM i クライアントに向けた Zend Server Basic Support は終了します。 IBM i 用の Zend Server Professional および Zend Server Enterprise エディションは、Zend by Perforce © (「Zend」) が直接、提供およびサポートします。

すでに、プリロードや ESS からダウンロードという形での Zend Server Basic エディションの IBM からの提供は終了しています。

さらに、2021-06-30 で無償使用権が終了します。
「Support は終了します」だから、他のサポート終了ソフトと同じように、ノーサポートで、そのまま使えばいんじゃないの ? と思われるかもしれませんが、ここでのサポートは「使用権」も対象になるようです。期限を超えての利用はライセンス違反・コンプライアンス違反 になる可能性があります。

レターにはこうも書いてあります。オリジナルの IBM のレターはリンクになっている部分があるので、オリジナルのレターも確認してください。

Effective June 30, 2020, IBM will no longer deliver Zend Server Basic edition with the IBM i operating system (5770-SS1).

  • Clients who wish to continue using Zend Server Basic edition can acquire the product directly from Zend.
  • Clients can continue developing and deploying their PHP applications on Zend Server Professional and Zend Server Enterprise editions for IBM i, which will be supported directly by Zend. See Transform Your IBM i Power® Systems into Platforms for Innovation.
  • Clients who wish to use the open source Community PHP, see Installing Community PHP on IBM i.
  • Clients who require support for Community PHP or other Open Source products may acquire an Open Source Support contract from IBM. See Speed innovation with open source support. Clients may explore other solutions for PHP code development such as PHPStorm or VSCode. This is not a comprehensive list of replacement products. Other companies and open source communities may offer PHP code development solutions.

Effective June 30, 2021, Zend Server Basic Support is withdrawn for all Zend Server delivered with IBM i. For IBM i clients who have acquired Zend Server with IBM i, the last date to register Zend Server for one-year Zend Server Basic Support is June 30, 2020. Any registrations for Zend Server Basic Support after June 30, 2020 will result in end of Zend Server Basic Support effective June 30, 2021. Zend Server Professional and Zend Server Enterprise editions for IBM i will continue be supported directly by Zend.

Zend by Perforce からの BLOGはこちらです。FAQ もあります。

IBM and Zend by Perforce Announcement for IBM i Users

移行先は?

IBM i 用の Zend Server Professional か Zend Server Enterprise エディションの購入

一つ目は上位の有料エディションである Zend Server Professional 、 Zend Server Enterprise エディションを Zend by Perforce 社から直接購入することです。
レターにはこうあります。

IBM i 用の Zend Server Professional および Zend Server Enterprise エディションは、Zend by Perforce © (「Zend」) が直接、提供およびサポートします。

この場合、IBM i の各種統合機能はそのまま動きます。多くの追加モジュール・IBM i 統合機能もこれまで同様にコンパイル済みで同梱されます。

無償の Community PHP への移行

無料で、PHP を使いたい場合は、Community 版の PHP に移行することもできます。
ただし、追加モジュールは別途自分で導入したり、コンパイルする必要があります。
また、バグやセキュリティパッチの提供は、有償の Zend Server より短期間となります。

比較情報

IBM i、Zend by Perforce 両方から比較の情報が出ています。
これらを検討して適切な移行先を判断してください。

PHP on IBM i
PHP and IBM i – A Winning Combination

上のリンクに Zend Server の Free to use? が「Yes, until June 30, 2021」とはっきり書いてあります。やはり、無償利用が許可されるのは 2021-06-30 までのようです。

Community PHP の導入

Community PHP を移行先とする場合、メディアからRSTLICPGMで導入というわけにはいきません。
Community PHP は、今どきの IBM i のオープンソースとして yum で導入します。

IBM i のオープンソースの yum 導入の機能は、こちらをご覧ください。

https://bitbucket.org/ibmi/opensource/src/154edd688e225f978cd20a428c2d9c879748af4b/docs/yum/

Community PHP の導入は追加レポジトリーから行います。
こちらに情報があります。

https://bitbucket.org/ibmi/opensource/src/master/docs/yum/3RD_PARTY_REPOS.md

yum-config-manager コマンドを使えるようにするために、yum-utils パッケージを導入します。
次に下記の様にレポジトリーを追加します。

yum-config-manager --add-repo http://repos.zend.com/ibmiphp/

下記で php の基本文が導入されます。

yum install php-common php-cli

この後、必要な Extension の入手、Webサーバーとの統合、DB アクセスのセットアップが必要です。

こちらの記事に、php の導入、Webサーバーとの統合、DB アクセスのセットアップがまとまっれていました。

IBM i PHP / PDO / ODBC Setup


許可の無い転載を禁じます。
この記事は筆者の個人的な責任で無保証で提供しています。
当記事に関してIBMやビジネスパートナーに問い合わせることは、固くお断りします。

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

PHP: クッキーの使い方

こちらと同じことを、PHP で行いました。
jquery-cookie の使い方
Perl: クッキーの使い方

クッキーを送る

cookie_put.php
<?php
// ------------------------------------------------------------------
//
//  cookie_put.php
//
$expire = time()+60*60*24*7;
setcookie('message','これはテストです。',$expire);
setcookie('aa','りんご',$expire);
setcookie('bb','ぶどう',$expire);
setcookie('cc','桃',$expire);

echo "<!DOCTYPE html>";
echo "<html lang=\"ja\">";
echo "<body>";

echo "Aug/14/2020<p/>";
echo "</body>";
echo "</html>";
// ------------------------------------------------------------------
?>

クッキーを確認

cookie_get.php
<?php
// ------------------------------------------------------------------
//
//  cookie_get.php
//
echo "<!DOCTYPE html>";
echo "<html lang=\"ja\">";
echo "<body>";
echo "message: " . $_COOKIE['message'] . "<br />";
echo "aa: " . $_COOKIE['aa'] . "<br />";
echo "bb: " . $_COOKIE['bb'] . "<br />";
echo "cc: " . $_COOKIE['cc'] . "<br />";

echo "Aug/14/2020<p/>";
echo "</body>";
echo "</html>";
// ------------------------------------------------------------------
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「型」はプログラムのシナリオ設計

要約

  • 「型」はプログラムのシナリオ。正しい使い方の道筋と、得られる結果を教えてくれる。
  • 「型」が間違っていることは想定されたプログラムのシナリオとは違っていることを示している。
  • 「型」が間違っていた場合、言語がエラーとして教えてくれるので起動修正することが出来る。
  • この辺意識すると関数型言語でもとっつきやすくなるよ!

はじめに

  • PHP歴が長いので、コードはPHPベースです。
  • 動的型付け言語だろうと型付ける派です。

「型」が定義されていることの恩恵

「型」が定義されていない場合

まずは簡単な例として数値を計算するメソッドを作ります。

function add($baseNumber, $addNumber);

baseNumberにもaddNumberにも型は指定されていない状態です。
この関数を見た時に、何を渡すでしょうか?
名前にNumberとあるので、整数か小数の、とりあえず数字を渡すことを考えるでしょう。
果たしてそれはこの関数の想定された動作なのでしょうか?
また保証された結果が返ってくるのでしょうか?

「型」が定義されている場合

次に型を付けてみましょう。

function add(int $baseNumber, int $addNumber): int;

こうなっていた場合、baseNumberaddNumberには必ず整数を渡しますよね。
型を付けたことでadd()関数は整数を渡すのが正しい使い方だと分かるようになり、整数を返すのが正しい結果だと分かるようになりました。

恩恵のまとめ

型があることでadd()関数の使い方の道筋=シナリオがより明確になった

よりシナリオを意識した「型」を作る

先ほどは加算という簡単な例でした。
型を付けなくても変数名で何とかしようと思えば、何とか出来る範囲でしょう。
ではより具体的なシナリオを意識した場合、どちらが分かりやすくなるか比較していきましょう。

支払金額累計の処理

支払金額累計の処理を作ることになりました。
ルールは以下の通りです。

支払金額累計のルール
支払金額累計 = 今月分までの支払い金額 + 昨年度の支払い累計

「型」が定義されていない場合

// 定義
function calculatePaymentTotal($currentPaymentTotal, $lastYearPaymentTotal);

function currentPaymentTotal();
function lastYearPaymentTotal();

// 利用
calculatePaymentTotal(currentPaymentTotal(), lastYearPaymentTotal());

変数名からそれとなく
currentPaymentTotal() = 今月分までの支払い金額

lastYearPaymentTotal() = 昨年度の支払い累計
を渡せばいいことが想定できると思います。

仕様が複雑化した場合

// 定義
function calculatePaymentTotal($currentPaymentTotal, $lastYearPaymentTotal);

function currentPaymentTotal();
function lastYearPaymentTotal();
function currentPaymentTotalByPurchaser();
function lastYearPaymentTotalByPurchaser();
function currentPaymentTotalBySale();
function lastYearPaymentTotalBySale();

// 利用
// どれが想定されたシナリオ?
calculatePaymentTotal(currentPaymentTotal(), lastYearPaymentTotal()); // 〇? ×?
calculatePaymentTotal(currentPaymentTotalByPurchaser(), lastYearPaymentTotalByPurchaser()); // 〇? ×?
calculatePaymentTotal(currentPaymentTotalBySale(), lastYearPaymentTotalBySale()); // 〇? ×?

仕様が増え、累計に関する処理が増えました。
果たしてcalculatePaymentTotal()を使おうとした場合、その使い方は正しいのでしょうか?
そもそも該当の関数を見落として、別の処理を使ってしまうかもしれません。
どれが正しいシナリオなのか、想定するのも困難になりました。

「型」が定義されている場合

function calculatePaymentTotal(
  CurrentPaymentTotal $currentPaymentTotal,
  LastYearPaymentTotal $lastYearPaymentTotal
);

class CurrentPaymentTotal();
class LastYearPaymentTotal();

型を指定していることで、CurrentPaymentTotalLastYearPaymentTotalを渡せばいいことがはっきりしていますね。
では仕様が複雑化した場合はどうでしょうか。

仕様が複雑化した場合

// 定義
function calculatePaymentTotal(
  CurrentPaymentTotal $currentPaymentTotal,
  LastYearPaymentTotal $lastYearPaymentTotal
);

class CurrentPaymentTotal();
class LastYearPaymentTotal();
class CurrentPaymentTotalByPurchaser();
class LastYearPaymentTotalByPurchaser();
class CurrentPaymentTotalBySale();
class LastYearPaymentTotalBySale();

// 利用
$currentPaymentTotal = new CurrentPaymentTotal();
$lastYearPaymentTotalnew = new LastYearPaymentTotal();
calculatePaymentTotal($currentPaymentTotal , $lastYearPaymentTotal)

型を指定していることで、いくら仕様が増えようと、
CurrentPaymentTotalLastYearPaymentTotalを渡すことが正しいシナリオであると定まっています。
またCurrentPaymentTotalLastYearPaymentTotalを先に処理ことがはっきりしているので、処理の順序も崩れることがありません。

まとめ

型を定義して、より堅牢なシナリオを作ろう

型を定義しない場合と定義した場合とで、何をしたいか、何をしてほしいかの読み取りやすさがきっぱりと別れたと思います。
型を考え定義することは少しのコストがかかりますが、堅牢なシナリオを作ることが出来ます。
その恩恵は持続し、より堅牢なプログラムになります。
どんどん定義していきましょう。

型に頼らなくても似たようなことできるのでは?

上記に挙げたメリットは頑張ればDocコメントやコードレビューなどのコミュニケーションで回避できるものでもあります。
ただ経験上、開発者、レビュアー、レビュイーの誰もがコストを強いられうまくいった試しがありません。
そこは言語に任せて、誰も間違えない環境を作っていく方がコスト減につながると思っています。

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

Laravel x GAS API で Spreadsheet 自動作成してみた

はじめに

実務で、GAS(Google Apps Script)を使って Spreadsheet 作成APIを作ったので、そのやり方を書く。
今回は、Laravel の Guzzle を使って APIを叩いたので、そのテンプレも書くことにした。

ここでは、clasp などのコマンドラインでの作業の説明は割愛する。

Guzzle テンプレート

$client = new \GuzzleHttp\Client();

url = https://script.google.com/a/XXXX.co.jp/macros/s/XXXXXXXXXXXXXXXXXXXXXXXX/exec

$sheets = ['[シート名]' => [['名前', 'Soma', 'Sekimoto'],['住所', 'Tokyo', 'Suginami'], ['年齢と性別', ' 23歳', '男']]]

$res = $client->post(
            $url,
            [\GuzzleHttp\RequestOptions::JSON => [
                'title' => 'スプレッドシートタイトル',
                'sheets' => $sheets,
                'spread_sheet_id' => '[スプレッドシート ID]',
                'startPositions' => ['startRow' => 3, 'startColumn' => 2],
            ]]);

GAS(Google Apps Script) API

CreateSheet.js
function doPost(e) {
    // 引数で送られてきたパラメータを取得する
    var c = e.postData.contents
    var contents = JSON.parse(c)

    // パラメーターをそれぞれの変数に格納
    var title = contents.title
    var sheets = contents.sheets
    var startPositions = contents.startPositions
    var startRow = startPositions.startRow
    var startColumn = startPositions.startColumn
    var spreadsheetId = contents.spread_sheet_id

    var spreadsheet = spreadsheetId ? SpreadsheetApp.openById(spreadsheetId).copy(title) : SpreadsheetApp.create(title);
    // Parameter に spread_sheet_id がない場合は、新しいスプレッドシートを作るようにする。

    i = 0
    for (var sheet_name in sheets) {
        if (i >= spreadsheet.getSheets().length) {
            spreadsheet.insertSheet()
        }
        // シートが足りなくなった時は新しいシートを追加させる。

        var sheet = spreadsheet.getSheets()[i]
        var rows = sheets[sheet_name]
        sheet.getRange(startRow, startColumn, rows.length, rows[0].length).setValues(rows)
        sheet.getRange(startRow, startColumn, rows.length, rows[0].length).createFilter()
        sheet.setName(sheet_name)
        i += 1;
    }
    spreadsheet.addEditor("/xx/domain/XXXX.co.jp")
    // 編集者を付与できる。

    return ContentService.createTextOutput(JSON.stringify({url: spreadsheet.getUrl()})).setMimeType(ContentService.MimeType.JSON);
    // 作成したシートの URL を返す。 
}

これでスプレッドシート自動作成API完成!!!!

おわりに

既存アプリ x GAS APIの無限大の可能性を利用すれば、最強のAPIレポジトリを完成させることも夢ではありません。みなさんの参考になれば幸いです。

もっとこうした方がいい!!、ここは間違ってる!! 等ございましたら遠慮なくお申し付けください!
もっとスマートなコードの書き方あると思うので、思いつく方いらっしゃいましたらご教授お願いします!

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

docker-compose を用いて Apache・PHP・MySQL の開発環境を構築してみた

はじめに

docker-compose を用いて Apache・PHP・MySQL の開発環境を構築してみた備忘録になります。

バージョン情報

docker-compose はインストールされていることが前提になります。
今回は以下のバージョンでの動きになります。

$ docker-compose -v
docker-compose version 1.26.2, build eefe0d31

PHP のバージョンは 5.4、MySQL のバージョンは 5.5 の環境を構築します。
Apache のバージョンは特に気にしていません。

ディレクトリ構造

ディレクトリ構造は以下のようになります。

.
├── config
│   ├── mysql
│   │   ├── Dockerfile
│   │   ├── initdb.d
│   │   │   └── init.sql
│   │   └── my.cnf
│   └── php
│       ├── Dockerfile
│       ├── apache2.conf
│       ├── php.ini
│       └── sites
│           ├── 000-default.conf
│           └── default-ssl.conf
├── data
├── docker-compose.yml
└── html
    └── test
        ├── connect.php
        └── index.php

GitHub にもあげました。ご参考まで。

設定

各サービスに以下のような設定ファイルがそれぞれあると思いますが、それらを変更したものを動かしたいと思います。

Apache の設定

今回は DocumentRoot を変更してみます。

公式ドキュメント をみると以下のように Dockerfile を記述することで DocumentRoot を変更できるようです。

b.png

いろいろな記事をみるとコンテナから 000-default.conf などの設定ファイルをホスト側にもってきてそれを修正した後、Dockerfile の COPY でコンテナ側にコピーするようでした。

やはり公式ドキュメントをみるのが一番ですね。

公式ドキュメントのように行ったらコンテナが再起動ループに陥ってしまいました。

※ 理由をよく調べたら php:5.5-apache 以降であればそれでよかったらしいです。

なので、php:5.4-apache のイメージからコンテナを立ち上げて、元ファイルをホスト側にコピーして、それを編集することにしました。

# とりあえず php:5.4-apache のコンテナを動かしてログインする
$ docker image pull php:5.4-apache
$ docker container run --name php54apache -d php:5.4-apache
$ docker exec -it php54apache /bin/bash

# 設定ファイルを確認 ( php:5.4-apache 以前は apache2.conf の修正も必要)
$ ls /etc/apache2/apache2.conf
httpd.conf

$ ls /etc/apache2/sites-available
000-default.conf  default-ssl.conf

# コンテナを抜ける
$ exit

# ホスト側にコピー
$ docker container cp php54apache:/etc/apache2/apache2.conf ./config/php
$ docker container cp php54apache:/etc/apache2/sites-available/000-default.conf ./config/php/sites
$ docker container cp php54apache:/etc/apache2/sites-available/default-ssl.conf ./config/php/sites

これらの apache2.conf000-default.confdefault-ssl.conf に記述してある DocumentRoot を変更します。

今回は以下のように変更しました。

# DocumentRoot /var/www/html
DocumentRoot /var/www/html/test

これで DocumentRoot の変更の準備は完了です。

PHP の設定

PHP は php.ini によって設定を行います。

公式ドキュメント をみると以下のように記述することで php.ini を指定するらしいです。

a.png

しかし、コンテナを立ち上げてログインしてみても、 php.ini-developmentphp.ini-produciton のようなファイルは存在しませんでした。

おそらく Apache の設定でもそうであったように公式のドキュメントは過去のバージョンまで挙動は保証していないようです。

サンプルにあるような php:7.4-fpm-alpine ならそれで良さそうですね。

今回は php.ini-developmentphp.ini-produciton については GitHub で公開してあったのでそちらの php.ini-development を元に php.ini を作成することにしました。

php.ini の修正についてはこちらを参照して、ロケーションや言語の設定を修正しました。

MySQL の設定ファイルの変更

MySQL の設定ファイルは my.cnf によって行います。

MySQL の設定ファイルの my.cnf は 公式ドキュメント によると /etc/mysql/my.cnf に配置してあるとのことでした。

c.png

なのでそちらをローカルホストに持ってきて、適宜修正していきたいと思います。

# とりあえず mysql:5.5 のコンテナを動かしてログインする
$ docker image pull mysql:5.5
$ docker container run -e MYSQL_ROOT_PASSWORD=sample_pw --name mysql55 -d mysql:5.5
$ docker exec -it mysql55 /bin/bash

# /etc/mysql/my.cnf を確認
$ ls /etc/mysql/my.cnf
/etc/mysql/my.cnf

# コンテナを抜ける
$ exit

# my.cnf をホスト側にコピー
$ docker container cp mysql55:/etc/mysql/my.cnf ./config/mysql/

あとは環境に合わせて修正して、my.cnf を作成してください。

※ ちなみに今回は my.cnf の修正は行っていません。

Dockerfile の作成

Dockerfile は php:5.4-apachemysql:5.5 のイメージについて作成しました。

php:5.4-apache

config/php/Dockerfile
# image
FROM php:5.4-apache

# Set php.ini
COPY ./php.ini /usr/local/etc/php/

# Set apache conf (Before tag:5.4-apache)
COPY ./apache2.conf /etc/apache2/
COPY ./sites/*.conf /etc/apache2/sites-available/

# Set apache conf (After tag:5.5-apache)
# ENV APACHE_DOCUMENT_ROOT /var/www/html/test
# RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
# RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf

# Install MySQL connection module
RUN apt-get update \
  && apt-get install -y libpq-dev \
  && docker-php-ext-install pdo_mysql pdo_pgsql mysqli mbstring

ここでは「DocumentRoot の変更」、「php.ini の配置」、「MySQL と疎通するためのモジュールを追加」しています。

mysql:5.5

config/mysql/Dockerfile
# image
FROM mysql:5.5

# Set my.cnf
COPY ./my.cnf /etc/mysql/conf.d/

# Set Japanese
RUN apt-get update && apt-get install -y \
  locales \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*
RUN sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen \
  && locale-gen
ENV LANG ja_JP.UTF-8
CMD ["mysqld", "--character-set-server=utf8", "--collation-server=utf8_unicode_ci"]

デフォルトのままであると MySQL にログインした後に日本語の入力ができなかったため、ここでは日本語を入力可能にするような設定をおこなています。

MySQL の日本語の設定は こちら を参照しました。

MySQL の初期データの投入

docker-compose 起動時に MySQL に初期データを投入してみたく、以下のような SQL を用意しました。

init.sql
DROP TABLE IF EXISTS sample_table;

CREATE TABLE sample_table (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name TEXT NOT NULL
) charset=utf8;

INSERT INTO sample_table (name) VALUES ("太郎"),("花子"),("令和");

コンテナ起動後にここで記述した SQL が実行されていることを確認します。

docker-compose.yml の作成

以下のように docker-compose.yml を作成しました。

docker-compose.yml
version: '3.8'

services:

  # PHP Apache
  php-apache:
    build: ./config/php
    ports:
      - "8080:80"
    volumes:
      - ./html:/var/www/html
    restart: always
    depends_on:
      - mysql

  # MySQL
  mysql:
    build: ./config/mysql
    ports:
      - 3306:3306
    volumes:
      - ./config/mysql/initdb.d:/docker-entrypoint-initdb.d
      - ./data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: sample_root_passward
      MYSQL_DATABASE: sample_db
      MYSQL_USER: sample_user
      MYSQL_PASSWORD: sample_pass

MySQL のデータの永続化について

MySQL のデータディレクトリは /var/lib/mysql であり、 - ./data:/var/lib/mysql とあるようにホスト側の ./data ディレクトリにマウントすることによってデータを永続化しております。

試してみたところ docker-compose stopdocker-compose down してもデータが永続化されていることが確認できました。

docker-compose 使い方

いよいよ docker-compose を動かします。

docker-compose 起動

まずはなにも起動されていないことを確認します。

$ docker-compose ps
Name   Command   State   Ports
------------------------------

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

今回はわかりやすいようにコンテナも空の状態にしておきます。

docker-compose.yml が存在する階層で docker-compose up -d コマンドを実行することで、docker-compose.yml の記述をもとにコンテナが作成されます。

※ docker-compose のコマンドの buildup の違いやオプションについてはこちらがわかりやすかったです。

# image の作成
$ docker-compose build --no-cache
Successfully tagged docker-compose-sample_php-apache:latest

# コンテナの構築・起動
$ docker-compose up -d
Creating network "docker-compose-sample_default" with the default driver
Creating docker-compose-sample_mysql_1 ... done
Creating docker-compose-sample_php-apache_1 ... done

# docker-compose の確認
$ docker-compose ps
               Name                            Command             State           Ports
-------------------------------------------------------------------------------------------------
docker-compose-sample_mysql_1        docker-entrypoint.sh mysqld   Up      0.0.0.0:3306->3306/tcp
docker-compose-sample_php-apache_1   apache2-foreground            Up      0.0.0.0:8080->80/tcp

# コンテナの確認
$ docker container ls
CONTAINER ID        IMAGE                              COMMAND                  CREATED             STATUS              PORTS                    NAMES
26b0cec2daad        docker-compose-sample_php-apache   "apache2-foreground"     3 minutes ago       Up 3 minutes        0.0.0.0:8080->80/tcp     docker-compose-sample_php-apache_1
eaae044f4bba        mysql:5.5                          "docker-entrypoint.s…"   3 minutes ago       Up 3 minutes        0.0.0.0:3306->3306/tcp   docker-compose-sample_mysql_1

コンテナが動いていることが確認できました。

PHP-Apache コンテナの確認

DocumentRoot の直下に配置する、php:5.4-apache コンテナの疎通確認用のページとして以下のようなファイルを用意しました。

html/test/index.php
<?php
echo  __DIR__;
phpinfo();

http://localhost:8080 にアクセスすると以下のように表示されるはずです。

echo __DIR__; はその PHP ファイルが置かれている絶対パスを出力するので DocumentRoot の変更が行われていることが確認できます。

また、php.ini についてオリジナルものから修正を加えていれば、phpinfo(); の出力でそちらも反映されていることも確認できるかと思います。

f.png

MySQL コンテナの確認

mysql:5.5 のイメージで作成されたコンテナにログインして、初期データが投入されていることを確認します。

shell
# コンテナにログイン
$ docker exec -it eaae044f4bba /bin/bash

# MySQL にログイン
$ mysql -p
Enter password:

パスワードを求められるので docker-compose.yml で MYSQL_ROOT_PASSWORD の環境変数に登録した sample_root_passward と入力するとログインできます。

docker-compose.yml に登録した MYSQL_DATABASEMYSQL_USER が登録されていることを確認します。

mysql> select user, host from mysql.user;

+-------------+-----------+
| user        | host      |
+-------------+-----------+
| root        | %         |
| sample_user | %         |
| root        | localhost |
+-------------+-----------+
3 rows in set (0.00 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sample_db          |
+--------------------+
4 rows in set (0.00 sec)

sample_usersample_db があることが確認できました。

続いて init.sql で作成したテーブルとカラムについても確認してみます。

mysql> use sample_db;
Database changed

mysql> show tables;
+---------------------+
| Tables_in_sample_db |
+---------------------+
| sample_table        |
+---------------------+
1 row in set (0.00 sec)

mysql> select * from sample_table;
+----+--------+
| id | name   |
+----+--------+
|  1 | 太郎 |
|  2 | 花子 |
|  3 | 令和 |
+----+--------+
3 rows in set (0.00 sec)

テーブルとカラムについても問題なく確認できました。

PHP-Apache から MySQL コンテナへの疎通確認

PHP-Apache から MySQL コンテナへの疎通確認用のページとして以下のようなファイルを用意しました。

html/test/connect.php
<?php
try {
    // host=XXXの部分のXXXにはmysqlのサービス名を指定します
    $dsn = 'mysql:host=mysql;dbname=sample_db;';
    $db = new PDO($dsn, 'sample_user', 'sample_pass');

    $sql = 'SELECT * FROM sample_table;';
    $stmt = $db->prepare($sql);
    $stmt->execute();
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    var_dump($result);
} catch (PDOException $e) {
    echo $e->getMessage();
    exit;
}

※ 注意点として、ホスト名は localhost127.0.0.1 ではなく、docker-compose.yml でサービス名として指定してた mysql を使用します。

http://localhost:8080/connect.php にアクセスすると以下のように表示されればが PHP-Apache から MySQL コンテナへの疎通がうまく行っています。

g.png
さきほど、初期投入されたデータが確認できますね。

docker-compose 停止・削除

以下のコマンドで 停止・削除 を行います。

# コンテナを停止
$ docker-compose stop

# コンテナを停止し、そのコンテナとネットワークの削除
$ docker-compose down

# コンテナを停止し、そのコンテナとネットワークを削除、さらにイメージも削除
$ docker-compose down --rmi all --volumes

./config/mysql/initdb.d/init.sql の初期データ投入からやり直したい時などは docker-compose down --rmi all --volumes してから ./data ディレクトリの中身も削除して docker-compose up -d でコンテナの構築・起動からやり直してください。

まとめ

以下を自動化する docker-compose の開発環境構築を行いました。

  • DocumentRoot の変更
  • php.ini の変更
  • my.cnf の変更
  • MySQL への初期データの投入
  • MySQL のデータの永続化
  • PHP-Apache コンテナから MySQL コンテナへの疎通

つまり、これらの環境がワンライナーで構築できるようになりました。

学んだこと

今回初めて docker-compose で開発環境を構築してみて学んだことを羅列します。

  • 公式ドキュメント(DockerHub)は読んだ方がいい
    • Qiita よりも DockerHub を先に見た方がよさそうです。
  • docker-compose.yml と Dockerfile はかき分ける
    • 一度変更すればコンテナ起動後に変更のない設定ファイルなどは Dockerfile で COPY でコンテナ側にファイルを配置して、コンテナ起動後に変更のあるものは docker-compose.yml でマウントさせるという書き分けがよさそうです。
  • Dockerfile をいきなり書くのは難しい
    • 以下の手順で Dockerfile を書くと良いです。
      • ベースイメージを決める
      • ベースイメージのコンテナ内で作業しつつ、うまくいった処理をメモ
      • 全て成功したらDockerfileを作成

以上になります。

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

PHPで「ここはis_null?empty?isset?」ってなったので比較してまとめた。

is_null?empty?isset?ってなることが多々あったので、比較実験した。

実験内容

それぞれに以下を与えて結果を取得する。

  • $string; //変数を宣言するだけ
  • $string = 'test'; //文字列
  • $string = null; //null
  • 未定義の変数
  • 空文字

最終結果はページの最後に表にしています。

is_null()

変数がNULLかどうか調べる。つまり、nullならばtrueが、それ以外であればfalseが返ってくる。

$string;
is_null($string); // true
$string = 'test';
is_null($string); // false
$string = null;
is_null($string); // true
//定義していない $a
 is_null($a); // true
//空文字
is_null(''); // false

empty()

変数が空であるかどうかを検査する。「変数が空」のイメージがしっくり来ないので、以下でテスト。

$string;
empty($string); // true
$string = 'test';
empty($string); // false
$string = null;
empty($string); // true
//定義していない $b
empty($b); // true

ここまでは、is_nullと挙動が同じだなーって思ったが、以下の挙動が違った。

//空文字
empty(''); // true

空文字は「変数が空である」と捉えられるらしい。

isset()

変数が宣言されていること、そして NULL とは異なることを検査する。

つまり、

  • 変数が宣言されている
  • その変数がnullではない

という2つの条件が整うとtrueが返ってくる。is_nullとemptyとは異なり2つ条件があるっぽい。

is_nullとemptyとは「あったらtrue」という感じでニュアンスが違う。

$string;
isset($string); //false (変数は宣言されているが、nullなのでアウト)
$string = 'test';
isset($string); // true (変数は宣言され、文字列が与えられているのでセーフ)
$string = null;
isset($string); // false (変数は宣言されているが、nullなのでアウト)
// 定義していない $c
isset($c); // false (変数が宣言されていないのでアウト)

なるほど。じゃあ変数としてではなく、直接文字列とか与えたらどうなる??(以下のように)

isset('');
isset('string');

結果は、

PHP Fatal error:  Cannot use isset() on the result of an expression (you can use "null !== expression" instead)

ダメらしい。

まとめ

上の結果を以下の表にまとめました。

$string $string = 'test' $string = null 定義していない変数 空文字を直接
is_null true false true true false
empty true false true true true
isset false true false false 空文字にかかわらず直接与えるとエラー
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む