20200210のPHPに関する記事は12件です。

リレーションで特定のユーザーの記事を取得する手順

はじめに

情報共有サイトにおいて、全てのユーザーが投稿した記事を取得することは簡単ですが、特定のユーザーが投稿した記事のみを取得したいとなると、リレーションが必要になり、初学者にとってはちょっと複雑になります。本記事では備忘録も兼ねてその手順をメモします。

テーブルの作成

テーブル名を決める

テーブル名は複数形にします
今回は下記のように定義します。

  • 記事を保存するテーブル名→news
  • ユーザー情報を保存するテーブル名→users

マイグレーションファイルの作成

下記コマンドを打ちます

$ php artisan make:migration create_テーブル名_table

database/migrationsディレクトリ直下にテーブル名.php というファイルが生成されます。

マイグレーションファイルへの編集

newsテーブルに記事のタイトルと本文のカラムを加える場合、下記のように'title'と'body'を追記します。

public function up()
    {
        Schema::create('news', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title'); // ニュースのタイトルを保存するカラム
            $table->string('body');  // ニュースの本文を保存するカラム
            $table->timestamps();
        });
    }

変更を反映させる為には、下記コマンドを実行します。
このコマンドはマイグレーションファイルのup関数を実行します。

php artisan migrate

モデルの作成

モデルとは簡単に言うとテーブル内のデータを操作でき、保存、編集、削除などが出来るものになります。

テーブルとモデルの紐付け

  • Laravelでは1つのテーブルにつき1つのモデルが紐づきます。
  • テーブルとモデルは命名規則で紐づけられます。テーブル名の単数形をモデル名にしてモデル名の頭文字を大文字にすることで紐づけられます。 例えば下記のようになります。
テーブル名 モデル名
news News
users User

モデルの作成

Newsモデルを作成するには下記コマンドを実行します。

php artisan make:model News

appディレクトリ直下にNews.php というファイルが生成されました。
同様にUserモデルも作成します。

ここまでで、ユーザー情報に関するusersテーブル/Userモデルと、記事情報に関するnewsテーブル/Newsモデルを作成しました。これでそれぞれの情報を個々に取得することはできます。しかしある特定のユーザーが投稿した記事を全て取得したいとなるとこのままでは難しいです。これを解決するにはnewsテーブルにusersテーブルのidを加えてあげる必要があります。このnewsテーブルに追加するusersテーブルのidを外部キーと呼びます。

外部キーにも命名規則があります。
usersテーブルのidカラムを外部キーとして設定する場合、リレーション先のカラム名は[リレーション元モデル名_id]となり、'user_id'と設定します。

テーブルにカラムを追加する手順

テーブルにカラムを追加する方法としては、テーブル自体を削除して作り直すやり方もあるのですが、これでは保存したデータが消えてしまう問題があります。そこで今回は別の方法でカラムを追加します。詳しいことは下記記事が参考になります。
【Laravel】カラムを追加する方法

下記コマンドでカラムを追加するための専用のmigrationファイルを作成します。

php artisan make:migration add_user_id_to_news_table --table=news

--table=news:ここにカラムを追加するテーブル名を書きます。

作成されたmigrationファイルを編集します。

public function up()
   {
       Schema::table('news', function (Blueprint $table) {
           $table->integer('user_id');
       });
   }

public function down()
   {
       Schema::table('news', function (Blueprint $table) {
           $table->dropColumn('user_id');
       });
   }

変更を反映させます。

php artisan migrate

モデルの紐付け

外部キーによってテーブル間の紐付けができたら次にモデル間の紐付けをします。
テーブル間の関係が '1対他' か '他対1' かによって使用するメソッドが下記のようになります。

  • [1対他]のリレーション → hasManyメソッド
  • [他対1]のリレーション → belongsToメソッド

今回はusersテーブルとnewsテーブルの関係は[1対他]になり、それぞれのメソッドは下記のように扱います。

  • newsテーブルを扱うNewsモデルではbelongsToメソッド
  • usersテーブルを扱うUserモデルではhasManyメソッド

それぞれのメソッドはモデル内で下記のように記入します。

Newsモデル

App/News.php
public function user()
    {
        return $this->belongsTo('App\User');
    }

Userモデル

App/User.php
public function news()
    {
        return $this->hasMany('App\News');
    }

これで特定のユーザーの記事を取得することができます。

参考

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

初心者向け 少し便利なPHP関数

はじめに

2つの配列があり、一つをキーにしてもう一つを値にして配列を作り直したい時

<?php
    $array1 = [ "a", "b", "c" ] ;
    $array2 = [ "1", "2", "3" ] ;

    // こういう配列を作りたい
    $combineArray = [
        "a" => "1",
        "b" => "2",
        "c" => "3"
     ];
?>

解決策

PHP7.2には、array_combine()と言う関数あり、これを使うだけで簡単に実現できます。

<?php
   // 配列
  $array1 = [ "a", "b", "c" ] ;
    $array2 = [ "1", "2", "3" ] ;

    // 実行
    $result = array_combine($array1, $array2) ;

    // 返り値
    print_r( $result ) ;
?>

結果

Array
(
    [a] => 1
    [b] => 2
    [c] => 3
)

以上です。

公式リファレンス

https://www.php.net/manual/ja/function.array-combine.php

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

PHPアプリで扱う設定ファイルの形式をどうするか

目的

PHPアプリを作る上で、挙動のカスタマイズや、DBの接続設定をどういった形式のファイルに保存するか考える。

結論

PHP においては ini ファイル一択。

後半にあるマトリックスでYAML,TOML,INIが同得点になったのは真実に迫っていると思う。

評価点

  • 表現力
  • 書きやすさ
  • 読みやすさ
  • パースの速さ
  • 導入の容易さ
  • ポータビリティー

候補

  • YAML
  • TOML
  • JSON
  • XML
  • INI

総評

PHPはXMLとJSONとINIしか標準でパースできないので、書きやすさ、読みやすさ、導入の容易さ、ポータビリティにおいてINIの妥当性が高い。

表現力

どんな設定ファイルかという具体性がないのでどれだけの表現力が求められるか評価が難しいところだけど、それぞれ設定ファイルとして採用された実績は沢山あるので、表現力に関して問題になる事は想定しなくてよいのではないか。

正直なところ、INIファイルは複雑な設定ファイルになると表現力に乏しいと思う。

書きやすさ

パースのしやすさ(syantax的な難しさ)と、意図通りに書きやすいかという論点があると思う。
JSONはシリアライズ用のフォーマット色が強い。

JSON、XMLは書きにくい。

読みやすさ

XMLは読みにくい。JSONもちょっと読みにくい。

パースの速さ

パースの速さにこだわりがちな人は多いが、設定ファイルを1回の処理で何度もパースする事はないので、評価が必要なほど遅いものは無いというだけで全部合格。

パーサーの書きやすさだとXML,JSONあたりになるだろうけど、それは別の話。

導入の容易さ

PHPアプリでという前提なので標準でパースできるXML、JSON、INIに軍配が上がる。
ただし、YAML、TOMLも負担はない。XML書くぐらいならYAML、TOMLを使いたい。

ポータビリティー

YAML、INIは仕様が若干曖昧でパーサによっては扱えない事があったりするかもしれないけど、何を選んでもまず困ることはないと思う。

モダンさ

評価点には挙げていないが、INI、XMLは古いイメージはある。

マトリックス

表現力 書きやすさ 読みやすさ パースの速さ 導入の容易さ ポータビリティー モダンさ 合計(モダンさを評価しない)
YAML 5 5 5 5 4 5 5 34(29)
TOML 5 5 5 5 4 5 5 34(29)
JSON 5 3 4 5 5 5 5 32(27)
XML 5 1 3 5 5 5 4 28(24)
INI 4 5 5 5 5 5 3 32(29)

感想

この記事はPHPだと設定ファイルはINIファイルが妥当なんじゃないのかなという実感が先にあって、表現力とモダンさでINIファイルを使うことを躊躇する必要は無いんじゃないかなという意図ありきで書きました。

具体的に選定する際はそれぞれの状況において適切なものを選ぶべきだと思うけど、選定理由はきちんと説明出来るようにしたほうが良い。

モダンなものは課題を解決した結果誕生しているので優れていることは多いが、用途や要件を満たしたものの中では、モダンだからという事自体は理由にならないと思う。

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

【WordPress】 Gutenberg関連のCSSまとめ

はじめに

Gutenberg(ブロックエディタ)対応のテーマを作成するために、CSSを設計しようと思ったのですが、

  • 読み込まれるCSSの数が多い
  • どのように読み込む/読み込まれるのか
  • フロント側、エディタ側どちらで読み込まれるのか

などこんがらがるポイントが多かったので、まとめてみました。

style.min.css

フロント側で自動的に読み込まれる最初のCSS。

各ブロックの基本的なレイアウトを定義しており、特にブロックエディタ用のCSSを用意していなくても、ある程度いい感じに表示してくれるのはこのCSSのおかげ。

以下のように、wp_dequeue_style で無効化する事も出来ます。

functions.php
function mytheme_enqueue() {
    wp_dequeue_style( 'wp-block-library' );
}
add_action( 'wp_enqueue_scripts', 'mytheme_enqueue' );

ただし、例えばテキストの中央寄せ( .has-text-align-center )などの基本的なスタイルすら適用されなくなるので、全ブロックをフルカスタマイズしてやろうという本気の方以外は、有効にしたままの方が良いです。

theme.min.css

フロント側で読み込まれる二つ目のCSS。

style.min.css に加え、もう少し見栄え良くスタイルを整えてくれます。

こちらは style.min.css と異なり、add_theme_support( 'wp-block-styles' ); で手動で読み込む必要があります。

コードもそれほど多くないので、読み込まずに自前でスタイルを用意してもよいと思います。

load-styles.php

エディタ側で自動的に読み込まれる最初のCSS。

ブロックエディタ含め、管理画面全体のCSSが定義されています。

style-both.css

※ファイル名は任意

アクションフック enqueue_block_assets を使うと、フロント側・エディタ側両方で読み込まれます。

functions.php
function mytheme_enqueue_both() {
    wp_enqueue_style( 'style-both', get_template_directory_uri() . '/style-both.css' );
}
add_action( 'enqueue_block_assets', 'mytheme_enqueue_both' );

フロント側・エディタ側を同時にスタイリング出来るので便利そうですが、前述した通り、

  • フロント側で読み込まれるCSSは style.min.css (+ theme.min.css
  • エディタ側で読み込まれるCSSは load-styles.php

と、標準で読み込まれるCSSが違います。

スタイリングのされ方も若干異なるので、一つのCSSプロパティ・値で見え方を完全に統一させることは難しいです。

また、エディタ側のエディタ部はeditor-styles-wrapperというクラス名でラップされており、フロント側のコンテンツ部は独自のセレクタ( .post-content とか)でラップするはずなので、共通のセレクタを使う事が出来ません。

とはいえ、セレクタに要素名( h1 とか)を使うと、エディタ側・フロント側とも想定外のパーツにスタイルが当たってしまう懸念があります。

一つの用途として、ブロックにスタイルを適用した時に「is-style-XXX」といったクラス名がエディタ・フロントともに自動付与されるので、双方のブロックスタイルの見え方を共通のCSSで管理したい、といったケースに使えるのではないかと思います。

style.css

※ファイル名は任意

フロント側で読み込まれるCSS。

アクションフック wp_enqueue_scripts を使って読み込みます。

functions.php
function mytheme_enqueue() {
    wp_enqueue_style( 'mytheme-style', get_template_directory_uri() . '/style.css' );
}
add_action( 'wp_enqueue_scripts', 'mytheme_enqueue');

フロント側で、 style.min.css (+ theme.min.css )の次に読み込まれるCSSです。

一般的に、ヘッダー・フッター・サイドバー等のコンテンツ部以外をスタイリングするためのサイトのベースとなるCSSや、リセットCSS、各種ライブラリのCSSなどを読み込みます。

block-editor-style.css

※ファイル名は任意

アクションフック enqueue_block_editor_assets を使うと、エディタ側で読み込まれます。

functions.php
function mytheme_enqueue_block_editor() {
    wp_enqueue_style( 'mytheme-block-editor-style', get_template_directory_uri() . '/block-editor-style.css' );
}
add_action( 'enqueue_block_editor_assets', 'mytheme_enqueue_block_editor' );

読み込み順は、

  • load-styles.phpの後

  • enqueue_block_assets で読み込んだCSS( style-both.css 等)の前

となります。

また、enqueue_block_assets と名前が似ているので紛らわしいですが、

  • enqueue_block_assets で読み込み → フロント側・エディタ側両方で読み込まれる

  • enqueue_block_editor_assets で読み込み → エディタ側で読み込まれる

という違いがあります。

エディタ側のブロックをスタイリングするためのCSSとして使われます。

editor-style.css

※ファイル名は任意

クラシックエディタでも使われるエディタ側のCSSで、以下のコードで読み込みます。

functions.php
add_theme_support( 'editor-styles' );
add_editor_style( 'editor-style.css' );

読み込み順は、

  • load-styles.phpの後

  • enqueue_block_assets で読み込んだCSS( style-both.css 等)の後

  • enqueue_block_editor_assets で読み込んだCSS( block-editor-style.css 等)の後

と、一番最後に読み込まれます。

特徴として、各セレクターの直前にブロックエディタ全体を囲っている .editor-styles-wrapper というクラスが自動的に挿入されます。
( セレクタがbodyの場合は、 .editor-styles-wrapper に置き換わる )

このCSSいついては使い所がまだ分かっていませんが、クラシックエディタ向けのスタイルを書いたりしたら良いんじゃないかと思います。

まとめ

フロント側

順番 ファイル名 自動/手動 読み込み方・特徴
1 style.min.css 自動 wp_dequeue_style( 'wp-block-library' ); で無効化出来る
2 theme.min.css 手動 add_theme_support( 'wp-block-styles' ); で読み込む
3 style-both.css
(ファイル名は任意)
手動 アクションフック(enqueue_block_assets)内で読み込む
4 style.css
(ファイル名は任意)
手動 アクションフック(wp_enqueue_scripts)内で読み込む

エディタ側

順番 ファイル 自動/手動 読み込み方・特徴
1 load-styles.php 自動 -
2 block-editor-style.css
(ファイル名は任意)
手動 アクションフック(enqueue_block_editor_assets)内で読み込む
3 style-both.css
(ファイル名は任意)
手動 アクションフック(enqueue_block_assets)内で読み込む
4 editor-style.css
(ファイル名は任意)
手動 add_editor_style( 'editor-style.css' ) で読み込む
※各セレクタの先頭に、editor-styles-wrapperというclassが自動で付与される
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC-CUBE の SameSite cookie 対応まとめ

EC-CUBE の SameSite cookie 対応がバージョンごとに散乱していて、わかりづらいのでまとめてみます

SameSite cookie とは

Chrome 80が密かに呼び寄せる地獄 ~ SameSite属性のデフォルト変更を調べてみた がわかりやすいです。

EC-CUBE の影響

標準状態では影響はありません。

  • 標準状態では、クロスサイト Cookie は使用していない
  • 外部サイトから POST で戻ってくるケースは、そもそも CSRF 対策でブロックしている

しかし、 一部の決済連携や独自カスタマイズで影響の出るケースがあります

  • 3Dセキュアや、キャリア決済、ID決済などの決済連携で POST で戻ってくるケース
  • 外部サイトから直接カートに入れるようカスタマイズしているサイト

詳細は EC-CUBE/ec-cube#4457 をご覧ください。

影響の出るケースでは、以下のような不具合が発生します。

  • 決済完了画面が表示されず、エラーとなる。
  • 決済完了画面は表示されるが、ログアウトされた状態となる。
  • 決済完了画面は表示されるが、カートの中身がクリアされない。
  • カートが空になり、ログアウトされた状態となる。

対応にあたっての課題

  • PHP の session_set_cookie_params() 関数は、 PHP7.3未満とそれ以上で振舞いが違うため、PHPバージョンで分岐する必要がある
  • iOS12, macOS10.14 など、SameSite=None に未対応のブラウザがある。これらのブラウザはリリース当時の SameSite standard draft を厳密に実装しているため、 SameSite=None を Strict と認識する
  • SameSite=None の場合は、 secure 属性が必須となる

EC-CUBE バージョンごとの対応方針

基本的に SSL 必須。 今後、 SSL 未対応のサイトでは決済連携できなくなると考えてよいと思われる。

2020年2月17日頃までには、公式パッチがリリースされる予定です。

2系

EC-CUBE/ec-cube2#374 で修正予定。
2.4〜2.13 までの対応方法も上記 Pull Request 参照

3系

  • PHP7.3未満 :: session.storage.options の cookie.path に '; SameSite=None' を追記し、 cookie.secure を true とする
  • PHP7.3以上 :: Symfony/HttpFoundation bundle にパッチを当てる必要がある

上記の対応をすると、 localhost など SSL の使用できない開発環境では、セッションが無効となりカートやログインが動作しなくなるため注意。
また、未対応ブラウザへの対応は個別にする必要がある。

4系

Symfony/HttpFoundation bundle のバージョンアップで対応可能になる予定。

それまでは暫定措置として以下のように対応する

  • PHP7.3未満 :: app/config/eccube/packages/framework.yaml の cookie.path に '; SameSite=None' を追記し、 cookie.secure を true とする
  • PHP7.3以上 :: Symfony/HttpFoundation bundle の修正を適用する

上記の対応をすると、 localhost など SSL の使用できない開発環境では、セッションが無効となりカートやログインが動作しなくなるため注意。
また、未対応ブラウザへの対応は個別にする必要がある。

その他

SameSite=None に対応していない環境の検証をしようと試みた。
が、以下の環境で外部サイトから POST されるケースでは、 SameSite=None が Strict と認識されるようなことは無く、 SameSite 属性は無視されて Cookie が拒否されることは無かった

参考

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

PHP プログラムの呼び出し

PHPで以下の機能があるプログラムを作成しようと思っています。
1.値を受け取る
2.DBに照会してチェックする
3.チェック結果を返す

まず、2と3の機能を作成しました。
思ったように動作しました。
■recieve.php

<?php

error_reporting(E_ALL);

define('DSN', 'mysql:host=localhost; dbname=test_db');
define('DB_USERNAME', 'dbuser');
define('DB_PASSWORD', 'r5b2Wisd');

\$key1 = "key1_A99";
\$key2 = "key2_A99";
/*
\$params = json_decode(file_get_contents('php://input'), true);
\$key1 = \$params['key1'];
\$key2 = \$params['$key2'];
*/

try {
\$db = new PDO(DSN, DB_USERNAME, DB_PASSWORD);
\$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (DOException \$e) {
result(9, 'unable connect to db');
}

try{
//DBに登録があればチェックOK
\$stmt = \$db->prepare("select count(*) cnt from test where key1 = :key1 and key2 = :key2");
\$stmt->execute([
':key1' => \$key1,
':key2' => \$key2]
);
\$row = \$stmt->fetch();
if (\$row['cnt'] == 1) {
result(0, 'already registered');
}
//DBに登録がなく、key1の登録件数が2件以上のときはエラー
\$stmt = $db->prepare("select count(*) cnt from test where key1 = :key1");

\$stmt->execute([
':key1' => \$key1
]);
\$row = \$stmt->fetch();
if (\$row['cnt'] >= 2) {
result(9, 'already exists');
}
//DBに登録がなく、key1の登録件数が2件未満のときは、DBに登録してチェックOKとする
\$sql = "insert into test(key1, key2) values(:key1, :key2)";
\$stmt = \$db->prepare(\$sql);
\$stmt->execute([
':key1' => \$key1,
':key2' => \$key2
]);
result(0, 'registered');
} catch (Exception \$e){
result(9, \$e->getMessage());
}

function result(\$result_code, \$result_message) {
header("Content-Type: application/json; charset=UTF-8");
\$result = array('result' => \$result_code, 'message' => \$result_message);
echo json_encode(\$result);
exit;
}

次に、1の機能(呼び出し)を作成しました。
■send.php
<?php
\$url = 'http://192.168.33.10:8000/recieve.php';
\$data = array(
'key1' => 'key1_A99',
'key2' => 'key2_A99'
);
\$options = array('http' => array(
'method' => 'POST',
'header' => "Content-type: application/json\r\n",
'content' => json_encode(\$data)
));
\$options = stream_context_create(\$options);
\$contents = file_get_contents(\$url, false, \$options);
header('Content-type: application/json');
echo \$contents;

send.phpを呼ぶとエラーになります。
PHP Warning: file_get_contents(http://192.168.33.10:8000/recieve.php): failed to open stream: HTTP request failed! in /home/vagrant/license/send.php on line 13

recieve.phpのパラメーター部分のコメントをどちらに切り替えてもエラーになるので、そもそも呼び出しがまずいのだと思っています。
ですが、そもそも呼び出せないので受け側を完成させることができないです。
(受け側が完成していれば、呼び出し方が悪いと確定するので、ネットで検索しつつ調整することも叶うということです)

Web関係の知識が圧倒的に不足していることは自覚しています。
聞き方(書き方)すら分からないレベルで恐縮ですが、どんなことを試してみれば良いかお教えください。

ちなみに、受け渡しはJSONでなくてもいいです。
プログラムもPHPでなくてもいいです。
ただ、やりたいことは小さいので、フレームワークなどを入れて大げさにしようとは考えていません。

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

PHP にちょっと触れてみたので書いとく

Ruby を少し齧った人間が PHP に触れてみたので後で見返すように書きました。

php タグ

PHP のコードは<?php?>の中に書く

<?php
$name = "伊藤";
print($name);
?>

HTMLに埋め込むのではなく、独立したファイルに PHP のコードを保存する場合は末尾の ?> は省略することができる。終端の ?> の後に改行文字などがあると予期せぬ不具合の元になる為、省略することを推奨されている。

<?php
$name = "伊藤";
print($name);

文の区切り文字

文の末尾には;が必要

$name = "神山"
// ; が抜けているのでダメ

文字列の連結

"Hello " . "world";
// "Hello world"

変数の宣言

// 変数は先頭に`$`が必要
$message = "hello";

出力

Rubyの puts などのように引数の文字列を出力する

var_dump($message);
// var_dump は型などが表示されるのでデバッグ用に使う
echo "hello";
print "world";

入力

// Ruby の gets のように標準入力から文字列を受け取る
print("入力してください");
$input = fgets(STDIN);
print($input);
// Ruby の gets 同様に改行文字が含まれる。 trim 関数を使って前後の空白文字を除去する
print("入力してください");
$input = trim(fgets(STDIN));
print($input);

PHPのbool値

PHPマニュアルよりの引用です。

boolean に変換する場合、次の値は FALSE とみなされます。
boolean の FALSE
integer の 0 および -0 (ゼロ)
float の 0.0 および -0.0 (ゼロ)
空の文字列、 および文字列の "0"
要素の数がゼロである 配列
特別な値 NULL (値がセットされていない変数を含む)
空のタグから作成された SimpleXML オブジェクト

上記以外は TRUE と見なされます。

条件分岐

if 文は条件式の()を省略できない。また PHP の条件分岐は if なので、Ruby のように代入文の右側に置くとエラーになる

if ($age >= 20) {
  print("成人です");
}else {
  print("未成年です");
}

//HTMLに埋め込む
<?php if($age >= 20): ?>
  <div>成人です</div>
<?php else: ?>
  <div>未成年です</div>
<?php endif; ?>

配列

//宣言
$week1 = ["月", "火", "水"];
$week2 = ["木", "金", "土"];
// 要素の型は揃っていなくても良い
$any_type = ["なんでもいいよ", 100, 1.14, true];

//末尾の要素を取り出す
$wday1 = array_pop($week1);
//"水"

//先頭の要素を取り出す
$wday2 = array_shift($week2);
//"木"

//配列の末尾に要素を追加する
array_push($week1, $wday1);
//["月", "火", "水"]
array_push($week1, $wday2);
//["月", "火", "水", "木"]

//複数の配列をつなぎ合わせる
$week = array_merge($week1, $week2);
// ["月", "火", "水", "木", "金", "土"]

//配列の要素を逆順にした配列を返す
array_reverse($week);
// ["土", "金", "木", "水", "火", "月"]

range関数

// range(開始値, 終了値)
range(0, 5)
// [0, 1, 2, 3, 4, 5] の配列を返す
// Ruby の 1..5 のようなもの?

foreach 文

配列から要素を先頭から一つづつ取り出し、ブロック内の処理を適用する。Ruby の foreach メソッドとほぼ同じ。

$week = ["月", "火", "水", "木", "金", "土"];
foreach ($week as $day) {
  print("今日は" . $day . "曜日\n");
}

while 文

条件式の( )は省略不可

$money = 30000;

while($money >= 0) {
  print("あと" . $money . "円\n");
  $money -= 3300;
}

for 文

for($money = 30000; $money >= 0; $money -= 3300){
  print("あと" . $money . "円\n");
}

関数の定義

function 関数名(引数) {
  関数の処理本体
  return 返り値
}
//関数定義
function addNum ($x, $y) {
  return($x + $y);
}

//関数呼び出し
print(addNum(2,4));
//6

ヒアドキュメント

変数 <<< EOM
複数行の
文章を
書く
EOM;

function createMessage($name, $price) {
  $msg = <<< EOM
{$name}ご注文を承りました
ご注文の代金は合計{$price}円です\n
EOM;
  echo $msg;
}

createMessage("伊藤", 300000);
createMessage("高崎", 3000);
//string(89) "伊藤様、ご注文を承りました。
//ご注文の代金は合計300000円です。"

//string(87) "高崎様、ご注文を承りました。
//ご注文の代金は合計3000円です。"

連想配列

Ruby の hash のようにキーと値の組でデータを保持する
連想配列の宣言 {} ではなく [] で括ることに注意

$member = ["name" => "伊藤", "nickname" => "テストマスター"];
$member["name"];
// ”伊藤”
$member["point"];
// 80

//入れ子にもできる
$members = [
  ["name" => "伊藤", "nickname" => "テストマスター"],
  ["name" => "高崎", "nickname" => "スクラムマスター"]
];
$members[1]["name"];
// "高崎"

外部ファイルを読み込む

// 読み込まれる側の記述は今まで通り
//sampleFunc.php
function createMessage($name, $price) {
  $msg = <<< EOM
{$name}ご注文を承りました
ご注文の代金は合計{$price}円です\n
EOM;
  echo $msg;
}

function getPrice($cnt) {
  return 500 * $cnt;
}

// 読み込む側は宣言が必要
require_once __DIR__ ."/sampleFunc.php";

$orders = [
  ["name" => "伊藤", "cnt" => 300000],
  ["name" => "高崎", "cnt" => 300],
  ["name" => "神山", "cnt" => 200],
  ["name" => "服部", "cnt" =>180]
];

// 外部ファイル内の関数 getPrice(), createMessage() を呼び出している
foreach($orders as $order) {
  $price = getPrice($order["cnt"]);
  createMessage($order["name"], $price);
}
// 伊藤様、ご注文を承りました。
// ご注文の代金は合計150000000円です。
// 高崎様、ご注文を承りました。
// ご注文の代金は合計150000円です。
// 神山様、ご注文を承りました。
// ご注文の代金は合計100000円です。
// 服部様、ご注文を承りました。
// ご注文の代金は合計90000円です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CakePHP2のオートロードを「最近のやり方」で考える

TL;DR

  • 「Composerの利用を前提に」作る場合、CakePHP2アプリケーションでも独自のオートロードから脱却して良いのではないか
  • 根幹となるのはComposerのclassmap autoload
  • CakePHP2のオートロードを無くすわけではない(できない)ので、共存させつつ恩恵を受ける方法
    • コアコード等の読み込みはCakePHPのクラスローダーに依存させる等

狙いと背景

狙い

元々、業務上の問題に対するアプローチを模索して得たアイディアです。
狙うのは「テストの信頼性をあげる」ことにあります。

後述する通り、素のCakePHP2の機構では単体テスト実装上のアンチパターンを踏みやすいというリスクがあります。これを改善したい、というのが狙いです。
より正確に言うなら、「テストの質を高める」というよりは、SUT側の挙動の変更によって「テスト実行時とプロダクト側の実行時の状態を近づける」というアプローチです。端的に言えば「テストとしてCI上で実行した時だけ動くコード」を排除したい、というのが取り組みのコアです。

「プログラマーの責任」を減らして「ミスが減る」という世界 になる訳です。

思考を停止して述べれば、この種の不安は(テストにだけ頼るなら)徹底的経路テストによって成仏させるしか無さそうですが、緩和するだけならSUT側を「easy」に動くようにすることでも可能なのでは・・・という風に整理しております。

背景

CakePHP2は「PHPのオートロード1以降」「Composer時代より前」に開発されたフレームワークである、という認識からスタートしましょう。
そのために、App クラスを用いた独自の機構を実装しています。
-> App::loadbootstrap

ざっくりと言えば、この 「App::loadで探索可能なクラス(パス)を登録する」のが App::uses() で、また App::import() はuses()に加えて即時的なファイルの読み込みも行う〜というものです。

namespaceの有るや無しやとオートロード

問題となるのは、namespaceの利用を前提とした(PSR-4のような)「読み込み」でない場合、オートロードにより意図せぬ副作用がもたらされる可能性が高くなる という点です。

PHPのオートロードというのは、結局の所「その行を実行した時点で、呼び出されているクラスが存在しなかったら、いい感じにファイルを読み込む」というものになります。掻い摘んで言えば「新しくファイルを include 'hoge';する」という機能です。
ちょ〜素朴にコードを示せば

if (!lcass_exists('Hoge')) {
    include 'Hoge.php';
}

です。
ここまで簡潔化すると「何が起きそうか」が透けて見えてくるのではないでしょうか。
すなわち、「どっかのファイルの実行時に行われたオートロードによって、その後に処理される別のファイル上でもHogeクラスを使える」という状態が生じます。

これは、namespace云々やPSR-0/4とは関係なく起きていることです。
例えばCakePHP2において、「ArticlesController::beforeFilter()の中で$this->loadModel('Author');した場合、 /articles/create ページ(action)において 実行される $this->Article->doSome()メソッドの中では無条件に Authorモデルの(static)メソッドを呼び出せる」という事になります。

とりわけ「PHPプログラムの実行単位」という意味でいうと、(CI上など)複数の単体テストを連続で行う場合には「全ての副作用を最後まで引き継ぐ」ということになります。
そうしてアンチパターンであるErratic Testを誘発し、Independentであることを侵害されかねません。

では、なぜ「namespaceがしっかりしていると安心感が有る」のでしょうか?
ここではPSR-0か4かの違いは問題になりません。2
肝要なのは、「利用者(プログラマーではなく、実行者=コンピューター)が、呼び出したい対象を 明確に 意識できる」という点だと思います。これは実際、すごくシンプルな話です。

例えばHashクラスを使いたい場合(これはCakePHPを使うとほぼ確実に読み込まれ済みですが)を例に考えてみましょう。

// cake2: namespaceなし
Hash::flatten($ar);

// cake3: namespaceあり
\Cake\Utility\Hash::flatten($ar);

後者の方が 詳細に呼び出される事になります。(ファイルの冒頭で use \Cake\Utility\Hash; を宣言していても同じ事です。)
また、PSR-4ベースのオートロードであれば、クラスメソッドを呼び出すために\Cake\Utility\Hash::{$method} と書いた時点で、未読み込みクラスを解決してくれることになります。
このように、「クラスの存在を意識すれど、クラスを読み込めるかどうかを意識しなくていい」というのがPSR-0/4及びそれをベースとしたオートローダーのもたらす開発体験である、という事ができます。
CakePHP2のオートロードであっても「やりたいこと」は同じですが、そのやり方が(開発者にとって)自然で、シームレスになるのです。

なぜCakePHP2のオートロードは「まどろっこしく」なっているのか

では、何故にこのような「真の意味で勝手に読んでくれる」状態が実現できるのか?あるいは出来ないのか?という疑問があります。

PSR-0/4の方は、クラス名(FQCN)から「どのパスにあるのか」を解決することが出来ます。
他方で、「namespaceを利用していない」場合に、クラス名から「どこのパスに有るファイルか」を特定するのは大変です。

例えば Bridge という名前のクラスがあったら、どうでしょうか?suffixのない単数形の名称なので、Lib Utility そして Model 辺りでしょう。このように、どの(サブ)パッケージがあればパスを解決しに行くことが出来ます3

App::uses('Bridge', 'Model');
$brigde = Bridge::build();

いかがでしょう、ともすればおまじない的で見覚えのあるコードが「理屈が透けて見える」ようになったのではないでしょうか。
根底にある「使いたい対象であるクラス名」という与えられた情報から取得できる情報が少なすぎる・・という問題に対して、このように「人力で」読み込み対象のクラスファイルについてヒントを与えておこう!というのが、CakePHP2形式のアプローチなのだなと思いました。

どうするか

改めて話を整理すると、我々が実現したい世界は「意識することが少なくても、ちゃんと動くコードが書ける」というものです。それに関連して、「テストの時だけどうにかなった」を避けたい・・という気持ちを持っています。

PSR-0/4及びそれをベースとしたオートローダーの活用は、そうした状態への到達を支援できているように思います。それを雑に云うなら、「使おうと思ったクラスが呼び出したとおりに存在する」という点に本質を求めらそうです。

ということで、「我々のCakePHP2」の世界において如何にそれを実現するか?を考えます。

Composerの活用: CakePHP2が標準としていなかった手段が用意されている

CakePHP2は「Composer以前」のフレームワークなので、Comopserに依存しないように作られていました。
今回の話でいうと、ComposerはPHPアプリケーションの開発において2つの「違い」をもたらしている、と言えると思います。

  1. PSR-0/4を含め、オートロードの手段を提供している
  2. デプロイ時点で利用される「セットアップ」手順の提供
    • composer install のことです!言い方難しい・・

ここまでに「パスの解決」「クラス名とファイルの紐付け」に関するコストについて、何度か言及してきました。
具体的には「ファイナライズ」のような、アプリケーション実行に際しての事前処理です。これを行う(事を前提とした)のを可能とした点がデカいな〜と感じています。

これによって、やろうと思えば「利用されるクラスを全てオートロード可能にしておく」ための処理、コストが高いと言われた「パスの解決をやっておいちゃう」余地が生まれます。

Composerのautoloadファイル

Composerのオートロードにおける「解決先」の指定(=CakePHP2でいうApp::build()に当たるような、どの名前(ないしパッケージ)がどこにあるかを登録する処理)はcomposer.jsonにて行われます。
3つの遅延読み込みと1つの即時(事前)読み込みのためのスキーマがあります。

  • 遅延読み込み
    1. PSR-0
    2. PSR-4
    3. ClassMap
  • 即時読み込み
    1. Files

よく使うのはPSR-4だと思いますが、今回は「ClassMap」に着目します。

https://getcomposer.org/doc/04-schema.md#classmap

Comopserがautoloadファイルを吐き出す際に、ココに指定されたパス(ディレクトリ、ファイル名)にあるクラスを読み取って[$fqcn => $filePath]という形式の連想配列 = クラスマップ を作成します。そうしてできあがるのが、 autoload_classmap.php というファイルです。

内容を見ていきましょう。

公式ドキュメントでは以下のような記述が例示されています。

{
    "autoload": {
        "classmap": ["src/", "lib/", "Something.php"]
    }
}

イメージをつかみやすくするために、次のようなファイルを作ってみました。

$ tree
.
├── Something.php
├── composer.json
├── lib
│   └── Utility.php
└── src
    ├── Entry.php
    └── Model
        └── User.php

この状態で composer dumpautoload を実行し、生成された(classmapの)オートロードファイルは次のようになります。

// vendor/composer/autoload_classmap.php
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'App\\Entry' => $baseDir . '/src/Entry.php',
    'App\\Model\\User' => $baseDir . '/src/Model/User.php',
    'NotAnything' => $baseDir . '/Something.php',
    'Something' => $baseDir . '/Something.php',
    'Utility' => $baseDir . '/lib/Utility.php',
);

このファイルは vendor/autoload.php を起点として読み込まれるものですが、コレさえあれば App::uses() のような手間をアプリケーション側に持たせずとも「NotAnythingクラスがどこにあるか分かる」訳です。

// src/Model/User.php
namespace App\Model;

class User
{
}
// Something.php
class NotAnything
{
}

class Something
{
}

このように、PSR-0/1/4の制約に縛られずに、「名前空間とファイルパスがどうなっていようと、同じファイルに複数のクラスが定義されていようと、構わずに FQCNとファイルパスのマップを作成する 」という挙動となります。

autoload_classmapの利用コスト

このように「愚直なクラスマップを作成する」機能にておいては、使えば使うほど容易に「クラスマップが肥大化する」という事態を招きます。
単なる連想配列ですが、今まで使い慣れていないものでもあると「パフォーマンス的にどうなのか?」という疑念も湧きます。

基本的には、(PSR-4ベースのオートロードよりも)低コストにクラスのオートロードを実現させるものだと考えて良いはずです。
実際に、Composerの「最適化オプション」である composer dumpautoload -o (あるいは comopser install -o でも良いですが)のアイディアは、「 通常であればautoload_psr4.phpに入るような内容も、autoload_classmap.phpに入れてしまおう 」というものになります。

なので、少なくとも「普段からComopserのパッケージ(PSR-4によるautoload)を使っている」局面においては、「classmapベースのオートロードが遅いか速いか」は気にするようなコストではなさそうに思います。

この話題については、StackOverflow上にいい感じな議論があるので確認してください。
php - Why use a PSR-0 or PSR-4 autoload in composer if classmap is actually faster? - Stack Overflow
また、この中で紹介されているblogも有用です。
Patrick Allaert's Blog about Free Software: Benchmarking Composer autoloading, Symfony and eZ Publish 5

ということで、結論としては「開発・実装における安心感と細やかなパフォーマンスチューニングによって受けられる恩恵」を天秤にかけることになると思います。その上で、「かなり肥大化しなければ目につく問題にはならなそう」であり、「最終的にはベンチとってみるしかないよね」という事も意識しながら、「どこまでやるか」の落とし所を見つけていくと良さそうです。


  1. 正確に言えば 「SPLのautoload」というニュアンスになります。双方の違いについてはこちら 

  2. 「PSR-0だから」といっても、オートロードでやるべきことは「区切り文字で区切ってパス(ディレクトリ名+ファイル名)を探し出し、includeする」です。例えばPackagistを漁ったらこんな実装が見つかりました。 https://github.com/Respect/Loader/blob/0.2.1/library/Respect/Loader.php 

  3. 「どこのディレクトリを探しに行くか?」という情報は、App::build()で予め登録している内容が基になります。また、「全く独特な場所から探しに行き、かつAppクラスのクラスマップに登録する」場合にはApp::improt()に適切な引数を渡して利用することになります。 

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

TwitterのREST APIで取得したツイートの日付をPHPで日本標準時に変換

よく忘れるので。
タイムゾーンはAsia/Tokyoであることが前提。

date( 'Y-m-d H:i:s', strtotime($tweet['created_at']) );

これで
Mon Feb 10 05:12:03 +0000 20202020-02-10 14:12:03になる。

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

htmlspecialchars関数についてのメモ

htmlspecialchars関数とは

・HTMLの特殊タグをエスケープ(変換)する関数。
ユーザーからの入力を受け取る際、XSS等悪意のあるスクリプト挿入などの攻撃を避ける。

例) & → '&amp;'
   < → '&lt;'
   > → '&gt;'
   " → '&quot;' ENT_NOQUOTEを設定していない時
   ' → '&#039;' ENT_QUOTES設定時

qiita.php
<?php 
$sample = htmlspecialchars("<script>window.location='http://yahooooooooo.coooo.jp';</script>", ENT_QUOTES, 'utf-8');
echo $sample;
//&lt;script&gt;window.location=&#039;http://yahooooooooo.coooo.jp&#039;;&lt;/script&gt; 
//ブラウザ上で右クリックでページのソースを表示するとエスケープされているのがわかる。
?>

・htnlspecialcharsを使用するタイミングはDBの内容を画面に呼び出す直前にする。
・DBに保存するタイミングで使用すると変換された文字列がDBに保存されてしまい、DBの内容を「HTMLへ出力する」以外の用途に使う場合に支障が出る可能性がある。

打つのが面倒なので関数化

qiita.php
<?php 
function h($str){
    echo htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

$sample = h("<script>window.location='http://yahooooooooo.coooo.jp';</script>");
echo $sample;
//&lt;script&gt;window.location=&#039;http://yahooooooooo.coooo.jp&#039;;&lt;/script&gt; 
?>
入力フォーム等改行がある入力を受け取る場合
qiita.php
function hbr($str){
    echo nl2br(h($str));
}

を追加してみた。

関数化の際、echoかreturnのどちらが適しているのかが気になっている点です。ご指摘ありましたらお願いいたします。

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

"the requested PHP extension zip is missing from your system"のエラー解決法

当方Macです。
terminalで以下を入力。

pecl install zip

これでいけました。

もし peclコマンドがないといわれたら、homebrewでPHPをインストールしてみてください。

brew install php@7.4

再度、以下を入力

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

array_filterでarray?ってなったお話

前置き

  • array_filter は何か便利そうだが、よく分かっていないので使っていないまま
  • 使えるようになりたい!ので整理する

array_filter とは?

callback 関数によりフィルタ処理が行われた array の全ての要素を含む配列を返します。 callback 関数が TRUE を返した場合、 array の現在の値が結果の配列に入ります。 配列のキーは保存されます。
引用元 : PHP_ array_filter - Manual

  • ・・ざっくり「第二引数でtrueを返したものだけを配列として返す(それ以外は削除する。配列のキーはそのまま)」

使ってみた

$ary = [0, 1, 2, 2, 3];

$tmpAry = (array_filter($ary, function($num){
    return $num == 2;
}));

for($i=0; $i<count($tmpAry); $i++){
    var_dump($tmpAry[$i]);
}

/* 出力結果 : 
NULL
NULL
*/

NULL??

  • array_filterでフィルタリングした内容をvar_dumpで出力し、値を確認してみます
$ary = [0, 1, 2, 2, 3];

$tmpAry = (array_filter($ary, function($num){
    return $num == 2;
}));

var_dump($tmpAry);

/* 出力結果 : 
array(2) {
  [2]=>
  int(2)
  [3]=>
  int(2)
}
*/

検証結果

  • フィルタに引っかかったものだけが(配列のキーはそのままに)残る
  • 上の例では、0番目と1番目が存在しないので NULL と出力された
  • 次:空いている要素を埋めたいので array_merge を使う

array_merge とは?

前の配列の後ろに配列を追加することにより、 ひとつまたは複数の配列の要素をマージし、得られた配列を返します。
(中略)
入力配列の中にある数値添字要素の添字の数値は、 結果の配列ではゼロから始まる連続した数値に置き換えられます。
引用元 : PHP_ array_merge - Manual

  • ・・ざっくり「配列を結合し、添え時の数値は0から振り直してくれる」(結合対象の配列がない場合は前者は無効、後者は有効)

使ってみた

$ary = [0, 1, 2, 2, 3];

$tmpAry = (array_filter($ary, function($num){
    return $num == 2;
}));

var_dump($tmpAry);

/* 出力結果 : 
array(2) {
  [0]=>
  int(2)
  [1]=>
  int(2)
}
*/

$tmpAry = array_merge($tmpAry);

for($i=0; $i<count($tmpAry); $i++){
    var_dump($tmpAry[$i]);
}

/* 出力結果 : 
int(2)
int(2)
*/

まとめ

  • array_filter後にfor文を制御変数で回す場合は、まずarray_mergeで空いている要素を前に詰める
  • foreachで回す場合も、「array_filter後の配列のキーはそのまま」ということを意識する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む