20210322のPHPに関する記事は14件です。

カスタムフィールドのインストールから出力方法のまとめ

どうも7noteです。カスタムフィールドのよく使う項目の使い方をまとめました。

プラグインの「Advanced Custom Fields」のインストールから、phpでの出力方法までを私が分かりやすく私が使いやすい用に私が良く使うものだけをまとめた記事です。

※すべての機能や変数は紹介しておりません。すべての出力が知りたい方はこちらの記事がわかりやすかったのでこちらへどうぞ。
https://bazubu.com/advanced-custom-fields-36452.html

①「Advanced Custom Fields」のインストールから準備

  1. Wordpressの管理画面から「プラグイン」→「新規追加」から、検索覧に「ACF」と打ち「Advanced Custom Fields」をインストールし有効化
  2. 同じく管理画面、サイドメニューの「カスタムフィールド」をクリック。
  3. 「新規追加」を押し、追加したいフィールドを作成。完了したら「位置」の欄で表示したい投稿タイプを設定。
  4. 設定したフィールドに値を入力。

これで準備OK。出力方法へ↓↓↓

②「Advanced Custom Fields」をPHPで出力する方法

「基本」

取得するもしくは表示するの2つの方法がある。

index.php
/*~~~取得する方法~~~*/
$hogehoge = get_field("フィールド名を入れる");

/*~~~表示する方法~~~*/
the_field("フィールド名を入れる");
// もしくは取得して表示
echo get_field("フィールド名を入れる");

「応用」

配列になっている値の取得

/*~~~画像(※出力方法:配列)~~~*/
$hogehoge = get_field("画像のフィールド名")["url"] //画像URL

/*~~~ラジオボタン・セレクトボックス~~~*/
$hogehoge = get_field("フィールド名")["value"] //Value値(英語)
$hogehoge = get_field("フィールド名")["label"] //label値(日本語化)

/*~~~チェックボックス(※チェックされているものすべて取得)~~~*/
$items = get_field('フィールド名');
if( $items ){
  foreach( $items as $item ){
    echo $item['value'];
    echo $item['label'];
  };
};

グループ化した情報の取得

$hogehoge = get_field("Groupのフィールド名")["子フィールド名"];
// 
$hogehoge = get_field("Groupのフィールド名")["子Groupのフィールド名"]["孫フィールド名"];

まとめ

まだまだあるので随時更新予定です。

おそまつ!

~ Qiitaで毎日投稿中!! ~
【初心者向け】WEB制作のちょいテク詰め合わせ

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

coc.nvimでPHPStanやPHP_CodeSnifferを使う

coc.nvimはVimのLSPクライアント・プラグインの一つです。

cocは自分自身がVimプラグインでありながら、拡張機能(エクステンション)の機構を持っており、LSPサーバをエクステンションをとしてインストールすることで機能を追加することができます。

PHPのLSPサーバのcocエクステンションもいくつかあります。例えばPHPのLSPサーバであるIntelephenseのcocエクステンションとしてcoc-phplsがあります。

coc-phplsをインストールするとcoc経由でPHPの自動補完・定義ジャンプ・ホバー(ヒント)表示・バグ検出などの機能を使えるようになります。

coc-phplsだけでもある程度のバグ検知は可能ですが、Intelephenseが提供するバグ検出は現時点ではPHPStanに比べると弱く、またPHP_CodeSnifferのようにコーディング規約違反を検出するものではありません。

こういうときに使えるのがdiagnostic-languageserverのcocエクステンションであるcoc-diagnosticです。

diagnostic-languageserverは各種linterと統合することで診断機能を提供する汎用言語サーバです。そのcocエクステンションであるcoc-diagnosticを使えば、PHPStanやPHP_CodeSnifferをcoc経由で使えるようになります。

インストール方法

  1. coc-diagnosticをインストールする。

    :CocInstall coc-diagnostic
    
  2. PHPStan、PHP_CodeSnifferをインストールする。

    composer require --dev phpstan/phpstan
    
    composer require squizlabs/php_codesniffer
    
  3. coc-settings.jsonに下記を設定する。

~/.vim/coc-settings.json
{
    "diagnostic-languageserver.filetypes": {
        "php": ["phpcs", "phpstan"]
    }
}

coc-diagnosticはデフォルトの設定./vendor/bin/phpstan./vendor/bin/phpcsを実行するようになっているので、Composerでローカルインストールしたのであればこれだけで使えるはずです。

こんな感じでcoc経由でPHPStanやPHP_CodeSnifferの出力が見れると思います。

スクリーンショット 2021-03-22 014726.png

linterの設定をカスタマイズする

coc-diagnosticはlinterの設定をカスタマイズすることもできます。下記に例を載せます。

~/.vim/coc-settings.json
{
    "diagnostic-languageserver.mergeConfig": true,
    "diagnostic-languageserver.linters": {
        "phpcs": {
            "command": "./tools/phpcs",
            "args": ["--report=emacs", "-s", "-"],
        },
        "phpstan": {
            "command": "./tools/phpstan"
        }
    }
}

diagnostic-languageserver.mergeConfigtrueに設定すると、追加した設定をデフォルトの設定とマージしてくれるので、差分だけを記載することができます。

上記の例ではcommandで実行ファイルのパスをComposerではなくPHIVEでローカルインストールした場合のパス(./tools/phpstan./tools/phpcs)に変更しています。また、PHP_CodeSnifferのデフォルトのargsから"--standard=PSR2"を削除することで、プロジェクトディレクトリ配下にあるphpcs.xmlを使うようにしています。

Dockerコンテナ上のPHPを使う

私はDocker(Laradock)を使って開発しており、ローカルにはPHPをインストールしていません。なので当然ですが、ローカルのPHPStanやPHP_CodeSnifferを実行できないため、上記の設定では動きません。こんな場合でも設定をひと工夫すれば、少し無理やりですがDockerコンテナ上のPHPを使って動かすことができます。

~/.vim/coc-settings.json
{
    "diagnostic-languageserver.mergeConfig": true,
    "diagnostic-languageserver.linters": {
        "ngmy.laradock.phpcs": {
            "command": "docker",
            "debounce": 100,
            "rootPatterns": ["composer.json", "composer.lock", "vendor", ".git"],
            "args": ["exec", "-u", "laradock", "laradock_workspace_1", "php", "tools/phpcs", "--report=emacs"],
            "offsetLine": 0,
            "offsetColumn": 0,
            "sourceName": "phpcs",
            "formatLines": 1,
            "formatPattern": [
                "(^.*):(\\d+):(\\d+):\\s+(.*)\\s+-\\s+(.*)(\\r|\\n)*$",
                {
                    "sourceName": 1,
                    "sourceNameFilter": true,
                    "line": 2,
                    "column": 3,
                    "message": 5,
                    "security": 4
                }
            ],
            "securities": {
                "error": "error",
                "warning": "warning"
            }
        },
        "ngmy.laradock.phpstan": {
            "command": "docker",
            "debounce": 100,
            "rootPatterns": ["composer.json", "composer.lock", "vendor", ".git"],
            "args": ["exec", "-u", "laradock", "laradock_workspace_1", "php", "tools/phpstan", "analyse", "--error-format", "raw", "--no-progress"],
            "offsetLine": 0,
            "offsetColumn": 0,
            "sourceName": "phpstan",
            "formatLines": 1,
            "formatPattern": [
                "^/var/www/([^:]+):(\\d+):(.*)(\\r|\\n)*$",
                {
                    "sourceName": 1,
                    "sourceNameFilter": true,
                    "line": 2,
                    "message": 3
                }
            ]
        }
    },
    "diagnostic-languageserver.filetypes": {
        "php": ["ngmy.laradock.phpcs", "ngmy.laradock.phpstan"]
    }
}

PHPStanとPHP_CodeSnifferの新しいlinter設定を追加しています。設定内容はデフォルトの設定をベースにしています。デフォルトの設定を上書きしていない理由は、Docker越しに使うという用途が特殊なので、デフォルトの設定を残しておきたいと思ったからです。設定名はなんでも良いのですが、デフォルトの設定と被らないようにしています。

commandargsを使ってdocker execを実行することで、Dockerコンテナ(私の場合はLaradockのworkspaceコンテナ)でPHPStanやPHP_CodeSnifferを実行するようにしています。

PHPStanのデフォルトのargsの最後にある"%file"を削除しています。%fileはdiagnostic-languageserverが用意しているargsで使える変数で、Vimで開いているファイルのフルパスに展開されます。他にもいくつかの変数が用意されています。削除する理由は、フルパスに展開されるとローカルのパスとDockerコンテナ上のパスが食い違ってしまい解析できないためです。削除すると、プロジェクトディレクトリ配下のphpstan.neonを使うようになるので、そこに記載されている全対象ファイルに対してPHPStanが実行されるようになります。

全対象ファイルに対して実行されたPHPStanの出力を今Vimで開いているファイルに対してだけ表示するために、formatPatternの正規表現を変更して相対パスを抽出してSourceNameとして使用しています。さらに複数ファイルを開いたときに出力が混ざらないように、sourceNameFiltertrueにしています。

もし相対パスに展開してくれる変数があればそれで済む話ですが、今のところないようなので、こんな設定をしています。

同じ理由で、PHP_CodeSnifferもargsから"-s", "-"を削除して、formatPatternを変更しています。

diagnostic-languageserver.mergeConfigtrueにしているのはコンテナ名を上書きできるようにするためです(後述)。

全対象ファイルに対して実行しているせいか少し処理が遅い気がしますが、数十ファイル程度の小さなプロジェクトであれば許容範囲内に感じています。

任意のDockerコンテナ上のPHPを使う

プロジェクトごとにLaradockをインストールしている場合、プロジェクトごとにコンテナ名が異なると思います。そういうときはプロジェクトディレクトリの配下に.vim/coc-settings.jsonを作ります。

cocは~/.vim/coc-settings.jsonとプロジェクトディレクトリ配下の.vim/coc-settings.jsonをマージしてくれます。この辺りの仕様についてはここに記載があります。

some-project/.vim/coc-settings.json
{
    "diagnostic-languageserver.linters": {
        "ngmy.laradock.phpcs": {
            "args": ["exec", "-u", "laradock", "laradock-some-project_workspace_1", "php", "tools/phpcs", "--report=emacs"]
        },
        "ngmy.laradock.phpstan": {
            "args": ["exec", "-u", "laradock", "laradock-some-project_workspace_1", "php", "tools/phpstan", "analyse", "--error-format", "raw", "--no-progress"],
        }
    }
}

argsでコンテナ名を変更しています。

こんな感じの設定を、cocを使いたい各プロジェクトに書いています。

以上です。

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

【PHP8.1】その配列、純粋配列?

PHPには配列と連想配列の区別がありません。
むしろPHPの配列は順序付きハッシュであり、その中でも数値のキーは特別に連番で扱える、みたいな扱いです。
他言語で言うところの配列はPHPにはありません。

$arr = [
    'hoge' => 'fuga',
    'foo' =>  'bar',
    'x', // キーは0になる
    3=>'y',
    'z' // キーは4になる
];

しかしまあ区別したいよねってことで、Add array_is_listというRFCが提出されました。
既に受理されており、PHP8.1にて実装されます。

Add array_is_list(array $array): bool

Introduction

PHPの配列は、整数キーと文字列キーの両方をサポートしており、さらに順序が保証されているという珍しい型です。

変数値が配列であるかのチェックは簡単ですが、その配列は連想配列だったり、オフセットの欠けた配列だったり、順序が異なっていたりする場合があります。
配列のキーが連続した整数であるか否かを確認することは、モジュールに渡すデータおよびモジュールから返されたデータのどちらにとっても有用なものです。
またシリアライザにとっても、配列と連想配列を区別するための効率的なチェック方法があると便利です。
たとえばjson_encodeは、配列を['0':0, '2':1, '1':1]とシリアライズしたいのか、[0, 1, 2]とシリアライズしたいのかを判定したいでしょう。

Proposal

新しい関数array_is_list(array $array):boolを追加します。
配列のキーが0から始まる数値で順番に並んでいる場合にtrueを返します。
それ以外の配列であればfalseを返します。
配列でない場合はTypeErrorをthrowします。

このRFCは、PHPの型システムを変更するものではなく、新しい型を追加することもありません。

この関数は、以下のPolyfillと同等です。

function array_is_list(array $array): bool {
    $expectedKey = 0;
    foreach ($array as $i => $_) {
        if ($i !== $expectedKey) { return false; }
        $expectedKey++;
    }
    return true;
}


$x = [1 => 'a', 0 => 'b'];
var_export(array_is_list($x));  // false キーが順番ではない
unset($x[1]);
var_export(array_is_list($x));  // true キーが0開始の配列である

// 単純にarray_valuesで比較するとNaNの罠がある
$x = ['key' => 2, NAN];
unset($x['key']);
var_export($x === array_values($x));  // false 
var_export($x);  // array (0 => NAN)
var_export(array_is_list($x));  // true キーが0開始の配列である

// 配列ではない
array_is_list(new stdClass());  // TypeError
array_is_list(null);  // TypeError

正しいPolyfillを書くためには注意して罠を避ける必要があります。
たとえばarray_values($array) === $arrayはNaNを含む配列でfalseになり、array_keys($array) === range(0, count($array) - 1)は空の配列で正しくなりません。

ネイティブ実装ではマクロHT_IS_PACKED(array) && HT_IS_WITHOUT_HOLES(array)を使えばほとんどの配列を正しく判定可能で、これは既にjson_encodeで使われています。

Proposed PHP Version

PHP8.1。

Discussion

Possibility of naming conflicts with future vector-like types

将来ベクトル型が導入されたときに命名が衝突しない?

この関数は元々is_listという名前にするつもりでしたが、可能性の指摘を受けたため名前を変更しました。
またis_listだと、PHP組み込みのSplDoublyLinkedList等のList型と混同してしまう可能性があります。

array_is_listは、配列しか受け付けない点で他の多くのarray_*関数と共通しています。

投票

2021/01/06から2021/01/20にかけて投票が行われ、賛成41反対1で可決されました。

Rejected Features

却下・拒否された仕様や実装。

Alternate names

関数名is_sequential_arrayarray_is_sequentialは、[2=>'a', 3=>'b']もシーケンシャルなので却下されました。
関数名is_zero_indexed_arrayarray_is_zero_indexedは、この用語はあまり使われていないので却下されました。

Alternate implementations

シグネチャはis_array_and_list(mixed $value): boolも検討されましたが、オブジェクトにfalseを返すのは直感的ではなく、またSplDoublyLinkedListArrayObjectなどに対する挙動が勘違いされる可能性があるため却下されました。
この関数は、連続したキーを持ち、なおかつ0始まりの配列に対してのみtrueを返します。

Adding flags to is_array()

is_array引数を追加するというアイデアは却下されました。

is_array($value, ZERO_INDEXED | ASSOCIATIVE | INTEGER_INDEXED)

基本的な型関数をシンプルにしておくことは言語の学習や読解にとって大事であり、またパフォーマンスに僅かな悪影響が発生します。

Changes to PHP's type system

このRFCはPHPの型システムを変更しません。
後方互換の無い厳格な型システムへの変更は可能だとは思いますが、そこまでする必要のない選択であり、実装も難しいと思われます。

感想

助けて!
SplFixedArrayちゃんが息をしていないの!!

RFCどころかメーリングリストですら誰一人SplFixedArrayに触れていないとかいう。

個人的にはPHPの配列と連想配列を厳密に区別する必要も意味もあまりないと思っているのですが、一般的には実はそうでもなかったみたいで、圧倒的多数の賛成によって導入が決定しました。
この関数によって、PHPにおける配列から、他言語で言うところの配列を区別することが可能になります。

まあPHP内においては区別する意味があまりないので、本文中にもあるように、JSON等で他言語とデータをやりとりするときに使うのが一般的な使用法となるでしょう。
むしろC言語側から使われて、PHPからはあまり使われないんじゃないかという気がしないでもない。

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

Apache2 の PHP で virtual を使う (ファイル書き込み)

こちらのファイルを改造して、ファイルの書き込みをするようにしました。
Apache2 の PHP で virtual を使う

次のフォルダーに書き込みます。

/var/log/apache2/php
/var/log/apache2/perl

test_virtual.php
<?php
// ------------------------------------------------------------------
//  test_virtual.php
// ------------------------------------------------------------------
// fputs ('STDERR',"*** start ***\n");
// fputs (STDERR,"*** aaa ***\n");
$fp_out = fopen('/var/log/apache2/php/php_aa.log', 'w');
fputs ($fp_out,"*** test_virtual.php *** start ***\n");
fputs ($fp_out,"*** bbb ***\n");
fputs ($fp_out,"*** ccc ***\n");
fputs ($fp_out,"*** ddd ***\n");
fputs ($fp_out,"*** Mar/22/2021 PM 17:09 ***\n");
fputs ($fp_out,"*** test_virtual.php *** end ***\n");
fclose ($fp_out);

echo '<!DOCTYPE html>';
echo '<html  lang="ja">';
echo "<head>";
echo '<meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" />';
echo "<title>virtual</title>";
echo "</head>";
echo "<body>";
echo "<h2>test_virtual.php</h2>";
echo exec('whoami');
echo "<p></p>";
echo "<p>Mar/22/2021 PM 15:30</p>";
echo "</body>";
echo "</html>";
?>
test_script.pl
#! /usr/bin/perl
#
#   test_script.pl
#
print STDERR "*** test_script.pl *** start ***\n";
print STDERR "*** Mar/22/2021 PM 15:38 ***\n";
#
print "Content-type: text/html; charset=utf-8\n\n";
print "*** test_script.pl ***<br />\n";
print "Hello<br />\n";
print "Good Morning<br />\n";
print "Mar/22/2021 PM 15:59<br />\n";
print "<p></p>\n";
#
#
$file_out = "/var/log/apache2/perl/perl_aa.txt";
open (OUT,">$file_out");
binmode OUT, ":encoding(UTF-8)";
print OUT "*** test_script.pl *** start ***\n";
print OUT "*** test ***\n";
print OUT "*** Mar/22/2021 PM 17:13  ***\n";
print OUT "*** test_script.pl *** end ***\n";
close (OUT);
#
print STDERR "*** test_script.pl *** end ***\n";
#
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

安易にPHPのバージョンを上げたら色々ハマった(TwitterOAuth編)

はじめに

自分の忘備録で記事を書いております。技術的に至らない点多々ありますがご容赦下さい。

今回の記事

前回の記事
安易にPHPのバージョンを上げたら色々ハマった(imagick編)
https://qiita.com/cafe-capitola/items/4c9ba9faad997b299fd9 
の続き。

今回のテーマは「PHPのバージョン上げたら今まで動いてたTwitterOAuthが動かなくなっちゃった」です。

私はTwitterにて細々とbotを運営しています。
仮面ライダーBLACKクレジットbot( https://twitter.com/black_staffrole )

はい、オタクです。しかも色んな意味で炎上してる番組です。

仕様としては番組の1話から最終話、劇場版2作におけるオープニングとエンディングに表示されるキャスト・スタッフのデータをひたすらタイプしたCSV化したファイルを用意。たまに本家の写植が間違ってるとかありますけど、それを突き詰めるとキリがないのでそのままパンチ。
30分毎にファイル内のデータをランダムに1レコード出力するという非常に単純かつ簡単なものです。
データは合計3000件オーバー。データづくりよりも、Twitter社との「認証キーを下さい」の長文英語メールを5回ぐらいやり取りした方が辛かった。google先生の翻訳機能様様です。

実はあまりにこの番組を取り巻く環境のカオスさに耐え切れなくなり、今年の頭にこっそりと一部処理を追加していますが、その話は今回のテーマに沿わないので割愛します。

我が家にはbot投稿用兼テスト用LAMP環境としてRaspberryPi3+を使用しています。
開発機のPHPは8.0.2。開発機と実行環境のバージョンが違うのもどうかと思い同じバージョンに揃えました。7.0.X→8.0.2にガッとバージョン上げました。
PHPのバージョンをあげたら、
sudo apt-get install php-curl
するのを忘れずに。

ハマった

botはcronによるphpの実行で動かしています。cronで実行が失敗した時には「失敗しましたメール」が飛んでくるようになっています。
…数分後、やってきました失敗メール。

エラー内容を見る限り、どうやら今まで使用してきたTwitterOAuthのライブラリがPHP8系では動かないようです。

TwitterOAuthの新しいバージョンを落とす

TwitterOAuthの公式ページを見に行くと、自分が当初開発していた頃に比べてあまりにもガラッと変わっていて面食らいました。

Googleで検索をすると、検索上位にGitHubのTwitterOAuthが表示されますが、そこから直でファイルをダウンロードするのは止めましょう。
必要なコンポーネントが含まれていませんので実行時にエラーになります。(やらかした)

公式ページ
https://twitteroauth.com/
を見に行くと、「composer経由で落としてね」とあります。composerってなにぞやと思いつつ、公式ページに行って落としに行きます。

composerをインストールする必要あり

yasuda.jpg

https://getcomposer.org/
若いころの安田顕チックな男性がトップページに出てきます。安田顕が所属しているTEAM NACSにcomposerという演目があるんですが偶然でしょうか。肌の色を見るとマッスルボディは傷つかないを思い起こさせるイラストです。

自分の環境はWindows10なので、Windows InstallerのComposer-Setup.exeを落としてきてインストール。
自分はcomposerをc:\xamppにインストールしました。
composer.pharがあるディレクトリに環境変数pathが通っている事を確認してください。

以上でcomposerのインストールは完了です。

composerでTwitterOAuthをダウンロード

続いて、composerを使用してTwitterOAuthをダウンロードします。コマンドプロンプトでカレントディレクトリをダウンロードしたいディレクトリに移動します。

ではダウンロードしましょう。TwitterOAuth公式のトップに書いてあるコマンドをそのままコピペして実行。
composer require abraham/twitteroauth

ほどなくしてダウンロードが始まります。
自分は一度、この時に「composerなんてものは知らん」とエラーが出ましたが、PCを再起動したら無事にコマンドが実行されました。
composer_cmd.jpg
ダウンロードが完了した後、カレントディレクトリ内を確認するとファイルとディレクトリが作成されています。
composer_dir.jpg

vendorディレクトリの下にautoload.phpがあるのでこのファイルをrequireして、自分の好きなような処理を書きましょう。自分の書いたプログラムのファイルの配置箇所によってrequireするパスは変わるので適時変更してください。(この場合は、カレントディレクトリに自作phpファイルを配置しています。)
require "vendor/autoload.php";

最後に、色々と番組に対する思いとか書きたくなりましたが、長くなりそうだしここは技術系のコミュニティなのでそこは我慢します。

では。

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

PHPオブジェクト内配列の取り出し

引用元
https://www.yuulinux.tokyo/14877/

dd($attr)

..array:1 [
0 => {#2063
+"is_kawaii": 1 ←●
}
]

アクセス方法
echo $attr[0]->is_kawaii; // 1

考え方
array直下のものはkey
{key: value}についてはプロパティでアクセスする

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

配列内のオブジェクトの(プロパティの)取り出し

引用元
https://www.yuulinux.tokyo/14877/

dd($attr)

..array:1 [
0 => {#2063
+"is_kawaii": 1 ←●
}
]

アクセス方法
echo $attr[0]->is_kawaii; // 1

考え方
array直下のものはkey
{key: value}についてはプロパティでアクセスする

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

php ログイン機能 セッション

index.php

<?php
//sessionを使う宣言
session_start();

$username = 'abcd';
$password = 'pass';

//$_SESSION = array(
//    'test' => $username,
//    'test2' => $password,
//);
//$_SESSION['test'] = "テストです";
//$_SESSION['test'] = $username;はサーバのファイルに一時保存する 保存したものはどこでもechoで表示できるので
//今回はmypageでパスとユーザーネームを表示させている
$_SESSION['test'] = $username;
$_SESSION['test2'] = $password;

//header('Location: /mypage.php');
//exit;
?>

<?php if (! $_POST): ?>
<!--action 空だと自分に返ってくる-->
<form action="" method="post">
    <p>ユーザーネーム</p><br>
    <input type="text" name="username" value="">
    <br>
    <p>パスワード</p><br>
    <input type="password" name="password" value="">
    <br>
    <input type="submit" value="送信ボタンです">
</form>
<?php endif; ?>



<?php
$input_name = $_POST['username'];
$input_password = $_POST['password'];



if(isset($input_name) && !empty($input_name) && isset($input_password) && !empty($input_password)) {

    if ($username === $input_name && $password === $input_password) {
        //requireはファイルの呼び出し
        //require './mypage.php';
        //header指定はページに遷移する
        header('Location: /mypage.php');
        //headerの後は必ずexit;を書くこと
        exit;

    } else {
        //ファイルの呼び出し
        require './no_user.php';


    }

}
?>

mypage.php

<h1>マイページにようこそ</h1>
<?php
session_start();
//表示方法printでもechoでもどちらでもよいがechoが好ましい
if (isset($_SESSION["test"]) && ! empty($_SESSION['test'])) {
    print "<p>";
    print "ログインID:".$_SESSION["test"];
    print "</p>";
}
if (isset($_SESSION["test2"])) {
    print "<p>";
    print "ログインパスワード:".$_SESSION["test2"];
    print "</p>";
}


?>

no_user.php

<h1>会員ではないので、会員になって下さい。</h1>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AzureBlobストレージのファイルをphpで削除する

本当はディレクトリ指定して下層にあるやつ丸ごと削除したかったが
仮想ディレクトリだから出来ないらしいので
対象のディレクトリ内のファイルリストを取得→1ファイルごとに削除処理するしかないらしい
とてもめんどくさい

public function deleteBlob(){
    $listBlobsOptions = new ListBlobsOptions();
    $listBlobsOptions->setPrefix("対象のディレクトリ名");
    $fileList = $this->blobClient->listBlobs("コンテナ名", $listBlobsOptions);
    foreach ($fileList->getBlobs() as $val){
        $this->blobClient->deleteBlob("コンテナ名",$val->getName());
    }
    return ;
}

解説

ListBlobOptionsでプレフィックスを指定する

コンテナ以下の下層ディレクトリにあたる部分を指定

container/folder1/folder2/test1.txt
container/folder1/folder2/test2.txt
container/folder1/folder2/test3.txt
container/folder1/folder2/test4.txt

↑こういう時、folder2以下にあるテキストファイルを全部削除したい場合は

$listBlobsOptions->setPrefix("folder1/folder2");

と指定しましょう。

指定したPrefix内のファイル一覧を取得

listBlobsで対象のコンテナに対してBlobsOptionを付与
fileListに一覧データが入ります。

1個ずつ取り出して削除処理を実行する

fileList内にBlobsメンバがあるのでfileList->Blobsをforeachでブン回したら
privateだからアクセス出来ないよ!って言われたので、
ソースを読んでみたらgetBlobsってpublicメソッドがあったからそれを使う。
同様にBlob->nameもprivateだったのでgetNameメソッドで取得。

おわり。

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

アクセサが便利なのでまとめてみる件【Laravel 8系】

とにかくforeachとif文でやれたけど…

今回は実務で初めて知ったLaravelの便利な使い方をまとめていきます。

どんなことを実務でやったのかというと例えば

class UserController extends Controller
{
    public function index()
    {
         $users = User::all();
         return view('users.index', compact('users'));
    }
}

というコントローラがありviewでUserを一覧で表示するみたいなことは
割と良くやることかなと思います。

@foreach ($users as $user)
    {{ $user->name }}さん
    {{ $user->email }}
    続く
@endforeach

そんなときに今回のUserのDB情報には権限(role)カラムがあって
管理者(1)と編集者(2)と言った1か2の値を持っていて
それに対して自分は下記の実装を行った

@foreach ($users as $user)
    {{ $user->name }}さん
    {{ $user->email }}
    @if ($user->role === 1)
        管理者
    @else 
        編集者
    @endif
@endforeach

上記の実装は別になにか悪いわけではない
ただ今回の案件では上記のようなコードが他に2〜3箇所書くことになっていて
共通箇所があるならまとめようとレビュワーから言われて
初めてアクセサのことを知ったのが今回の記事の経緯です!

一体何をするのか

実際のコードを書く前に
レビュワーからは

共通箇所があるならまとめよう

の他にも言われてアクセサを使用することに至った。

ifの条件で'1'と書かれてそれが管理者に繋がる意図がviewだけではわからない

最近良いコードについての記事を見ている自分からしたら
まさにわからないですね!と前のめりで共感した。
さっそくコードを書いてみよう

今回はUser絡みの対応なので
UserModelで下記のようなコードを書く

class User extends Model
{
    public const ADMIN = 1;
    public const EDITOR = 2;
    public const ROLENAMES = [
        self::ADMIN => '管理者',
        self::EDITOR => '編集者'
    ];

    public function retriveRoleNameAttribute()
    {
        teturn self::ROLENAMES[$this->role];
    }
}

と言った感じに
対象のmodelにget{Attribute}Attributeメソッドを作成する
{Attribute}はアクセスするカラムのアッパーキャメルケースの名前です。
今回はretrive{RoleName}Attributeとしてます。

これを先程のviewで使用します。

@foreach ($users as $user)
    {{ $user->name }}さん
    {{ $user->email }}
    {{ $user->role_name }} // 管理者or編集者が表示される
@endforeach

ふぁ〜!!!
if文が無くなってめっちゃすっきりしましたね。
また、権限を返していることも明確にわかるのでこれはいい
便利すぎる。
今後必要なときにはしっかりと使っていくようにします。

最後までありがとうございます。LGTM押してくれるとうれしいです!
誤っている点やもっといいやり方などありましたらコメント頂けるとありがたいです!

参考

laravel.jp

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

一つのユーザーテーブルを複数のモデルとガードに分ける

背景

ロールを元にユーザーテーブルのガードを分けたかった。

実践的には、ガードを分けるよりも、一つのガードでログインした後に権限でアクセス制御したほうがいいと思う。あくまでも実験的なものとして覚え書きを残す。

動作環境

  • Mac OS X 10.15.7
  • Laravel 8.33.1
  • Laravel Permission 4.0.0

プロジェクトの作成

Laravel-Permission の導入

composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

モデルの作成

php artisan make:model Student -f
php artisan make:model Teacher -f

app/Models/Student.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Spatie\Permission\Traits\HasRoles;

class Student extends Model
{
    use HasFactory;
    use HasRoles;

    protected $table = 'users';

    protected $fillable = ['name', 'email', 'password'];

    protected $hidden = ['password', 'remember_token'];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    protected static function booted(): void
    {
        static::creating(function ($student) {
            $student->assignRole('student');
        });

        static::addGlobalScope('role', function (Builder $builder) {
            $builder->role('student');
        });
    }
}

app/Models/Teacher.php

namespace App\Models;

use Spatie\Permission\Traits\HasRoles;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Teacher extends Model
{
    use HasFactory;
    use HasRoles;

    protected $table = 'users';

    protected $fillable = ['name', 'email', 'password'];

    protected $hidden = ['password', 'remember_token'];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    protected static function booted(): void
    {
        static::creating(function ($teacher) {
            $teacher->assignRole('teacher');
        });

        static::addGlobalScope('role', function (Builder $builder) {
            $builder->role('teacher');
        });
    }
}

User に書いてそれぞれ継承でもいいと思う。

ガードの設定

プロパイダーの設定を auth.providers に追記する。

'students' => [
    'driver' => 'eloquent',
    'model' => App\Models\Student::class,
],
'teachers' => [
    'driver' => 'eloquent',
    'model' => App\Models\Teacher::class,
],

ガードの設定を auth.guards に追記する。

'student' => [
    'driver' => 'session',
    'provider' => 'students',
],
'teacher' => [
    'driver' => 'session',
    'provider' => 'teachers',
],

ファクトリの作成

UserFactorydefinition() をコピーして、StudentFactoryTeacherFactory を用意する。

ロールの投入

マイグレーションして、Role を用意する。

php artisan migrate
php artisan permission:create-role student student
php artisan permission:create-role teacher teacher

動作確認

Tinker で動作確認する。

php artisan tinker

Student, Teacher を作成して、それぞれ別々に取得できるのを確かめる。

Student::factory(3)->create();
Teacher::factory(3)->create();
Student::all();
Teacher::all();

補足:ガードを追加したくない場合

Laravel-Permission はガードに紐づいているプロバイダーの model を元にロールを絞り込む。
ガードを新たに追加しなくても、モデルに public function guardName()protected $guard_name を明示すると特定のガードを流用できる。

この方法を選んだ場合、「ロールの投入」でも第二引数の guard_name を合わせること。

参考資料

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

Laravel と MessagePack で遊ぶ

動作環境

  • Mac OS X 10.15.7
  • Laravel 7.x|8.x
  • rybakit/msgpack 0.8
  • msgpack-tools 0.6

プロジェクトの作成

composer require rybakit/msgpack

レスポンスの作成

routes/web.php

use MessagePack\Packer;

Route::get('/example.mp', function () {
    $value = (new Packer())->pack([
        'id' => 1,
        'name' => 'John Doe',
        'age' => 18,
    ]);
    return response($value)->header('Content-Type', 'application/x-msgpack');
});

ターミナルで確認

brew install msgpack-tools
php artisan serve
curl http://localhost:8000/example.mp | msgpack2json -d

レスポンスマクロの作成

php artisan make:provider MessagePackResponseServiceProvider

app/Providers/MessagePackResponseServiceProvider.php

namespace App\Providers;

use MessagePack\Packer;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Response;

class MessagePackResponseServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Response::macro('mpac', function ($value) {
            return response((new Packer())->pack($value))->header(
                'Content-Type',
                'application/x-msgpack'
            );
        });
    }
}

config/auth.php

return [
    'providers' => [
        # ...
        App\Providers\MessagePackResponseServiceProvider::class,
    ],
];

routes/web.php

Route::get('/example.mp', function () {
    return response()->mpac([
        'id' => 1,
        'name' => 'John Doe',
        'age' => 20,
    ]);
});

ターミナルで確認

curl http://localhost:8000/example.mp | msgpack2json -d

Model を渡せるようにする

toArray() があれば変換しておくと Eloquent Model もそのまま渡せる。

Response::macro('mpac', function ($value) {
    if (method_exists($value, 'toArray')) {
        $value = $value->toArray();
    }
    return response((new Packer())->pack($value))->header(
        'Content-Type',
        'application/x-msgpack'
    );
});

資料

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

PHPアウトプット

エンジニアの卵です

昨年新卒で入社した会社を辞めてテックキャンプを卒業。(84期生)
今年からIT会社でエンジニアとして働きはじめましたが、まだまだわからないことだらけなので、
Qiitaを通して自分の理解度の定着と他の方の記事をみて学んで行きたいなと思います。

今はプロゲートレベルですが、これから胸を貼ってエンジニアですと言えるようにがんばっていきます。

オブジェクト思考

オブジェクト思考とはオブジェクト(もの)を中心とした考え方で
ECサイトでいうところの商品やユーザー、商品を入れるカートなどの1つ1つを指します。

コードを書く際に商品についてのコード、ユーザーについてのコード、カートについてのコードなど分けて記し、またそれらを組み立てながら作成していくため、この考え方が必要になります。

クラスとインスタンス

クラスとはオブジェクトを生成するための設計図のようなものです。
クラスの中に商品についてやメニューについてなど書いていきます。

インスタンスとはクラスを元に生成された実体を言います。
インスタンスとオブジェクトは同じような意味です。

分かりやすくたこ焼きで例えると、
クラス たこ焼き機
インスタンス たこ焼きの生成
インスタンスに情報を追加する場合 ソースなど

を指します。
当時全く理解出来ませんでしたがこの例えを聞いて少し理解出来るようになりました。
分かりやすいものもあったので自分用に貼ります。

image.png

PHPのクラスとインスタンスの書き方は以下のようになります。

・クラス

<?php
class Menu{
}

「class クラス名」と定義して{}の中にクラスの内容を書いていきます。
クラス名の最初は大文字になります。

・インスタンス
$menu1 = new Menu():
$menu2 = new Menu();

インスタンスはnewを使って生成します。
$menuという変数に生成したインスタンスMenuを代入しています。
インスタンスはクラスの中では生成出来ません。

プロパティ

プロパティとメソッドはクラスの中に記述するものです。
プロパティはインスタンスが持つデータを言います。

例えばプロパティはインスタンスが持つデータなので名前や値段などです。

PHPのプロパティの書き方は以下のようになります。

<?php
class Menu{
public $name;

}

Munuクラス(設計図)の中に「public $プロパティ名」でプロパティを定義します。
今回は$nameプロパティを定義しました。
また「インスタンス->プロパティ名」斗することでインスタンスのプロパティにアクセスすることが出来ます。

class Menu{
public $name
}

$sushi =new Menu();
$sushi ->name ='SUSHI';
echo $sushi->name;

上記で起こっていることは以下になります。

Menuクラスを生成。
$name プロパティを定義。

Menuインスタンスを生成して$sushiに代入。
$sushiの値をSUSHIに設定。
(echo は出力する時に使うもの)
echo $sushi->name;の結果はSUSHIになる。

メソッド

メソッドもクラスの中に記述するもので、
それぞれのインスタンが持つ関数(処理するもの)です。

例えば、インスタンスの名前を取得したり注文数を取得したりします。
インスタンス固有の関数をメソッドと言います。

PHPのメソッドの書き方は以下のようになります。

class Menu{
public function hello(){
 echo '私はMenuインスタンスです';
}
}

$sushi =new Menu();
$sushi -> hello();

上記で起こっていることは以下になります。

Menuクラスに「public function メソッド名()」でメソッドを定義。

Munuインスタンスを生成して$sushiに代入。
「インスタンス->メソッド名()」でメソッドにアクセスするため(呼び出している)
結果は私はMunuインスタンスですと表示される

$sushi -> hello();が実行されたら
public function hello(){
 echo '私はMenuインスタンスです';を呼び出しているというイメージ。

自分のまとめとして投稿していますが、今後文章も上手くなれるように頑張ります。
また次回PHPの続きを投稿します。

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

Laravelで検索機能実装時に「リダイレクトが繰り返し行われました。」と表示された件

検索機能作成時に「リダイレクトが繰り返し行われました。」とエラーが発生!

自分なりに調べてみましたが、核心を得た答えに辿り着くことができず、なかなかに苦労したので備忘録として残します。

まずは検索フォームのコードです。

Formファサードを利用しているので以下のコードになります。

index.blade.php
{!! Form::open(['route' => 'report.index', 'method' => 'GET']) !!}
  {!! Form::text('search', '', ['class' => 'form-control', 'type' => 'search', 'placeholder' => '----年--月']) !!}
  {!! Form::button('<i class="fa fa-search"></i>', ['class' => 'btn btn-icon', 'type' => 'submit']) !!}
{!! Form::close() !!}

検索フォームに入力された値をindexアクションへGET送信します。
送信された値はRequest $requestで受け取ります。

続いてはコントローラー内のindexアクションの記述になります。

DailyReportController.php
public function index(DailyReportRequest $request)
{
    $search = $request->input('search');
    dd($search)
    return view('user.daily_report.index', compact('dailyReports'));
}

formから送信された値を$requestで受け取ります。

ここではLaravelに既存で搭載されているサービスプロバイダという機能を使って、自動でRequestクラスから$requestインスタンスを作成してくれています。

詳しくは以下の記事を参考にしてください。
hoge(Request $request)ってなんだ???

$requestに送信された値をinputメソッドで受け取り、変数$searchへ代入。
dd($search)で送信された値を確認しようとしました。

しかし...ここでエラーが発生!!

スクリーンショット 2021-03-21 23.17.08.png

リダイレクトが繰り返し行われているとな、、、?

念のためデベロッパーツールのNetworkでも確認してみます。
スクリーンショット 2021-03-21 23.18.26.png

やはりリダイレクトが起きている様子...!

ちなみに「status」の「302」はHTTPステータスコード「302リダイレクト」というものでその名の通りリダイレクト発生を示しています。

今回のケースではコントローラー内のindexアクションのdd($search)$searchの中身が表示され、処理が止まるはずなのですが、先ほどのエラー画面が表示されています。
このことからdd($search)まで処理が来ておらず、DailyReportRequest$requestの箇所に原因があることがわかります。

ここまでは特定できた、、、、、
だが、今回のエラーを自分なりに調べてみたが、解決策を見つけることができず、、、、

自分の調べ方が悪いのかと、色々考えを巡らせましたが、どうにもこうにもいかない、、、
最終的に先輩社員に質問し、原因が特定でき、解決することができました。

結論としては、Validationが掛かっていたためのリダイレクトでした!

どういうことかというと、、、

コントローラーのstoreupdateアクションで設定していたDailyReportRequestindexアクションの引数に渡していたことで、storeupdateアクションのvalidationが掛かってしまっていました。

DailyReportRequest.php
~省略~
    public function rules()
    {
        return [
            'reporting_time' => 'required|before_or_equal:today',
            'title' => 'required|max:255',
            'contents' => 'required|max:1000',
        ];
    }

    public function messages()
    {
        return [
            'required' => '入力必須の項目です。',
            'reporting_time.before_or_equal' => '今日以前の日付を入力してください。',
            'max' => ':max 文字以内で入力してください。',
        ];
    }

このvalidationが掛かっていると、検索フォームから送信した値にもvalidationチェックを行ってしまいます。
当然、送信された値には上記のアクションでvalidationが失敗する値が送信されてきます。

validationは入力値が設定した条件を満たしていない場合、入力画面にリダイレクトします。

そのため、入力フォームから値を送信した際に、無限リダイレクトが発生し、「リダイレクトが繰り返し行われました。」のようなエラーが発生していました。

というわけでindexアクションを修正します。

DailyReportController.php
public function index(Request $request)
{
    $search = $request->input('search');
    dd($search)
    return view('user.daily_report.index', compact('dailyReports'));
}

Request, $requestでよかったんだ、、、、

しっかりuse宣言で利用するRequestクラスの記述を忘れずに、、、

use Illuminate\Http\Request;

こちらで無事に$searchで送信された値の受け取りに成功!

こうしたエラーは調べてもなかなかお目当てのものに辿り着かないため、しっかりとロジックを理解して解決を目指すことが重要だそうです。

いい勉強になりました、、、、

また、エラーを調べる時も、予め自己解決を目指す時間を設定し、それを超えてしまったら直ぐに質問するのが良いですね。
素直に現在の自分の実力と向き合うのが大切だと学びました。

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