20201126のPHPに関する記事は20件です。

【ajax】本番環境で謎の403エラー

環境

  • Laravel 8系
  • PHP 7.4
  • appache

実装したいこと

ajaxを使用して非同期でPATCH処理を行いたい。

本題

$.ajax({
    type: 'PATCH',
    url: '/hoge_update',
    headers: {
      'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
     },
     data: {
       id: id,
       item: item,
      },
      dataType: 'json',
 }).done(function (data) {

こんな感じで非同期でPATCH処理を実装しようと思ったら、本番環境で 403 Forbiddenエラーが。

調べてみるとどうやらapache側で許可されているのが GET POSTのみみたい(詳細はこちら

ただssh接続の許可がなかったり本番環境に入れない場合、confファイルを直接編集できないので、

$.ajax({
    type: 'POST', //POSTに変更
    url: '/hoge_update',
    headers: {
      'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
     },
     data: {
       _method: 'PATCH', //PATCHを追加
       id: id,
       item: item,
      },
      dataType: 'json',
 }).done(function (data) {

だいぶ無理やりですがこれでしっかり200ステータスを返してくれました。

参考

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

WordPressでサイドバーを自作する

ワードプレスのテーマtwentytwentyを基盤に、ページに表示するサイドバーを自作する実装を行ったので、備忘録的にこの記事を残す。

環境情報

PHP:version 7.3.12
WordPress:version 5.5.3
WPテーマ:twentytwenty

作業

ワードプレスのいろいろな機能は、全て「テーマのファイル」の中でプログラム的に記述されていて(定義されていて)、これはサイドバーも同じです。

ということから、サイドバーを増やす、ホームならホーム専用のものを別に作る、といった場合には、以下3つの事を行えば良さそうです。

1)サイドバーの定義(register_sidebar)を探す
 テーマのファイルの中で、サイドバーが定義されているところ(記述されているところ)を探す
2)サイドバーの定義を複製する
 その「定義されているサイドバーの記述」をコピペして、もう1つサイドバーを定義する
3)複製したサイドバーを場合分けで呼び出す
 サイドバーを表示するファイルの中で、ホームならホームの場合はこれ、と場合分けして、サイドバーを表示する

サイドバーの定義を探す

テーマファイルの中で、サイドバーは、register_sidebar (または register_sidebars )で定義されてるので、その箇所を探します。

function.php
function twentyseventeen_widgets_init() {
    register_sidebar(
        array(
            'name'          => __( 'Blog Sidebar', 'twentyseventeen' ),
            'id'            => 'sidebar-1',
            'description'   => __( 'Add widgets here to appear in your sidebar on blog posts and archive pages.', 'twentyseventeen' ),
            'before_widget' => '<section id="%1$s" class="widget %2$s">',
            'after_widget'  => '</section>',
            'before_title'  => '<h2 class="widget-title">',
            'after_title'   => '</h2>',
        )
    );

    register_sidebar(
        array(
            'name'          => __( 'Footer 1', 'twentyseventeen' ),
            'id'            => 'sidebar-2',
            'description'   => __( 'Add widgets here to appear in your footer.', 'twentyseventeen' ),
            'before_widget' => '<section id="%1$s" class="widget %2$s">',
            'after_widget'  => '</section>',
            'before_title'  => '<h2 class="widget-title">',
            'after_title'   => '</h2>',
        )
    );

    register_sidebar(
        array(
            'name'          => __( 'Footer 2', 'twentyseventeen' ),
            'id'            => 'sidebar-3',
            'description'   => __( 'Add widgets here to appear in your footer.', 'twentyseventeen' ),
            'before_widget' => '<section id="%1$s" class="widget %2$s">',
            'after_widget'  => '</section>',
            'before_title'  => '<h2 class="widget-title">',
            'after_title'   => '</h2>',
        )
    );
}
add_action( 'widgets_init', 'twentyseventeen_widgets_init' );

twentyseventeenではfunction.phpの中で定義されています。

サイドバーの定義を複製する

では、サイドバーの定義が分かったので、それを使って新たにサイドバーを複製(追加)してみます。

複製する場合、サイドバーの定義に対して3点の変更が必要ですが、今回はまず、ホーム(トップページ)専用のサイドバーを作ってみます。

1)3つの変更点

専用のサイドバーをつくるにあたり、先ほどコピーして抜き出したサイドバーの定義の中で、name, id, description の3点を変更しておきます。

ただ description は、サイドバーの定義の中にある場合だけ変更する、でOK。

name について

「ホーム専用サイドバー」とかにすると分かりやすいですね。以下のような感じです。

'name'          => __( 'ホーム専用サイドバー' ),

id について

今回はまずホーム専用サイドバー、ということで、たとえば、my-top-sidebar としていきます。
※元々あるサイドバーと同じ名前にしない!

'id'            => 'my-top-sidebar',

description について

テーマによって、サイドバーの定義に含まれないものもありますが、含まれている場合には、以下の様にしておけば分かりやすくて良さそうです。

'description' => 'ホーム専用サイドバーに表示されるコンテンツ',

作ったサイドバーを貼り付け

つづいて、上で変更したサイドバーの定義を子テーマのfunction.phpファイルの中に貼り付けます。
この時親テーマの関数とコンフリクトする場合は、別途関数定義を外す処理が必要。

複製したサイドバーを呼び出す

ここまででサイドバーを新たに追加した、ということになりますが、最後は、その追加したサイドバーを場合分けをして呼び出して表示するだけ。

サイドバーを呼び出している箇所を探す

まずサイドバーを呼び出して表示する箇所(dynamic_sidebar)を探します。

基本は「サイドバー」(sidebar.php)の中で、以下の様にな感じになっていると思います。

<?php dynamic_sidebar( 'sidebar-1' ); ?>

※丸カッコ内の sidebar-1 がテーマによって異なる(数字だったり違う名称となっている)

場合分けをして呼び出し表示

サイドバーを呼び出して表示する箇所(dynamic_sidebar)が分かったら、以下のような場合分けをしてサイドバーを呼び出すように変更します。

1)ホームでは
 今回追加したサイドバーを呼び出し表示する

2)その他では
 今まで使用していたサイドバーを表示する

今回追加したサイドバーは、上の方で id に「my-top-sidebar」を設定したので、呼び出すには以下のような記述になります。

dynamic_sidebar( 'my-top-sidebar' );

ということで、場合分け含めた具体的なサイドバーの呼び出しは、以下の様な感じになりますね。

<?php
if( is_front_page() ){ // ホームの場合
    dynamic_sidebar( 'my-top-sidebar' ); // 新たに追加したサイドバー
} else { // その他の場合
    dynamic_sidebar( 'sidebar' ); // 今まであるサイドバー
}
?>

追加したサイドバーを呼び出して表示

では実際に、新たに追加したサイドバーを呼び出せるように「サイドバー」(sidebar.php)を編集してみます。

sidebar.php
if( is_front_page() ){ // ホームの場合
    dynamic_sidebar( 'my-top-sidebar' ); // 新たに追加したサイドバー
} else { // その他の場合
    dynamic_sidebar( 'sidebar-1' ); // 今まであるサイドバー
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Requestは、ただのバリデーションファイルじゃない【2020/11/26】

今まで僕は、LaravelのRequestファイル(ex userRequest.phpとか)は、バリデーションをするためにあるファイルだと思っていました。しかし、バリデーション機能はどちらかとういうとおまけ。

  • △ バリデーション機能
  • ◯ データを保持するオブジェクト + おまけでバリデーション

だった。

strutsでいう、ActionFormに一番近い(懐かしいな)。
ここからフォームやクエリパラメータに突っ込んだデータを取り出してくる。

だから、Controllerファイルの中で、

xxxController.php
$this->all() 

でデータとれるのはもちろん、

xxxRequest.php
$this->all() 

と、Requestファイルの中でも普通に$thisでデータが取れる。

バリデーションしているだけなのになんでRequestファイルなのか疑問だったけど、get/postのリクエストを受け取っている紛れもないファイルだった。

フレームワーク作る人って、すごいな。

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

PHP: Google Cloud Translation API の使い方 (Basic)

basic01.php
#! /usr/bin/php
<?php
// ------------------------------------------------------------------
//
//  basic01.php
//
//                      Nov/26/2020
//
// ------------------------------------------------------------------
require 'vendor/autoload.php';
use Google\Cloud\Translate\TranslateClient;

$text = 'Es war einmal ein kleines Mädchen.';
$targetLanguage = 'ja';

$translate = new TranslateClient();
$result = $translate->translate($text, [
    'target' => $targetLanguage,
]);
print("Source language: $result[source]\n");
print("Translation: $result[text]\n");
// ------------------------------------------------------------------
?>

実行方法

export GOOGLE_APPLICATION_CREDENTIALS=./***.json
#
./basic01.php

実行結果

Source language: de
Translation: 昔々、小さな女の子がいました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PHPUNIT]setupメソッド使用時のよくあるエラー

laravelのphpunitでsetUp()メソッドを定義したところ、エラーが出て詰まったので共有します。

現状

.php
class ...Test extends \TestCase
{
    public function setUp()
    {
       ...
    }
}

エラー文

PHP Fatal error:  Declaration of W3\Controllers\...Test::setUp() must be compatible with Illuminate\Foundation\Testing\TestCase::setUp(): void in

作成したテストのsetUp()の返り値と自動で作成されるIlluminate\Foundation\Testing\TestCase
のsetUp()の返り値が違うことでした。

解決策

.php
class ...Test extends \TestCase
{
    public function setUp():void
    {
       ...
    }
}

上記のようにsetup()に:voidを追記することで、実行できるようになります。

会社の紹介

私は現在、株式会社ダイアログという物流×ITの会社に勤務しております。
2020年11月現在、エンジニアの募集はしていませんが、他にも様々な職種を募集しているので、Wantedlyのページをご覧ください。

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

LaravelのKernel.phpのscheduleメソッド内でcommandを使ってハマった件

PHPとLaravelのバージョン

  • Laravel Framework 5.7.28
  • PHP 7.3.11

出ていたエラー

ERROR: Aborted. {"exception":"[object] (Symfony\\Component\\Console\\Exception\\RuntimeException(code: 0): Aborted. at /home/hoge/hoge/hoge_api/vendor/symfony/console/Helper/QuestionHelper.php:135)
 {"exception":"[object] (Symfony\\Component\\Console\\Exception\\CommandNotFoundException(code: 0): Command \"hoge\" is not defined.

書いていたコード

app/Console/Kernal.php

    protected function schedule(Schedule $schedule)
    {
        $schedule->command('hoge')->cron('* * * * *');
    }

app/Console/Commands/Hoge.php

   /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:hoge';

解決策

下記が叩かれていてそんなものないよ、と怒られていたので

'/usr/bin/php' 'artisan' hoge 

command:を付ける

    protected function schedule(Schedule $schedule)
    {
        $schedule->command('command:hoge')->cron('* * * * *');
    }

そうすると下記が叩かれるようになります。

'/usr/bin/php' 'artisan' command:hoge
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPを深くまでしろう part1~言語構造編~

はじめに

最近、同年代の最強エンジニア方からいろいろなアドバイスを頂いているのですがその中で言語の言語仕様そのものについて深く知ることが重要と言われてから自分がよくPHPを使用しているので今一度PHPについて深く勉強しようとした記録です。

言語構造

PHPでよく使うechoやprintなどは関数ではなく言語構造と言われているのですが、そのことについて全然違いを理解していなかったので調べてみました。

if () {
}

上記のif文は()の中に式をいれることにより条件に一致する場合は{}の中が実行されます。

if (1 === 1) {
    echo 'ok';
}
// とか
if ($result = 'hello') {
    echo 'ok';
}

など、他にも沢山の方法があります。
()の中には式を入れるのですが、式とはどういったことを指すの言葉なのでしょうか?
簡潔にいうと、

PHPにおける式とは、値を持つものすべてのこと

このことを踏まえて、最初の話に戻るのですが、echoは言語構造なので()の中に使うとエラーが起きてしまいます。
なら、printはとなるのですがprintは関数の振る舞いをする言語構造になるのでエラーが起きません。

if (print('hello')) {
    echo 'world';
}
// hellowolrdと出力される

print()を実行した後、返り値で1が返ってくるのでhellowolrdが出力されます。
このように他にも言語構造では返り値が返ってくるものがあります。
また、言語構造はif文やwhile文など制御構造に近しい部分があるみたいです。

関数のように決まった形である

hello();

とかではなく

echo 'hello';

echo('hello');

のように両方の方法で呼び出せる

さいごに

自分が普段何気なく使用しているものを深く知ることが、一つ抜き出たエンジニアになれるのではないかと思っているので今後もこのような記事をアップしていきます。
まだまだ、全然理解できていな部分もありますが言語仕様を知ると今までとは違う視点で見ることが出来るので、とにかく楽しいです(笑)

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

PHPのセッション時間を伸ばす

目的

レンタルサーバーmixhost上でのセッション時間を伸ばす
php.iniのsession.gc_maxlifetimeを伸ばしたが変化なし

参考

セッションの有効期間とか設定とか挙動とかを調べました

仮説

セッションファイルはレンタルサーバーの共有フォルダにありそうなので他のユーザのアクセスで消される可能性がある

やること

セッション時間設定の延長(php.iniのsession.gc_maxlifetime)とともに、セッションファイルの保存場所を変更しためしてみる

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

レンタルサーバーのPHPのセッション時間を伸ばす

目的

レンタルサーバーmixhost上でのセッション時間を伸ばす
php.iniのsession.gc_maxlifetimeを伸ばしたが変化なし

参考

セッションの有効期間とか設定とか挙動とかを調べました

仮説

セッションファイルはレンタルサーバーの共有フォルダにありそうなので他のユーザのアクセスで消される可能性がある
※下記の記事より仮説は正しそう
https://webmaster.chielog.com/php/161.html

やったこと

セッション時間設定の延長(php.iniのsession.gc_maxlifetime)とともに、セッションファイルの保存場所を変更した
MultiPHP INI Editorに設定(session.save_path)があるので変更

結果

セッション時間が延長された

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

レンタルサーバーのPHPのセッション時間を伸ばす【結果確認中】

目的

レンタルサーバーmixhost上でのセッション時間を伸ばす
php.iniのsession.gc_maxlifetimeを伸ばしたが変化なし

参考

セッションの有効期間とか設定とか挙動とかを調べました

仮説

セッションファイルはレンタルサーバーの共有フォルダにありそうなので他のユーザのアクセスで消される可能性がある
※下記の記事より仮説は正しそう
https://webmaster.chielog.com/php/161.html

やったこと

セッション時間設定の延長(php.iniのsession.gc_maxlifetime)とともに、セッションファイルの保存場所を変更した
MultiPHP INI Editorに設定(session.save_path)があるので変更

結果

セッション時間が延長された

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

PHPで作る二段階認証

はじめに

 PHPに限らず、Webアプリケーションを作る際に、何らかの”本人確認”的なことをしたい場面があると思います。事前に登録されたユーザ情報と照らし合わせるとかでいいと思うんですが、もうちょい手軽にできる一例を紹介したいと思います。それが、メールと組み合わせた2段階認証です。
 この手法は、PHPで敷居の高い掲示板を作ってみたに使ったもので、Tokenというランダムな文字列を合言葉にして、メール宛てにその合言葉を送ることで、2段階認証をするものです。つまり、「メールアドレス=身分証明」的な扱いをしているという事です。

※この手法の欠点としては、そもそも”メール=本人”ではないこと。メールで認証したから必ず本人とは限らない。あと、GETはURLの一部として扱われるので、気を付けてください。

概要

 この仕組みの概要としては、

  1. サーバーでtokenを作り、token.txtに保存する
  2. メールにurl(token付き)を送信
  3. 確認画面でtoken.txtの内容と、urlについたtokenを確認する

7.PNG

って感じで、3のステップにて、urlについたtokenが正しければ、認証成功ってことです。
 ちなみに、2のurl(token付き)とは、確認用ページへのURLに、GETでtokenをくっつけるって事です。例えば、確認用ページが「http://hogehoge/confirm.php」だったとして、tokenが「abcdefg123456」だったとすると、

http://hogehoge/confirm.php?token=abcdefg123456

がtoken付きURLという事になります。
(PHPでの例ですが、GETで指定できれば何の言語でも構いません。)

ソースコード(PHPでの実装)

まず、ステップ1と2を実装したPHPファイルです。

index.php
<?php

function generate_token(){
    $length = 10;
    return substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstuvwxyz', $length)), 0, $length);
}

function send_mail($message, $to){
    $subject = "Tokenを送ります";
    $headers = "From: hogehoge@gmail.com" . "\r\n";
    $headers .= 'Content-type: text/html; charset=UTF-8';
    mail($to, $subject, $message, $headers);
}

function inputForm(){
    echo '
    <form action="" method="post">
        メールアドレス:
        <input type="email" name="mail">
        <input type="submit">
    </form>
    ';
}

function main(){

    inputForm();

    if(isset($_POST["mail"])){
        $token = generate_token();
        file_put_contents("token.txt", $token);
        echo $token.'<br><br>というTokenを発行しました。<br>';
        $url = '<a href="http://localhost/confirm.php?token='.$token.'">token付きのURL</a>';
        send_mail($url, $_POST["mail"]);
        echo $_POST["mail"].'へToken付きURLを送信しました。<br>';
    }else{
        echo 'メールアドレスを入力してください。';
    }
}

main();

?>

確認用ページのPHPファイルです。

confirm.php
<?php

$token_form_mail = "";
if(isset($_GET["token"])){
    $token_form_mail = $_GET["token"];
    echo $token_form_mail.'というTokenを受け取りました。<br>';
}

$token_from_file = file_get_contents("token.txt");
echo $token_from_file.'というTokenが保存されていました。<br>';

if($token_form_mail === $token_from_file){
    echo "一致しました!";
}else{
    echo "不一致です、有効でないTokenを受け取りました。";
}

?>

動かしてみる

 そんじゃ動かしてみます。index.phpに行くと、入力フォームがあるので、メールアドレスを入力してあげます。
1.PNG
すると、こんな感じでメッセージが出ます。token.txtにtokenを保存して、メールへtoken付きURLを送信しました。
2.PNG
サーバーのディレクトリのtoken.txtを確認すると、ちゃんと保存されてますね。
3.PNG
ちなみに、あて先のメールを確認してみると、ちゃんとURLが届いています。
4.PNG
メールのURLをクリックすると、承認成功!って感じですね。
5.PNG
ちなみに、tokenを適当に変更してみる(getで指定しているので、urlの最後に変な文字列を入れればいい)と、こんな感じで、認証に失敗します。
6.PNG

さいごに

 これが、メールとtokenの組み合わせによる認証の仕組みです。ほかにもいろいろあるかと思いますが、簡単にまとめてみました。
それでは!

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

Token+メールによる認証

はじめに

 PHPに限らず、Webアプリケーションを作る際に、何らかの”本人確認”的なことをしたい場面があると思います。事前に登録されたユーザ情報と照らし合わせるとかでいいと思うんですが、もうちょい手軽にできる一例を紹介したいと思います。
 この手法は、PHPで敷居の高い掲示板を作ってみたに使ったもので、Tokenというランダムな文字列を合言葉にして、メール宛てにその合言葉を送ることで、本人確認的なことをするものです。つまり、「メールアドレス=身分証明」的な扱いをしているという事です。

概要

 この仕組みの概要としては、

  1. サーバーでtokenを作り、token.txtに保存する
  2. メールにurl(token付き)を送信
  3. 確認画面でtoken.txtの内容と、urlについたtokenを確認する

7.PNG

って感じで、3のステップにて、urlについたtokenが正しければ、認証成功ってことです。
 ちなみに、2のurl(token付き)とは、確認用ページへのURLに、GETでtokenをくっつけるって事です。例えば、確認用ページが「http://hogehoge/confirm.php」だったとして、tokenが「abcdefg123456」だったとすると、

http://hogehoge/confirm.php?token=abcdefg123456

がtoken付きURLという事になります。
(PHPでの例ですが、GETで指定できれば何の言語でも構いません。)

ソースコード(PHPでの実装)

まず、ステップ1と2を実装したPHPファイルです。

index.php
<?php

function generate_token(){
    $length = 10;
    return substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstuvwxyz', $length)), 0, $length);
}

function send_mail($message, $to){
    $subject = "Tokenを送ります";
    $headers = "From: mimurosyunya@gmail.com" . "\r\n";
    $headers .= 'Content-type: text/html; charset=UTF-8';
    mail($to, $subject, $message, $headers);
}

function inputForm(){
    echo '
    <form action="" method="post">
        メールアドレス:
        <input type="email" name="mail">
        <input type="submit">
    </form>
    ';
}

function main(){

    inputForm();

    if(isset($_POST["mail"])){
        $token = generate_token();
        file_put_contents("token.txt", $token);
        echo $token.'<br><br>というTokenを発行しました。<br>';
        $url = '<a href="http://localhost/confirm.php?token='.$token.'">token付きのURL</a>';
        send_mail($url, $_POST["mail"]);
        echo $_POST["mail"].'へToken付きURLを送信しました。<br>';
    }else{
        echo 'メールアドレスを入力してください。';
    }
}

main();

?>

確認用ページのPHPファイルです。

confirm.php
<?php

$token_form_mail = "";
if(isset($_GET["token"])){
    $token_form_mail = $_GET["token"];
    echo $token_form_mail.'というTokenを受け取りました。<br>';
}

$token_from_file = file_get_contents("token.txt");
echo $token_from_file.'というTokenが保存されていました。<br>';

if($token_form_mail === $token_from_file){
    echo "一致しました!";
}else{
    echo "不一致です、有効でないTokenを受け取りました。";
}

?>

動かしてみる

 そんじゃ動かしてみます。index.phpに行くと、入力フォームがあるので、メールアドレスを入力してあげます。
1.PNG
すると、こんな感じでメッセージが出ます。token.txtにtokenを保存して、メールへtoken付きURLを送信しました。
2.PNG
サーバーのディレクトリのtoken.txtを確認すると、ちゃんと保存されてますね。
3.PNG
ちなみに、あて先のメールを確認してみると、ちゃんとURLが届いています。
4.PNG
メールのURLをクリックすると、承認成功!って感じですね。
5.PNG
ちなみに、tokenを適当に変更してみる(getで指定しているので、urlの最後に変な文字列を入れればいい)と、こんな感じで、認証に失敗します。
6.PNG

さいごに

 これが、メールとtokenの組み合わせによる認証の仕組みです。ほかにもいろいろあるかと思いますが、簡単にまとめてみました。
それでは!

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

【CakePHP】フォーム内のhiddenに配列を設定し、コントローラーで受け取る方法

実現したかったこと

hiddenに配列をもたせて、コントローラー側で取得する。

改善前

cakeのビュー側
<?php echo $this->Form->hidden('hoge',array('value' => 1)); ?>
<?php echo $this->Form->hidden('hoge',array('value' => 2)); ?>
<?php echo $this->Form->hidden('hoge',array('value' => 3)); ?>

クロムの検証ツールで覗いてみる

<input type="hidden" name="hoge" value="1">
<input type="hidden" name="hoge" value="2">
<input type="hidden" name="hoge" value="3">

よし、いい感じ。

コントローラー側で受け取れているか見てみる。

コントローラー側のアクションの中で実行
print_r($this->request->getData("hoge"));
exit;
画面出力結果
3

なんでやねん(笑)

自分が期待してた(欲しかった)結果
Array ( [0] => 1 [1] => 2 [2] => 3 )

修正した箇所

cakeのビュー側
<?php echo $this->Form->hidden('hoge[]',array('value' => 1)); ?>
<?php echo $this->Form->hidden('hoge[]',array('value' => 2)); ?>
<?php echo $this->Form->hidden('hoge[]',array('value' => 3)); ?>

のようにhogeの後ろに[]をつけてあげれば期待通りの配列が取得できました。

終わりに

解決出来てよかったですが、なんで改善前の記述だと「3」が返ってくるのか、いまいちわからないのが現状です。。。

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

Laravelでシングルアクションコントローラーを採用した際にInvalid route actionが出た件

はじめに

最近、アプリケーションアーキテクチャに興味を持ち、実際にLaravelでADR(Action Domain Responder)を試しています。Laravelは自由度が高いのでアーキテクチャ次第で全く違ったものになるという印象を受けています。

ADRには、1つのアクションとルートを対応させる事で複雑化を防ぐという思想があり、シングルアクションコントローラー(実際は、Action)を採用しました。(他にもメリットがあるかは勉強中です...)

ルートが認識されない

公式を見つつシングルアクションクラスとルートを作成しました。

BookRegisterAction.php
<?php

namespace App\Http\Actions\Book;

use App\Http\Controllers\Controller;

final class BookRegisterAction extends Controller
{
    public function __invoke()
    {
        // アクションの処理
    }
}

以下のようにルーティングを設定。

web.php
Route::prefix('book')->group(function () {
    Route::post('/register', App\Http\Actions\Book\BookRegisterAction::class)->name('book.register');
});

すると下図のようなエラーが発生しました。
スクリーンショット 2020-11-23 9.56.05.png
どうやらルーティングがだめとのこと。。。

原因と解決策

Laravelはデフォルトで、コントローラーがApp\Http\Controllers\にあることを前提としています。
実際に発生したエラーには以下のような記載がありました。

Illuminate\Routing\RouteAction::makeInvokable("App\Http\Controllers\App\Http\Actions\Book\BookRegisterAction")

完全にデフォルト+追記したアクションになってます...
こちらはapp/Providers/RouteServiceProvider.phpに定義されているので修正を加えます。

RouteServiceProvider.php
<?php

namespace App\Providers;

class RouteServiceProvider extends ServiceProvider
{
    protected $namespace = 'App\Http\Controllers';  ココがデフォルトのネームスペース



    /**
     * Define the "api" routes for the application.
     *
     * These routes are typically stateless.
     *
     * @return void
     */
    protected function mapApiRoutes()
    {
        Route::prefix('api')
             ->middleware('api')
             ->namespace($this->namespace) ← ここを削除
             ->group(base_path('routes/api.php'));
    }

上記のように変更を加えるとルートを認識させることができました。(デフォルトのnamespaceを変更でも可能です)
よりよい解決策などあれば、ご教授お願いいたします。

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

シングルアクションコントローラーをLaravelで採用した際にInvalid route actionが出た件

はじめに

最近、アプリケーションアーキテクチャに興味を持ち、実際にLaravelでADR(Action Domain Responder)を試しています。Laravelは自由度が高いのでアーキテクチャ次第で全く違ったものになるという印象を受けています。

ADRには、1つのアクションとルートを対応させる事で複雑化を防ぐという思想があり、シングルアクションコントローラー(実際は、Action)を採用しました。(他にもメリットがあるかは勉強中です...)

ルートが認識されない

公式を見つつシングルアクションクラスとルートを作成しました。

BookRegisterAction.php
<?php

namespace App\Http\Actions\Book;

use App\Http\Controllers\Controller;

final class BookRegisterAction extends Controller
{
    public function __invoke()
    {
        // アクションの処理
    }
}

以下のようにルーティングを設定。

web.php
Route::prefix('book')->group(function () {
    Route::post('/register', App\Http\Actions\Book\BookRegisterAction::class)->name('book.register');
});

すると下図のようなエラーが発生しました。
スクリーンショット 2020-11-23 9.56.05.png
どうやらルーティングがだめとのこと。。。

原因と解決策

Laravelはデフォルトで、コントローラーがApp\Http\Controllers\にあることを前提としています。
実際に発生したエラーには以下のような記載がありました。

Illuminate\Routing\RouteAction::makeInvokable("App\Http\Controllers\App\Http\Actions\Book\BookRegisterAction")

完全にデフォルト+追記したアクションになってます...
こちらはapp/Providers/RouteServiceProvider.phpに定義されているので修正を加えます。

RouteServiceProvider.php
<?php

namespace App\Providers;

class RouteServiceProvider extends ServiceProvider
{
    protected $namespace = 'App\Http\Controllers';  ココがデフォルトのネームスペース



    /**
     * Define the "api" routes for the application.
     *
     * These routes are typically stateless.
     *
     * @return void
     */
    protected function mapApiRoutes()
    {
        Route::prefix('api')
             ->middleware('api')
             ->namespace($this->namespace) ← ここを削除
             ->group(base_path('routes/api.php'));
    }

上記のように変更を加えるとルートを認識させることができました。(デフォルトのnamespaceを変更でも可能です)
よりよい解決策などあれば、ご教授お願いいたします。

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

Composerがインストールできない環境でComposerを使用したい時

PHPでシステムを組むときにComposerを使うかと思いますが、
レンタルサーバーだったり、権限がなかったりと、Composerをインストールできないときがあると思います。

その場合、以下手順でComposerのコマンドを叩けます。
※既にローカル環境などで開発していて、composer.jsonがある前提です。


まずはcomposer.jsonがあるフォルダに移動します。

次にcomposer.pharというComposerを叩くためのファイルを落としてきます。

curl -sS https://getcomposer.org/installer | php

あとは以下で叩けます。

php composer.phar install

update

php composer.phar update

composer

php composer.phar
に置き換わった感じですね。

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

Laravel で Target class [Controller] does not exist.が出た時の対応

事象

最近のLaravel (多分v8以降とかそのあたり)では、初期状態で以下のようなコードをroutes/web.phpに記述すると、あるはずのController Classを見つけられずエラーが発生する。

Route::get('/hoge', 'Controller@index');
Illuminate\Contracts\Container\BindingResolutionException
Target class [Controller] does not exist.

http://laravel.internal/hoge

Illuminate\Container\Container::build
htdocs\laravel\vendor\laravel\framework\src\Illuminate\Container\Container.php:811

どうやらContollerの初期位置を見失うようになったらしい。

対応

App/Providers/RouteServiceProvider.php の$namespace のコメントアウトを外す

    /**
     * The controller namespace for the application.
     *
     * When present, controller route declarations will automatically be prefixed with this namespace.
     *
     * @var string|null
     */
    protected $namespace = 'App\\Http\\Controllers'; // ここがデフォルトでコメントアウトされるようになった

参考等

https://kawax.biz/laravel8-routing/
(ここまで書いて理由とか気になってググったらこのブログで全部解決していた)

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

MAMPが起動できない時のエラー解除方法

Apache couldn't be started because port 8888 is in use by some other software.

というエラーが出て、MAMPがスタート出来なかったので調べていたところ、

https://qiita.com/xiaoxiao/items/9e5686a4b02b34929c8b

こちらの方の記事を見たら解決出来ました。

実際に実行したことは、

1、ターミナルのホームディレクトリで、以下コマンドを入力。
$ sudo lsof -i -P | grep "LISTEN"

2、ポートの状態が一覧で表示
  ※8888が使われているものを見つける。

3、以下のコマンドで8888を削除
$ kill -9 XXXX(XXXXは該当する番号を入力)

4、もう一度 1、と同じコマンドで状態を確認すると、
  8888を使っていたものが削除されていました。

5、その後、MAMPを起動してみると無事に起動出来ました。

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

Centos7にてcomposer installを行うと「Your requirements could not be resolved to an installable set of packages.」と怒られた

なにが起こったか

Centos7に運用中のLaravelプロジェクトをクローンしてcomposer installしたらコケた。

$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for php-di/invoker 2.1.0 -> satisfiable by php-di/invoker[2.1.0].
    - php-di/invoker 2.1.0 requires php >=7.3 -> your PHP version (7.2.28) does not satisfy that requirement.
  Problem 2
    - Installation request for doctrine/dbal 2.11.1 -> satisfiable by doctrine/dbal[2.11.1].
    - doctrine/dbal 2.11.1 requires php ^7.3 -> your PHP version (7.2.28) does not satisfy that requirement.
  Problem 3
    - php-di/invoker 2.1.0 requires php >=7.3 -> your PHP version (7.2.28) does not satisfy that requirement.
    - mnapoli/silly 1.7.2 requires php-di/invoker ~2.0 -> satisfiable by php-di/invoker[2.1.0].
    - Installation request for mnapoli/silly 1.7.2 -> satisfiable by mnapoli/silly[1.7.2].

解決方法

エラーメッセージを見る限りPHPのバージョン問題だったので、PHPを7.2から7.4へアップデートした。
※正確にはPHP7.3以上を求められていた。

PHP7.4インストール

$ sudo yum install -y --enablerepo=remi-php74 php which
み込んだプラグイン:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: ty1.mirror.newmediaexpress.com
 * epel: nrt.edge.kernel.org
 * extras: ty1.mirror.newmediaexpress.com
 * remi-php74: ftp.riken.jp
 * remi-safe: ftp.riken.jp
 * updates: ty1.mirror.newmediaexpress.com
...

PHPインストール確認

$ php -v
PHP 7.4.13 (cli) (built: Nov 24 2020 10:03:34) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.13, Copyright (c), by Zend Technologies
    with Xdebug v2.9.8, Copyright (c) 2002-2020, by Derick Rethans

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

Symfonyの認可について

初めに

Userに紐づいたPostエンティティがあったとき「自分の所有するPost以外は見れない」という用件を達成するにはどのような実装方法があるか、という話です。

最も単純な方法

最も単純な方法は、コントローラーでログインしているUserPostかどうか判断する方法です。特に難しいこともありません。
が、しかし、 アクセス権を確認する際に同じようなコードが至る所に出てきてしまうことが容易に想像できます。

    /**
     * @Route("/{id}/detail", name="post_detail")
     */
    public function edit(Post $post)
    {
        $user = $this->getUser();
        if ($post->getUser() !== $user) {
            throw $this->createAccessDeniedException();
        }
        ...
    }

Voterを用いる方法

SymfonyにはVoterという認可のための仕組みがあります1
こちらを用いることで、認可のロジックを分離することができ、あらゆる箇所で使い回すことが出来るようになります。

makeコマンドで作れます。

bin/console make:voter

 The name of the security voter class (e.g. BlogPostVoter):
 > PostVoter

 created: src/Security/Voter/PostVoter.php

makeコマンドで作ると雛形が作成されます。今回は閲覧だけなのでこのように書き換えます。

src/Security/Voter/PostVoter.php
<?php

namespace App\Security\Voter;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;

class PostVoter extends Voter
{
    protected function supports($attribute, $subject)
    {
        return in_array($attribute, ['VIEW'])
            && $subject instanceof \App\Entity\Post;
    }

    protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
    {
        $user = $token->getUser();
        if (!$user instanceof UserInterface) {
            return false;
        }

        switch ($attribute) {
            case 'VIEW':
                return $subject->getUser() === $user; // $subjectに認可対象のエンティティが入る
        }

        return false;
    }
}

これを、コントローラーからこのようにして呼び出すことができます。内部ではVotervoteOnAttributeメソッドが呼び出され、アクセス権がないと判断されると、自動でAccessDeniedExceptionが投げられます。

    /**
     * @Route("/{id}/detail", name="post_detail")
     */
    public function edit(Post $post)
    {
        $this->denyAccessUnlessGranted('VIEW', $post);
        ...
    }

更に美しく

コントローラーでの認可に限った話で言えば、アノテーションを使うことで更にメソッド内部をすっきりさせることができます。
認可のためのコードがメソッド内に1行も現れず、本来のロジックのみに集中出来るのでとても見やすくて良いと思います。

    /**
     * @Route("/{id}/detail", name="post_detail")
     * @IsGranted("VIEW", subject="post")
     */
    public function edit(Post $post)
    {
        ...
    }

参考文献

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