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

PHP基礎構文 マジックメソッドを全て利用してみる

前提

  • 実行環境
    • PHP 7.1.32
  • 本エントリーでは、PHPが用意する全マジックメソッドに関する挙動について触れる

マジックメソッドとは

インスタンスがある特定の条件になったときに、明示的にcallしなくても暗黙的にcallされるメソッド

引用元便利だけど使いどころが難しいPHPの代表的なマジックメソッドと無名関数の使い方

  • 暗黙的にコールされる事がポイント

__construct()

class Message
{
    public function __construct()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
    }
}

var_dump(new Message());

/* 出力結果 */
call Message::__construct
object(Message)#1 (0) {
}

__destruct()

class Message
{
    public function __construct()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
    }

    public function __destruct()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
    }
}

var_dump(new Message());

/* 出力結果 */
call Message::__construct
object(Message)#1 (0) {
}
call Message::__destruct

__call()

<?php

class Message
{
    public function __call(string $name, array $arguments)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        print 'Value of second argument $arguments: ' . implode($arguments) . PHP_EOL;
    }
}

$message = new Message();
$message->getMessage('Hello');
var_dump($message);

/* 出力結果 */
call Message::__call
Value of first argument $name: getMessage
Value of second argument $arguments: Hello
object(Message)#1 (0) {
}

__callStatic()

  • アクセス不能メソッドを静的にアクセスした時にコールされるメソッド
class Message
{
    public static function __callStatic(string $name, array $arguments)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        print 'Value of second argument $arguments: ' . implode($arguments) . PHP_EOL;
    }
}

$message = new Message();
$message::getMessage('Hello');
var_dump($message);

/* 出力結果 */
call Message::__callStatic
Value of first argument $name: getMessage
Value of second argument $arguments: Hello
object(Message)#1 (0) {
}

__get()

<?php

class Message
{
    private $message = [];

    public function __get(string $name)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        return $this->message;
    }
}

$message = new Message();
var_dump($message->message);

/* 出力結果 */
call Message::__get
Value of first argument $name: message
array(0) {
}

__set()

<?php

class Message
{
    private $message = [];

    public function __get(string $name)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        if (array_key_exists($name, $this->message)) {
            return true;
        } else {
            return false;
        }
    }

    public function __set(string $name, $value)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        print 'Value of second argument $value: ' . $value . PHP_EOL;
        $this->message[$name] = $value;
        print PHP_EOL;
    }
}

$message = new Message();
$message->message = 'Hello';
var_dump($message->message);

/* 出力結果 */
call Message::__set
Value of first argument $name: message
Value of second argument $value: Hello

call Message::__get
Value of first argument $name: message
bool(true)

__isset()

<?php

class Message
{
    private $message = [];

    public function __isset(string $name)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        return isset($this->message[$name]);
    }
}

$message = new Message();
var_dump(isset($message->message));

/* 出力結果 */
call Message::__isset
Value of first argument $name: message
bool(false)

__unset()

<?php

class Message
{
    private $message = [];

    public function __unset(string $name)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        unset($this->message[$name]);
    }
}

$message = new Message();
unset($message->message);

/* 出力結果 */
call Message::__unset
Value of first argument $name: message

__sleep()

class Message
{
    public function __sleep()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        return [];
    }
}
$message = new Message();
var_dump(serialize($message));

/* 出力結果 */
call Message::__sleep
string(18) "O:7:"Message":0:{}"

__wakeup()

  • unserialize()をコールした時にコールされるメソッド
class Message
{
    public function __wakeup()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        return [];
    }
}
$message = new Message();
$serializedMessage = serialize($message);
var_dump(unserialize($serializedMessage));

/* 出力結果 */
call Message::__wakeup
object(Message)#2 (0) {
}

__toString()

class Message
{
    public function __toString()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        return 'Hello' . PHP_EOL;
    }
}

$message = new Message();
echo $message;

/* 出力結果 */
call Message::__toString
Hello

__invoke()

class Message
{
    public function __invoke($value)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        echo $value . PHP_EOL;
    }
}

$message = new Message();
$message('Hello');
var_dump($message);

/* 出力結果 */
call Message::__invoke
Hello
object(Message)#1 (0) {
}

__set_state()

class Message
{
    public $val_a;
    public $val_b;

    public static function __set_state(array $properties)
    {
        $this->val_a = $properties['val_a'];
        $this->val_b = $properties['val_b'];
        print 'call ' . __METHOD__ . PHP_EOL;
    }
}

$message = new Message();
$message->val_a = 'Hello';
$message->val_b = 'Good morning';
var_export($message);

/* 出力結果 */
Message::__set_state(array(
   'val_a' => 'Hello',
   'val_b' => 'Good morning',
))

__clone()

class Message
{
    public function __clone()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
    }
}

$message = new Message();
var_dump(clone $message);

/* 出力結果 */
call Message::__clone
object(Message)#2 (0) {
}

__debugInfo()

class Message
{
    public function __debugInfo()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        return [];
    }
}

var_dump(new Message());

/* 出力結果 */
call Message::__debugInfo
object(Message)#1 (0) {
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP基礎構文 マジックメソッドを全て利用してみる

マジックメソッドとは

インスタンスがある特定の条件になったときに、明示的にcallしなくても暗黙的にcallされるメソッド

引用元便利だけど使いどころが難しいPHPの代表的なマジックメソッドと無名関数の使い方

  • 暗黙的にコールされる事がポイント

__construct()

class Message
{
    public function __construct()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
    }
}

var_dump(new Message());

/* 出力結果 */
call Message::__construct
object(Message)#1 (0) {
}

__destruct()

class Message
{
    public function __construct()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
    }

    public function __destruct()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
    }
}

var_dump(new Message());

/* 出力結果 */
call Message::__construct
object(Message)#1 (0) {
}
call Message::__destruct

__call()

<?php

class Message
{
    public function __call(string $name, array $arguments)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        print 'Value of second argument $arguments: ' . implode($arguments) . PHP_EOL;
    }
}

$message = new Message();
$message->getMessage('Hello');
var_dump($message);

/* 出力結果 */
call Message::__call
Value of first argument $name: getMessage
Value of second argument $arguments: Hello
object(Message)#1 (0) {
}

__callStatic()

  • アクセス不能メソッドを静的にアクセスした時にコールされるメソッド
class Message
{
    public static function __callStatic(string $name, array $arguments)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        print 'Value of second argument $arguments: ' . implode($arguments) . PHP_EOL;
    }
}

$message = new Message();
$message::getMessage('Hello');
var_dump($message);

/* 出力結果 */
call Message::__callStatic
Value of first argument $name: getMessage
Value of second argument $arguments: Hello
object(Message)#1 (0) {
}

__get()

<?php

class Message
{
    private $message = [];

    public function __get(string $name)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        return $this->message;
    }
}

$message = new Message();
var_dump($message->message);

/* 出力結果 */
call Message::__get
Value of first argument $name: message
array(0) {
}

__set()

<?php

class Message
{
    private $message = [];

    public function __get(string $name)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        if (array_key_exists($name, $this->message)) {
            return true;
        } else {
            return false;
        }
    }

    public function __set(string $name, $value)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        print 'Value of second argument $value: ' . $value . PHP_EOL;
        $this->message[$name] = $value;
        print PHP_EOL;
    }
}

$message = new Message();
$message->message = 'Hello';
var_dump($message->message);

/* 出力結果 */
call Message::__set
Value of first argument $name: message
Value of second argument $value: Hello

call Message::__get
Value of first argument $name: message
bool(true)

__isset()

<?php

class Message
{
    private $message = [];

    public function __isset(string $name)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        return isset($this->message[$name]);
    }
}

$message = new Message();
var_dump(isset($message->message));

/* 出力結果 */
call Message::__isset
Value of first argument $name: message
bool(false)

__unset()

<?php

class Message
{
    private $message = [];

    public function __unset(string $name)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        print 'Value of first argument $name: ' . $name . PHP_EOL;
        unset($this->message[$name]);
    }
}

$message = new Message();
unset($message->message);

/* 出力結果 */
call Message::__unset
Value of first argument $name: message

__sleep()

class Message
{
    public function __sleep()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        return [];
    }
}
$message = new Message();
var_dump(serialize($message));

/* 出力結果 */
call Message::__sleep
string(18) "O:7:"Message":0:{}"

__wakeup()

  • unserialize()をコールした時にコールされるメソッド
class Message
{
    public function __wakeup()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        return [];
    }
}
$message = new Message();
$serializedMessage = serialize($message);
var_dump(unserialize($serializedMessage));

/* 出力結果 */
call Message::__wakeup
object(Message)#2 (0) {
}

__toString()

class Message
{
    public function __toString()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        return 'Hello' . PHP_EOL;
    }
}

$message = new Message();
echo $message;

/* 出力結果 */
call Message::__toString
Hello

__invoke()

class Message
{
    public function __invoke($value)
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        echo $value . PHP_EOL;
    }
}

$message = new Message();
$message('Hello');
var_dump($message);

/* 出力結果 */
call Message::__invoke
Hello
object(Message)#1 (0) {
}

__set_state()

class Message
{
    public $val_a;
    public $val_b;

    public static function __set_state(array $properties)
    {
        $this->val_a = $properties['val_a'];
        $this->val_b = $properties['val_b'];
        print 'call ' . __METHOD__ . PHP_EOL;
    }
}

$message = new Message();
$message->val_a = 'Hello';
$message->val_b = 'Good morning';
var_export($message);

/* 出力結果 */
Message::__set_state(array(
   'val_a' => 'Hello',
   'val_b' => 'Good morning',
))

__clone()

class Message
{
    public function __clone()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
    }
}

$message = new Message();
var_dump(clone $message);

/* 出力結果 */
call Message::__clone
object(Message)#2 (0) {
}

__debugInfo()

class Message
{
    public function __debugInfo()
    {
        print 'call ' . __METHOD__ . PHP_EOL;
        return [];
    }
}

var_dump(new Message());

/* 出力結果 */
call Message::__debugInfo
object(Message)#1 (0) {
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPの「define」と「const」の違い

どちらも定数を定義するときに使う

<?php

define('MY_CONST_1', 'defineで定義した定数');
const MY_CONST_2 = 'constで定義した定数';

echo MY_CONST_1;
// defineで定義した定数
echo MY_CONST_2;
// constで定義した定数

1.関数と構文

define は関数
const は構文

define は関数の呼び出しのオーバーヘッドがあるため 遅い
const は関数じゃないから 速い

2.変数や、関数の戻り値を使えるか使えないか

define は変数や、関数の戻り値を使える

<?php
$val = '適当な変数';
define('MY_CONST', $val);
echo MY_CONST; // 適当な変数
<?php
define('NOW', microtime(true));
echo NOW; // 1579431033.9034

const は変数や、関数の戻り値を使えない

<?php
$val = '適当な変数';
const MY_CONST = $val;
// PHP Fatal error:  Constant expression contains invalid operations in ...
<?php
const NOW = microtime(true);
// PHP Fatal error:  Constant expression contains invalid operations in ...

いったん定数にすれば使える

<?php
define('NOW', microtime(true));
const MY_CONST = NOW;
echo MY_CONST; // 1579431879.0664

3.トップレベル以外で使えるか使えないか

constiffor function の中では使えない

<?php
if (true) {
    define('MY_CONST', 'defineはifの中でも使える');
}
<?php
if (true) {
    const MY_CONST = 'constは構文エラーになる';
    // PHP Parse error:  syntax error, unexpected 'const' (T_CONST) in ...
}

4.クラス定数

define はクラス定数を定義するときに使えない

<?php
class Example
{
    public define('MY_CONST', 'defineは関数だから構文エラーになる');
}
// PHP Parse error:  syntax error, unexpected 'define' (T_STRING), expecting function (T_FUNCTION) or const (T_CONST) in ...
<?php
class Example
{
    public const MY_CONST = 'クラス定数を定義するときに使う';
}

echo Example::MY_CONST;
// クラス定数を定義するときに使う

まとめ

クラス定数を定義するときは const を使う

クラス定数以外は const を使うと速いけど
何回も定義するわけじゃないし、 const には制約もあるから
define を使った方が無難

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

laravelで作った画像も表示できるブックレビューの機能について ※ポートフォリオの解説です

こんなアプリを作りました

http://book-review123456.herokuapp.com/articles

スクリーンショット 2020-01-19 17.29.05.png

書籍のカバー画像も表示できる掲示板アプリです。画像はGoogleBooksAPIsを利用して取得します。
     APIを利用した画像の取得方法については下記にまとめました ↓

laravelでカバー画像も表示できるブックレビューアプリを作った。※自分の学習用です

デプロイ出来れば一番良かったのですが、出来なかったのでイメージ画像と言葉で補足します。
(gitはしばらくの間はprivateにしておこうと思います。個人情報的に。特定の相手にだけ共有しようかと)

機能はこんな感じです。

  • 画像も表示できるブックレビューの投稿
  • ログイン/新規登録
  • バリデーション(ログイン時だけレビューの投稿・編集ができる)
  • お問い合わせ
  • 退会処理
  • TwitterOAuth
  • いいね機能

TwitterOAuthは個人情報的にアレかな〜と思って、gitで共有する段階ではやはり封印しました。

反省と今後の課題

機能に関する課題は別記事に書いたので、勉強について。
しばらくはLaravelではなく、生のPHPで制作し勉強した方がいいのかなと思いました。
なので、こちらの記事を参考に勉強を進めたいです。

Web開発初心者がLaravelで実務開発するまでの3ヶ月の研修ステップ

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

LambdaでPHPを実行する手順

なるべく、簡潔にできるようにまとめてみました。

前提

Mac
AWSアカウント作成済
Dockerインストール済

手順

  1. Docker内で、PHPの実行環境を作り、それをzip化
  2. そのzipファイルをLambdaにアップロード
  3. API GatewayでAPI作成

1. Docker内で、PHPの実行環境を作り、それをzip化

stackery/phplambdalayerというツールを使うと、makeコマンド一つで簡単にできました。

このツールには、PHPの実行環境をzip化するために必要なスクリプトや、Lambdaでカスタムランタイム自体を動作させるboostrapファイル等がまとめて入っている

$ git clone https://github.com/stackery/php-lambda-layer.git
$ cd php-lambda-layer
$ make php71.zip

php71.zipというファイルが作成される

make php71.zip の内容を見ると、

Makefile
php71.zip:
    docker run --rm -e http_proxy=${http_proxy} -v $(ROOT_DIR):/opt/layer lambci/lambda:build-provided /opt/layer/build.sh
  • lambci/lambdaというlambda環境のDockerイメージを使っている
  • そのイメージで起動したDockerコンテナ内で、build.shが実行されている
  • build.shの内容を見ると、Lambda用のPHP7.1実行環境を作って、zip化している
build.sh
#!/bin/bash

# 依存関係により、php本体もここでインストールされている
yum install -y php71-mbstring.x86_64 zip php71-pgsql php71-mysqli

mkdir /tmp/layer
cd /tmp/layer
cp /opt/layer/bootstrap .
sed "s/PHP_MINOR_VERSION/1/g" /opt/layer/php.ini >php.ini

mkdir bin
cp /usr/bin/php bin/

mkdir lib
for lib in libncurses.so.5 libtinfo.so.5 libpcre.so.0; do
  cp "/lib64/${lib}" lib/
done

cp /usr/lib64/libedit.so.0 lib/
cp /usr/lib64/libpq.so.5 lib/

cp -a /usr/lib64/php lib/

zip -r /opt/layer/php71.zip .

2. 作成したzipファイルをLambdaにアップロード

ここから下は、全てAWSのコンソールでやりました。

2-1. レイヤー作成

レイヤーは、実行環境をアップロードしておくことで、複数のLambda関数で利用できるようにするもの

  • レイヤーを作成し、作成したzipファイルをアップロードする
  • レイヤーバージョンARNは、後で使うため、メモっておく

2-2. Lambda関数作成

  • 「一から作成」
  • ランタイム:「ユーザー独自のブートストラップを提供する」
  • 「実行ロール」(IAMロール)も一緒に作成される
  • すでにある3つのファイルは使わないため、削除。代わりに、以下のindex.phpを作成
index.php
<?php phpinfo(); ?>

2-3. Lambda関数とレイヤーを結びつける

「レイヤーバージョンARNを提供」を選択し、上でメモった「レイヤーバージョンARN」を貼る

3. API GatewayでAPI作成

「APIの作成」

  • メソッドの作成:Any
  • 「Lambdaプロキシ統合の使用」にチェックを入れる
  • 最後にAPIのデプロイ(ステージ名入力。このステージは、アクセスするパスになる)

APIへのアクセスは、該当するLambda関数を選択→APIGatewayを選択すると、APIエンドポイントが表示され、ワンクリックでできる。

参照

PHPでゼロからはじめるAWS Lambda Custom Runtime

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

PHPの「static」は「3種類」??

JavaScriptの「this」は「4種類」??

負けた

1.静的メンバ

静的プロパティ、静的メソッドを定義するために使うパターン

<?php

class Example
{
    /**
     * @var string
     */
    protected static $staticProperty = '静的プロパティ';

    /**
     * @return string
     */
    final public static function getStaticProperty(): string
    {
        return self::$staticProperty;
    }
}


echo Example::getStaticProperty();
// 静的プロパティ

一番よく使うパターン

クラス自体に持たせるメンバ
インスタンス化しないで使う

静的メンバには :: (スコープ演算子)を使ってアクセスする
スコープ演算子の左辺は クラス 、右辺は 静的メンバ

2.静的変数

  • コンパイルされる時に 1度だけ 初期化される
    • 同スコープ内で複数回初期化される場合 最後に初期化される値 になる
    • 変数 や、インスタンス を使って初期化 できない
  • スコープが外れてもガベコレされない
    • 再度初期化された時、前回の値で初期化される
  • メソッドの中で使うと静的プロパティと同じ挙動をする

最初の一度のみ 0 で初期化され
スコープが外れてもガベコレされず
前回の値が入っていることがわかる例

<?php

/**
 * カウントアップする
 */
function countUp(): int
{
    static $cnt = 0;
    return ++$cnt;
}

echo countUp();
// 1
echo countUp();
// 2
echo countUp();
// 3

参照を切ってもガベコレされない
同スコープ内で複数回初期化される場合 最後に初期化された値になる
再度宣言された時、前回の値で初期化される例

<?php

/**
 * 第1引数で指定した名前の変数が存在する場合、その値をechoします
 * 存在しない場合は「参照ないよ」とechoします
 *
 * @param string $valName
 * @return void
 */
function echoIfExists(string $valName): void
{
    echo $GLOBALS[$valName] ?? '参照ないよ';
}

/**
 * 改行文字を返す
 * コマンドラインから実行されている場合は \n
 * それ以外は <br>
 *
 * @return string
 */
function endOfLine(): string
{
    return php_sapi_name() === 'cli'
        ? PHP_EOL
        : nl2br(PHP_EOL);
}

// 静的変数を初期化する(1回目)
static $staticVal = '静的変数_1';
echoIfExists('staticVal');
echo endOfLine();
// 静的変数_2
// ↑1じゃなく2で初期化されている

$staticVal = '値を書き換える';

// 参照消す(普通の変数ならガベコレされる)
unset($staticVal);
echoIfExists('staticVal');
echo endOfLine();
// 参照無いよ

// 静的変数を初期化する(2回目)
static $staticVal = '静的変数_2';
echoIfExists('staticVal');
echo endOfLine();
// 値を書き換える
// ↑前回の値で初期化されている

変数を使って初期化できない例

<?php

$val = '適当な変数';

// 変数で初期化を試みる
static $staticVal = $val;
// PHP Fatal error:  Constant expression contains invalid operations in ...

変数を使っても定義できる

<?php

$val = '変数';
define('MY_CONST', $val); // 変数を定数にする
static $staticVal = MY_CONST;
echo $staticVal;
// 変数

インスタンスを使って初期化できない例

<?php

// インスタンスで初期化を試みる
static $staticVal = new class{};
// PHP Fatal error:  Constant expression contains invalid operations in ...

もちろんクロージャもインスタンスなのでだめ

<?php

// クロージャで初期化を試みる
static $staticVal = function () {};
// PHP Fatal error:  Constant expression contains invalid operations in ...

ユーザー定義定数を使って初期化できない例

<?php

static $staticVal = '普通に初期化';
// PHP Warning:  Use of undefined constant MY_CONST - assumed 'MY_CONST'

$val = '変数';
define('MY_CONST', $val); // 変数を定数にする
static $staticVal = MY_CONST;

同スコープ内で複数回初期化される場合 最後に初期化された値 になる ため
MY_CONST で初期化しようとするけど
最初の初期化のタイミングには MY_CONST は存在しないのでエラーになる

メソッドの中で使うと静的プロパティと大体同じ挙動をする例

PHPの静的変数 (static変数) の挙動まとめ

静的プロパティとメソッド内の静的変数の違いは
参照を切れるか外からアクセスできるか

プロパティは unset() できないけど
静的変数は unset() できる

静的プロパティはメンバなので Reflection を使えば
private であってもアクセスすることができる
静的変数はローカルスコープなので絶対にアクセスできない

3.遅延静的束縛

静的メンバにアクセスする際に使う特別なクラスに
selfstatic があります

  • self はそのメソッドが定義してあるクラス自身
    • コンパイルされるときに決定(束縛)される
  • static はそのメソッドが呼び出されたクラス自身
    • ランタイムで決定(束縛)される

名前の通り遅延して束縛されるわけですね

具体的にどういう違いがあるかというと、継承したときに違いが現れます

<?php

class Base
{
    /**
     * selfが指すクラスをechoする
     *
     * @return void
     */
    public static function echoSelfClass(): void
    {
        echo self::class;
    }

    /**
     * staticが指すクラスをechoする
     *
     * @return void
     */
    public static function echoStaticClass(): void
    {
        echo static::class;
    }
}

class Sample extends Base {}

Sample::echoSelfClass();
// Base
Sample::echoStaticClass();
// Sample

まとめ

PHPの static は難しい

まとまってない

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

Docker+Laravelでタスクスケジューラ

目的

通常サーバのタスクスケジューラはcronにて行うが、

  • 少し面倒であること
  • Laravelにタスクスケジューラの機能があること

から、画面を作成して画面経由でタスクスケジュール設定を行えるものを作成してみる。

前提

下記の環境で実装した。

  • PHP7.1.x
  • Laravel6.x+AdminLTE3
  • PostgreSQL11.4
  • Docker phpfpm

サンプル仕様

サーバのタスクスケジュールのため、画面操作するためにログインが必要とした。

タスク自体は別途実装済みであるとして、タスク名と実行スケジュールを設定する画面を
用意する。

タスクの実行はLaravelにタスクスケジューラをcron経由で実行するものとして、タスクスケジューラの設定を先の画面で設定したものを反映させるものとする。

タスクの実装

詳細は省略するが、タスクの雛形をartisanで作成できる。

% php artisan make:command タスク名

とりあえず、タスクが動いたか確認したいため、下記のタスクを実装した。

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

class Shout extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:shout';

〜省略〜

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        Log::info("start shout!!");
    }
}

見ての通り、実行されたらログが出力されるだけのタスクである。

モデル定義

定義するモデルはScrapingCommandである。
migrateの内容を下記に示す。

 ?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateScrapingCommandsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('scraping_commands', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string("name",200);
            $table->string("cmd",200);
            $table->integer("cron1")->nullable();
            $table->integer("cron2")->nullable();
            $table->integer("cron3")->nullable();
            $table->integer("cron4")->nullable();
            $table->integer("cron5")->nullable();
            $table->integer("flg")->default(0);
            $table->softDeletes();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('scraping_commands');
    }
}

画面実装

ログイン画面

AdminLTEのものをそのまま使用した。

タスク一覧画面

登録されているタスクの一覧表示している。

タスクスケジュールはcronと同じ形式である。スケジュールとは別に有効/無効の設定が可能である。
img_tsk2.png

タスク設定画面

タスクの設定を行う。コマンド名はartisanで使用するコマンド名(\$signatureに設定しているもの)を設定する。

設定内容はScrapingCommandモデルに保存される。
img_tsk3.png

Laravel側のスケジュール定義

LaravelのタスクスケジュールはApp\Console\Kernelのscheduleメソッドで定義するため、
同メソッドを下記のように修正した。

<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\Facades\Log;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\ScrapingCommand;

class Kernel extends ConsoleKernel
{
〜省略〜
    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $lst = ScrapingCommand::where("flg",1)->get();
        foreach($lst as $rec) {
            Log::info("cmd:".$rec->cmd);
            Log::info("cron:".$rec->cronStr());
            $schedule->command($rec->cmd)->cron($rec->cronStr());
        }
    }
〜省略〜
}

Cron側の設定

Cron側はLaravelのタスクスケジューラを実行するだけなので、

* * * * * /usr/local/bin/php /svr/app/artisan schedule:run >> /var/log/cron.log 2>&1

(注)/svr/appはLaravelをインストールしたディレクトリ  

本来はcrontab -e で上記のファイルを作成することになるが、今回はDockerなので、
rootファイルとして作成しておく。

以上で、要望を実装済みであるが、今回はDockerで実装したため、コンテナ上でcronのインストールが必要になる。

そこで、cronのインストールはDockerイメージ作成時に行う。よってDockerファイルは

FROM php:fpm

# For composer
RUN apt-get update \
    && apt-get install -y gcc make zlib1g-dev libzip-dev libpng-dev unzip libmcrypt-dev libjpeg-dev libfreetype6-dev \
    && docker-php-ext-install zip \
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include \
    && docker-php-ext-install -j$(nproc) gd

# Install composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php composer-setup.php \
  && php -r "unlink('composer-setup.php');" \
  && mv composer.phar /usr/local/bin/composer

# Set composer environment
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer
ENV PATH $PATH:/composer/vendor/bin

# Install laravel installer
RUN composer global require "laravel/installer"

# PHP's DB setting
RUN apt-get update \
    && apt-get install -y libpq-dev \
    && docker-php-ext-install pdo_mysql pdo_pgsql

# Install Node.js
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \
    && apt-get update \
    && apt-get install -y nodejs

RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
RUN apt-get install -y cron

RUN mkdir /debugbar
RUN chmod go+w /debugbar
WORKDIR /svr

とした。

次にcrontabの登録とcron起動が必要になるが、今回は手動で行うものとした。
よって、上記で作成したイメージからコンテナを起動して、コンテナ上で

% cp root /var/spool/cron/crontab
% service cron start

を実行する。
rootファイルはあらかじめ/svrにコピーしておくこと。

考察

cronの設定って結構面倒なものである。何よりわざわざサーバにログインして作業しないと行けないのである。
この仕組みだと、画面で設定するだけなので大分楽である。タスクはLaravel上で動くため、
DB接続とかログ出力とかの考慮が必要なくなる。

問題点は

  • crontabの設定とcronの起動をコンテナ起動後手動で行わなければならない。
    Dockerイメージでcronの起動もできるらしいが、今回は割愛した
  • 5分毎等のスケジュールに対応できてない
    これは実装の問題であるが。。。

サンプルで実装したプロジェクトはGitHubにアップしたので、必要であれば参照していただきたい。

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

PHPの備忘録

PHPの変数名にハイフンは使えない

変数名に使えるのは英数字、アンダースコア、ASCIIの127-255の範囲のみ。
数字始まりは不可。

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

【個人開発】Webサイトを作ってみた

つくったもの

2020_01_19_15_14.56.jpg

概要

予算300円以内で買えるだけのお菓子が選ばれ、その結果を楽しめるサイトです。

なぜ作ったか

仕事でPHPなどは触っているもののWebサイトを作成したことがなかったので習作として作成しました。
制作に挫折しないよう扱ったことのない技術は極力避け、完成させることを第一目標にしました。

使用技術

OS:CentOS 7.7
言語:PHP 5.4
DB:MariaDB 5.5.64
キャッシュ:memcached 1.4.15
プラグイン:lightbox2
VPS:KagoyaVPS
DNS:お名前ドットコム
広告:i-mobile Affiliate

工夫したところ

・なるべくDBにアクセスしないようにDBアクセスの結果はキャッシュに乗せるようにしています。
・管理ページでCSVファイルをアップロードできる仕組みにしているため、データの追加や更新が容易にできるようになっています。

苦労したところ

・本番環境でモジュールのインストールがうまくいかず試行錯誤していました。
 具体的にはphp-pecl-memcachedです。
 OSをCentOS6からCentOS7にすることで依存関係の問題は解決しました。
・データ作成に想定より時間がかかりました。
 今後のデータ追加を考慮してデータ構造を何回も変えていたので、そのたびにデータ修正をしていました。

管理ツール

管理ツールのページでは以下のことが行えます。

  • マスターデータ閲覧
    • DBのマスターデータ参照。
  • マスターデータ(CSV)のインポート
    • CSVファイルを選択し、DBのテーブルにインポート。
  • キャッシュクリア
    • memcachedのクリア。

制作管理

完成させるためにタスクの洗い出しや進捗記録をつけていました。
仕事みたいで微妙ですが、終わりが見えない作業をやってる感よりは良いかなと。

・機能リスト
最終的に実装したい機能のリスト。
優先順位をつけ、優先度の高いものから実装していく。
機能リスト.jpg

・Todo
機能リストの項目を細かく分解してタスク化。
ふと時間ができた時、取り掛かれるタスクをパッと見つけるためのもの。
機能リストには無い思いつきもここに追加。
Todo.jpg

・仕様書
仕様どおりに作るためのものではなく、あとでどんな実装をしたのか確認するためのもの。
最初に各仕様を記述する枠だけは作っておいて書けるものは書く。
作りながら考えたものは実装した後で仕様書に実装をフィードバック。
仕様.jpg

・作業記録
前回の作業で何をやってたっけ…?というのを思い出すためのもの。
副産物として少しづつコツコツ進んでる実感を得られる。
作業記録.jpg

制作期間

2019年10月下旬から2020年01月上旬が製作期間です。
子供がいるので休日もまとまった時間が取れず、出社前の15分や休日にふとできた30分の空き時間などを積み重ねていました。

まとめ

・小さいながらも作成したWebサイトが公開に至れたのはよかったです。
 個人開発の記事でよく見る「完全を求めてるといつまでも公開できない」というのは実感としてよく分かります。
 どこかで切り上げてまとめに入る決断が必要です。
・思うがままに作成できる個人開発は楽しいです。
 面倒だったら仕様を変えられるし、思いつきもすぐ反映できます。
 とはいえデータ作成とか、あぁこれ自分がやらないといけないんだよな…と思ったりします。
・制作管理の資料を作成していたのは良かったです。
 一段落したときに自分のやったことを見返せますし、制作での資産が分かりやすくまとまっているので今後に活かすことができます。

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

CentOSにcomposerをインストールする方法

はじめに

phpを用いた開発の時によく用いられるcomposerについてインストール方法をまとめます。

そもそもcomposerとは一言で言うとphpのパッケージ管理システムです。
phpのパッケージ依存管理ツールであるComposerを利用することで、パッケージの管理コストを下げ開発効率をあげることができます。
composerはプロジェクトが必要とするライブラリやパッケージを管理していて、それをもとにインストールをすることができます。
例えば、あるライブラリをインストールしようとしたときにその前に特定のライブラリをインストールしなければならないような依存関係を自動的にインストールしてくれる便利なツールです。

composerをインストールする

今回は公式のやり方とyumを用いてインストールする方法の2種類をまとめます。
他のパッケージをインストールする時にyumを用いている場合は依存関係などのバージョン管理しやすいようにするため、yumを用いてインストールする方法をおすすめします。
※今回はphpコマンドyumコマンドが使えると言う前提で話を進めます。
phpはパージョン7.3系です。

①公式のcomposerインストール手順

command
# インストーラをダウンロードする(phpコマンド以外にもcurlやwgetなどのコマンドでダウンロードもできます。)
$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"

# ダウンロードしたインストーラのハッシュをチェックし、正しければ'Installer verified'と表示する
$ php -r "if (hash_file('sha384', 'composer-setup.php') === '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"

# インストーラを実行する
$ php composer-setup.php

# インストーラを削除する
$ php -r "unlink('composer-setup.php');"

#CentOSのどこからでも使えるようにするために、/usr/local/binフォルダにcomposer.pharを移動させる
$ sudo mv composer.phar /usr/local/bin/composer

②yumを用いたcomposerインストール手順

command
#yumでcomposerをインストールする
$ sudo yum install --enablerepo=remi,remi-php73 composer
#(中略)
エラー: パッケージ: composer-1.9.2-1.el7.remi.noarch (remi) 
要求: php-zip

php-zipがないと怒られてしまいました。。。
なので下記のようにphp-pecl-zipを指定してインストールされると成功しました!!
(php-zip指定してもなぜかできませんでした。。。なぜか分かる方いたら教えてください。。。)

command
$ sudo yum install --enablerepo=remi,remi-php73 php-pecl-zip composer

composerの配置とインストール確認

command
#CentOSのどこからでも使えるようにするために、/usr/local/binフォルダにcomposer.pharを移動させる
$ sudo mv composer.phar /usr/local/bin/composer

#インストール確認する(大きくcomposerの文字とバージョンと出てくれば完了です)
$ composer -v

さいごに

何かパッケージをインストールするときは公式のやり方を見つつ自分の開発環境とあっているか確認しながら行うと良いかと思います。

参考:Composer を CentOS にインストールする手順

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

PHPで暗号学的に安全でランダムな文字列を柔軟に生成する方法

やりたいこと

  • ランダムな文字列を取得したいよ。
  • PHPの標準関数だけでやりたいよ。
  • 暗号学的に安全なランダム性を持たせたいよ。
  • 出力で使用する文字や文字長を指定したいよ。(0~fだけとかヤダよ)
  • PHP5系では動かせなくてもいいよ。

暗号学的に安全じゃない方法とは?

「ランダムな文字列をゲットしたいな」というとき、よく以下のようなコードが使われるかと思います。

echo sha1(uniqid(mt_rand(), true));
// 出力例 : f282fe197b5f9837a520118d5636facc0ff3832d

実はこれだと問題があって(まぁ実際はほぼ問題になることは無いと思いますが…)、「mt_rand()」も「uniqid()」も生成される値が暗号学的に安全ではないとリファレンスに明記されています。

PHPマニュアル - 関数リファレンス

あと、そもそもこの方法だと「出力で使用する文字を指定」したり、「出力文字長を指定」したりできないですね。

暗号学的に安全な方法1(0~f固定だけど簡単な方法)

出力される文字が0~f固定でも良い場合は、以下のコードでOKです。

$length = 16;
echo substr(bin2hex(random_bytes($length / 2 + 1)), 0, $length);
// 出力例 : cf7ffd47e6cb2399

random_bytes()は、暗号学的に安全なランダムバイト列を生成します。
生成したバイト列をbin2hex()で文字列に変換し、最後にsubstr()で必要な長さに切っています。

PHPマニュアル - 関数リファレンス

暗号学的に安全な方法2(0~f固定じゃないし文字長指定できるし使用文字を指定できる方法)

見た方が早いですね。以下の通りです。

function random_string($length = 16) {
    // 出力で使用する文字を決める。
    // 例えば、以下の場合は、「英数字から見間違え安い文字を除外した文字」のみにしてある。
    // (lとかOとか除外してある)
    $baseChars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789';
    $baseCharLength = strlen($baseChars);

    // 指定された数だけランダムな文字を取得する
    $output = '';
    for ($i = 0; $i < $length; $i++) {
        // rand()ではなく、暗号学的に安全なrandom_int()を使用する。
        // ちなみに、文字列は角括弧で任意の文字にアクセスできるので、substr()じゃなくてOK。
        $output .= $baseChars[random_int(0, $baseCharLength - 1)];
    }

    return $output;
}

コメントにも書いてありますが、出力で使用したい文字を$baseCharsで指定しています。
そこから、必要な文字長だけランダムに文字を取り出していく、という流れです。

このとき、ランダムな取り出し位置はrandom_int()で指定します。random_int()で生成される値は暗号学的に安全であることがリファレンスにも明記されています。

PHPマニュアル - 関数リファレンス

出力結果はこんな感じです。

for ($i = 0; $i < 5; $i++) {
    echo random_string(16)."\n";
}
// deWWGQ9wWGBVDFcU
// ui7usFxSx8FwWPCM
// d7AGQmydVcPbcwv6
// xzuVnAKCr5QB7Rz8
// SHHc8UV3Ha5F2th3

出力する文字にマルチバイト文字を使用したい場合

マルチバイト文字の場合は「strlenではなくmb_strlenを使う」、「文字列からの文字取り出しは角括弧ではなくmb_substrを使う」にします。

function mb_random_string_iroha($length = 16) {
    $baseChars = 'いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてあさきゆめみしゑひもせすん';
    // マルチバイト文字を使う場合は、mb_strlen()で。
    $baseCharLength = mb_strlen($baseChars);

    $output = '';
    for ($i = 0; $i < $length; $i++) {
        $pos = random_int(0, $baseCharLength - 1);
        // マルチバイト文字ならこちらもmb_substr()で。
        $output .= mb_substr($baseChars, $pos, 1);
    }

    return $output;
}

出力結果はこんな感じです。

for ($i = 0; $i < 5; $i++) {
    echo mb_random_string_iroha(16)."\n";
}
// なたれあなやろおおてほねさえれゆ
// あよらゑふめさたなりよゐむきたせ
// ゆてるいもそしやぬりゐてちひたか
// しのけちほねせねえつゆくすおめせ
// のつすいらなほれれむりとよまさる

最後に、高精度の姓名分割ツール とか Writeningっていうメモサービス とか、その他色々作ってるのでぜひプロフィール欄のホムペを覗いてみてください〜

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