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

Laravel 5.8 にて["SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint"]エラー

Laravel 5.8 にて Cannot add foreign key constraint が出たときの対応

さきに答え書くと、

型がbigIncrementsorbigIntegerで一致してるか確認してみて〜

Laravel 5.8だとphp artisan make:migration 〜で生成したテーブル定義では、
テーブルのメインidの型はbigIncrementsになってる。

経緯

わーいLaravel 5.8 つかってみよ〜としたら、php artisan migrateで早速つまづいた。
以下のようなテーブル定義でmigrate実行したらエラー。

//質問テーブル
    public function up()
    {
        Schema::create('questions', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->string('slug');
            $table->string('body');

            $table->integer('category_id')->unsigned();
            $table->integer('user_id')->unsigned();
            $table->timestamps();
        });
    }

//返答テーブル
    public function up()
    {
        Schema::create('replies', function (Blueprint $table) {
            $table->bigIncrements('id');

            $table->text('body');
            $table->integer('question_id')->unsigned();
            //外部キー制約
            $table->foreign('question_id')->references('id')
                ->on('questions')->onDelete('cascade');
            $table->integer('user_id');
            $table->timestamps();
        });

//エラー
   Illuminate\Database\QueryException  : SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint (SQL: alter table `replies` add constraint `replies_question_id_foreign` foreign key (`question_id`) references `questions` (`id`) on delete cascade)

  at /var/www/bitfumes/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint")
      /var/www/bitfumes/vendor/laravel/framework/src/Illuminate/Database/Connection.php:458

  2   PDOStatement::execute()
      /var/www/bitfumes/vendor/laravel/framework/src/Illuminate/Database/Connection.php:458

  Please use the argument -v to see more details.

対応

んじゃぐぐってみっか。
SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint でぐぐると、先人たちが

  • テーブルの生成順序がおかしいのでは?外部キーを参照するテーブルが存在してないのでは?
  • 制約キーのっけるカラムと、制約キーとして参照するカラムの型が一致してないのでは?

といった事例あり。たしかに確認してみると、自分の場合は

//questions 
$table->bigIncrements('id');
//replies
$table->integer('question_id')->unsigned();
            //外部キー制約
            $table->foreign('question_id')->references('id')
                ->on('questions')->onDelete('cascade');

で型ずれてる。。。
ということで、以下なおしたらエラー解消されました◎

//replies
$table->bigInteger('question_id')->unsigned();
        ^^^^^^^^^^^
            //外部キー制約
            $table->foreign('question_id')->references('id')
                ->on('questions')->onDelete('cascade');

ちなみに、参照する側のテーブルのidをincrementsにしてもOKのようです。
以下参考URLのBe Careful: Laravel 5.8 Added bigIncrements As Defaults
には英語ですが、そのように記述が。
公式が変更点としてbigIncrementsorbigIntegerを取り上げてなかったことに、この記事の方おかんむりでした。

参考URL
Be Careful: Laravel 5.8 Added bigIncrements As Defaults
【メモ】Laravelで外部キー制約付きのテーブルをマイグレーションする時に気をつけた方がよかった点
laravel5.7 外部キーエラー SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint

Added bigIncrements

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

WindowsでCSVファイルアップロードうまくいかない。。。

導入

先日、とあるWebアプリケーションのCSVファイルアップロード機能を追加開発した時にMacではうまくいっていたファイルの中身のバリデートチェックがWindowsでは全然機能しなかった問題が発生したので備忘録として。

アプリケーション環境

  • Ubuntu(バージョンは忘れた)
  • PHP-7.2fpm
  • CakePHP 2系
  • nginx

動作確認環境

不具合が発生した環境

  • WindowsのChrome、InternetExplorer11、FireFox

問題がなかった環境

  • MacのChrome、FireFox

原因調査内容

MacとWindowでは同じChromeでも仕様がちがう?

→検索していろいろ調べてみたが原因解明できず、一旦保留

MacとWindowsではブラウザの初期のセキュリティ設定が違う??

→JavaScriptやCookieの設定等いろいろいじってみたが挙動変わらず。

nginxかPHPの設定がおかしい???

→nginxはそもそも関係なかった。。。
→PHPはphp.confにuploadファイルの設定があったので、一時保存先や最大ファイルサイズ等変更するが変わらず(そもそもサーバの設定ならWindowsとMacで挙動変わらないはず。)

ctpファイル(View)の実装がおかしい????

→こちらもformタグに「multipart/form-data」が設定されているから問題ない気がする?

MacとWindowsでrequestの中身が違うんじゃ?????

→とりあえず、アップロードファイルが格納される変数をCotrollerで画面に出力するコードを記載
 MacとWindowsのそれぞれで出力結果を確認してみる。

 // ファイルの中身を出力
 echo "<pre>";
 var_dump($_FILE);
 echo "</pre>";
 exit();

出力結果が以下、、、ん??

#Windows

array(1) {
  ["data"]=>
  array(5) {
    ["name"]=>
    array(1) {
      ["XXXXXXXX"]=>
      array(1) {
        ["upfile"]=>
        string(12) "TEST.csv"
      }
    }
    ["type"]=>
    array(1) {
      ["XXXXXXXX"]=>
      array(1) {
        ["upfile"]=>
        string(24) "application/vnd.ms-excel"
      }
    }
    ["tmp_name"]=>
    array(1) {
      ["XXXXXXXX"]=>
      array(1) {
        ["upfile"]=>
        string(14) "/tmp/phpQYwrAs"
      }
    }
    ["error"]=>
    array(1) {
      ["XXXXXXXX"]=>
      array(1) {
        ["upfile"]=>
        int(0)
      }
    }
    ["size"]=>
    array(1) {
      ["XXXXXXXX"]=>
      array(1) {
        ["upfile"]=>
        int(56)
      }
    }
  }
}

#Mac
array(1) {
  ["data"]=>
  array(5) {
    ["name"]=>
    array(1) {
      ["XXXXXXXX"]=>
      array(1) {
        ["upfile"]=>
        string(12) "TEST.csv"
      }
    }
    ["type"]=>
    array(1) {
      ["XXXXXXXX"]=>
      array(1) {
        ["upfile"]=>
        string(8) "text/csv"
      }
    }
    ["tmp_name"]=>
    array(1) {
      ["XXXXXXXX"]=>
      array(1) {
        ["upfile"]=>
        string(14) "/tmp/phpICM53a"
      }
    }
    ["error"]=>
    array(1) {
      ["XXXXXXXX"]=>
      array(1) {
        ["upfile"]=>
        int(0)
      }
    }
    ["size"]=>
    array(1) {
      ["XXXXXXXX"]=>
      array(1) {
        ["upfile"]=>
        int(18)
      }
    }
  }
}


MIMEtypeが違うな?

結論

ファイルの型のチェックの際、MIMEtypeのチェックをしていたが、
"text/csv"のみ許容する実装になっていたため、Windowsから送信されるMIMEtypeが"application/vnd.ms-excel"になっていたのでCSVファイルと判定されていませんでしたね。。。

現在はファイル名の後ろが「.csv」かどうかで判定するように変更して落ち着きました。

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

conohaのオブジェクトストレージをopenstackでPHPから使う(2019年)

はい、S3使ったら苦労はないということはわかってます、ただ、転送量無料は正義です。
リファレンスも少しわかりにくかったので、メモ。

$optionsの内容についてはconohaの管理画面にある。

使ってるopenstackは▽
https://github.com/php-opencloud/openstack/blob/master/doc/index.rst

まずは、composerで入れてね。

$ composer require php-opencloud/openstack
use OpenStack\OpenStack;
use Guzzle\Http\Exception\ClientErrorResponseException;
use GuzzleHttp\Psr7\Stream;

       $options = [
            'authUrl' => env('CONOHA_AUTH_URL'),  // APIの認証URL
            'username' => env('CONOHA_USERNAME'),  // API ユーザーのユーザー名
            'password' => env('CONOHA_PASSWORD'),      // API ユーザーのパスワード
            'tenantName' => env('CONOHA_TENANT_NAME'),  // テナント情報のテナント名
        ];

        try {
            $client = new OpenStack($options);
            $identityService = $client->identityV2();

            $service = $client->objectStoreV1([
                'identityService' => $identityService,
                'catalogName' => 'Object Storage Service',
            ]);

            //コンテナの存在確認
            //ここはもしかすると作成だけでも良いかもしれない
            if ($service->containerExists('test')) {
                $container = $service->getContainer('test');
            }else{
                //コンテナが存在しない場合はコンテナを作成する
                $container_name = array(
                    'name' => 'test',
                    'readAccess' => '.r:*' //パブリックアクセスにする
                );
                $container = $service->createContainer($container_name);
            }

            //gazelleでアップするファイルを読み込み
            $stream = new Stream(fopen('/var/www/html/app/webroot/img/unko.png', 'r'));

            $file_option = [
                'name'   => 'unko.png',  //オブジェクトストレージ保存するファイル名
                'stream' => $stream,     //これはファイルの本体データ
            ];

            //ファイル(オブジェクトをアップする)
            $container->createObject($file_option);

            // コンテナ内のオブジェクト一覧の取得
            $objects = $container->listObjects();

            // コンテナの中のオブジェクトのパブリックURLを取得する
            foreach ($objects as $object) {
                $images_url[] = $object->getPublicUri();
            }

続いて、ACL関係も調査予定。

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

HomesteadでLaravel環境構築(Windows10)

前置き

もう各所で散々書かれている内容だから私が書かなくても…と思ったのですが、

というsuinさんのツイートに背中を押されたので書くことにしました。
七転八倒したポイントを重点的に書けばいいよね。うんうん。

環境

※ 構築したのは2019年1月頃

  • Windows10
  • Oracle VM VirtualBox 6.0
  • vagrant 2.2.2
  • PHP 7.2
  • Laravel 5.7.21

準備

BOX追加

Git Bashまたはコマンドプロンプトを起動して以下を入力

GitBash
cd c:\
vagrant box add laravel/homestead

ここで「Enter your choice:」と出るので、
3(virtualbox) を入力してEnterします。

しばらく待って、以下のような表示が出ればOK

GitBash
==> box: Successfully added box 'laravel/homestead' (v6.4.0) for 'virtualbox'!

Homesteadダウンロード(git clone)

公式からcloneします。
ディレクトリ名はお好きに。今回は特に何も考えずHomesteadにしました。

GitBash
git clone https://github.com/laravel/homestead.git ~/Homestead

設定の初期化

GitBash
cd ~/Homestead

./init.sh
# Homestead initialized! と表示されたらOK

Homestead.yamlの修正

foldersとsitesを修正します

Gitbash
# 場所は ~/Homestead のまま
vi Homestead.yaml
Homestead.yaml
# (中略)
folders:
    - map: D:\homestead\Laravel # ローカルディレクトリ
      to: /home/vagrant/code # vagrant上のディレクトリ

sites:
    - map: homestead.test # host名(起動後のドメイン名)
      to: /home/vagrant/code/public # web上に公開するディレクトリ(Apachで言うところのhtdocs)

(補足) sites:mapに記載するドメイン名について

後で構築した環境をブラウザで確認するとき、
取り急ぎ https:// ではなく http:// でアクセスする場合は
homestead.app ではなく homestead.test を指定する方が無難です。

http://homestead.apphttps://homestead.app とリダイレクトされるのですが、
Chromeだと自己署名証明書がエラーとなるためアクセスできないためです。

ただ、昨今の情勢としてはSSL通信で動かすことがベターだと思うので、
余裕があれば自己証明が通るよう設定した方がいいかもしれません。
(私はまだやってない……)

SSH鍵ファイル作成

Homestead.yaml の authorize と keys にあたるファイルを作成します

Homestead.yaml
authorize: ~/.ssh/id_rsa.pub # 公開鍵

keys:
    - ~/.ssh/id_rsa # 秘密鍵
GitBash
cd ~

# 鍵が既にあるかどうか
ls -la .ssh

# 無ければ以下を実行

mkdir .ssh
cd .ssh # ~/.ssh へ移動

# 鍵ファイル作成
ssh-keygen -t rsa

# id_rsa.pub と id_rsa が作成されたことを確認する
ls -l

hostsファイル修正

C:\Windows\System32\drivers\etc\hosts にホスト追加します。
これはGit Bashで編集できなかったのでサクラエディタでやっちゃいました。

GitBash
# Homestead.yamlの ip と sites:map を紐づける感じです
192.168.10.10    homestead.test

いざ、起動!

GitBash
# Vagrantfileがある場所に移動
cd Homestead/

# 起動 ちょっと時間かかります
vagrant up

502 Bad Gateway になった

起動して vagrant ssh もできる状態になったのですが、
http://homestead.test をブラウザで開くと502の表示……

どうやら原因はnginxの方にあるらしい
502 Bad Gateway nginx (1.9.7) in Homestead [ Laravel 5 ]

というわけで、nginxの設定を見ていきます

GitBash
# homestead環境にログイン
vagrant ssh

# nginxの設定ファイルはこちら
less /etc/nginx/nginx.conf

「Virtual Host Configs」の項に何がincludeされているか書いてます

/etc/nginx/nginx.conf
http {
# (中略)
        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

includeしているディレクトリを見ると、
どうやら /etc/nginx/sites-available/homestead.test がhomestead環境の設定ファイルのようです。

/etc/nginx/sites-available/homestead.test
server {
# (中略)
    access_log off;
    error_log  /var/log/nginx/homestead.test-error.log error;

エラーログの場所が分かったので、そちらを見ます。
(と言いつつ、調査当時のメモによるとなぜか homestead.app-error.log の方を見ている私…)

/var/log/nginx/homestead.app-error.log
2019/01/17 04:20:09 [crit] 1603#1603: *1 connect() to unix:/var/run/php/php7.2-fpm.sock failed (2: No such file or 
directory) while connecting to upstream, client: 192.168.10.1, server: homestead.app, request: "GET / HTTP/1.1", up
stream: "fastcgi://unix:/var/run/php/php7.2-fpm.sock:", host: "homestead.test"

php-fpmの方に原因がありそう。
類似のエラーを対処した方の記事を見ながら修正してみます。
nginxでphpのパッケージを追加したら’502 Bad Gateway’になってしまったときの対処法

vagrant@homestead
sudo vi /etc/php/7.2/fpm/pool.d/www.conf

# www.confのlistenを修正
;listen = /run/php/php7.2-fpm.sock
listen = /var/run/php/php7.2-fpm.sock

そしてphp-fpmとnginxを再起動します

vagrant@homestead
sudo service php7.2-fpm restart
sudo service nginx restart

これで502エラーが解消されました!

No input file specified が表示される

こちら によると
Homestead.yamlsites:to のディレクトリが間違っているかもということで確認しましたが、私の場合その問題ではなさそうです。

vagrant@homestead
ls -l ~/code/public/index.php
-rwxrwxrwx 1 vagrant vagrant 1823 Jan 17 04:56 /home/vagrant/code/public/index.php

# Homestead.yamlの設定は以下のようになっているので問題なし
sites:
    - map: homestead.test
       to: /home/vagrant/code/public

というわけで、エラーログをtailしながら http://homestead.test に再度アクセス。

/var/log/nginx/homestead.app-error.log
2019/01/17 05:16:18 [error] 7864#7864: *6 FastCGI sent in stderr: "Unable to open primary script: /home/vagrant/code/test/public/index.php (No such file or directory)" while reading response header from upstream, client: 192.168.10.1, server: homestead.app, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.2-fpm.sock:", host: "homestead.test"

なぜか /home/vagrant/code/test/public を見に行っている模様。

実は最初の頃Homestead.yamlを以下のように設定していた時期があり、その時の設定が適用されたままだったようです。

Homestead.yaml(古い設定)
sites:
    - map: homestead.app
      to: /home/vagrant/code/test/public

エラーログが homestead.app-error.log の方に出ていたのもそれが原因です。
強制プロビジョニングで解消しました。

GitBash
vagrant up --provision

これで例の初期画面が表示されました。
その後、エラーログも /var/log/nginx/homestead.test-error.log の方に出力されるようになりました。

お疲れ様でした……!

その他参考

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

Wordpress でメールアドレスなしでユーザー登録したい(プラグインなし)

※注意:
Wordpressビギナーの記事(備忘録)です。
鵜呑みにしたら想定外のことが起こるかもしれないので気をつけてください。セキュリティ的なことも考慮していません。(気づきがあれば随時補足します)

やりたいこと

Wordpress よ、メールアドレスなしでもユーザー追加させてくれ。

対象バージョン

Wordpress 5.1.1

結論から

以下のファイルを修正する。

[wp-admin/includes/user.php]

if ( empty( $user->user_email ) ) {
  // ↓をコメントアウト
  $errors->add( 'empty_email', __( '<strong>ERROR</strong>: Please enter an email address.' ), array( 'form-field' => 'email' ) );
}

[wp-admin/user-new.php]
メールアドレス部分のタグから form-required を削除。「必須」の表示が気になる人は required も削除。

<!-- ↓からform-required を削除 -->
<tr class="form-field form-required">

<!-- ↓をまるごと削除 -->
<span class="description"><?php _e( '(required)' ); ?></span>

経緯

開発用のユーザーとシステム管理のユーザーを同じメールアドレスで登録しようとしたらエラーになる。
Wordpress では同じメールアドレスで複数ユーザーを登録できない。

ならば同じメールアドレスでも登録できるようにすればいいじゃない。
ということでソースを弄ることにする。

ユーザー登録周りは user.php で処理している。
メールアドレスの存在チェックは email_exists() でしてる。

function email_exists( $email ) {
    $user = get_user_by( 'email', $email );
    if ( $user ) {
        return $user->ID;
    }
    return false;
}

最初は email_exists を弄るか、こいつの呼び出し元を変更しようと思ったけど、影響大きそうだからやめた。
ユーザー情報が複数とれたときの挙動の考慮とかしたくないし、email_exists の戻りのユーザーIDを一意にできない。
呼び出し元も各所に散らばってる。

ググると「Allow Multiple Accounts」ってプラグインで解決できるとのことだったけど、プラグインが見つからない。
プラグインの新規追加で検索したら普通出てくるんじゃないの?とか思いながら、出ないものはしょーがないので、自力でどうにかする。

そもそもメールアドレスを登録したいわけではないので、メールアドレスを非必須にする方針に変更。
意図的にメールアドレスを登録するなら、存在チェックもちゃんとやる。(つまり現状維持)

サーバ側(user.php)は POST されたメールアドレスがブランクだったらそもそもテーブル参照すらせずにエラーにしてるから、そこをコメントアウトするだけ。
クライアント側(user-new.php)は form-required で必須チェックが効いてるから、それを外すだけ。

これで無事、メールアドレスなしで複数ユーザー登録できました。
qiita_01.jpg

メールアドレスなしユーザーでのログインも問題なし。(ログインしただけだからその先は知らない)

おまけ

user.php は wp-includes/user.php と wp-admin/includes/user.php があるので注意。
最初 wp-includes/user.php しかないと思ってて、修正しても全然反映されないとか思ってハマった。

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

【Laravel】Request/Responseオブジェクトの利用

コントーラ編集

  • useでRequest/Response追加。
  • アクションを定義時に引数にRequest/Responseを入れる。
  • この引数に入れた変数がオブジェクトになるので、アクションとして使用していく感じ。

サンプルコード

テストページの表示。
クライアント側からのリクエスト情報を表示。
こちらからのレスポンスを表示。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\Response;

class HelloControllerRR extends Controller
{
    public function index(Request $request, Response $response) {
        $html= <<<EOF
<html>
<head>
<meta charset="UTF-8">
<title>Request/Response</title>
<style>body{font-size:16pt;} h1{font-size:100px; color:#eee;}</style>
</head>
<body>
<h1>Request/Response</h1>
<p>これはRequest/Responseオブジェクトを利用したアクションです。</p>
<hr>
<h3>Request</h3>
<pre>{$request}</pre>
<ul>
<li>url()メソッド:{$request->url()}</li>
<li>fullUrl()メソッド:{$request->fullUrl()}</li>
<li>path()メソッド:{$request->path()}</li>
</ul>
<hr>
<h3>Response</h3>
<pre>{$response}</pre>
</body>
</html>
EOF;

    $response->setContent($html);
    return $response;
    }
}
?>

使用メソッド

Request

  • $request->url():アクセスしたURLを返す。
  • $request->fullUrl():アクセスしたアドレスを完全な形で返す。クエリー文字(?abc=xyzなど)を含む。
  • $request->path():ドメイン下のパス部分だけ返す。

Response

  • this->status():クライアント返答時のステータスコードを返す。正常なら200。
  • this->content():コンテンツの取得。
  • this->setContent(値):引数の値にコンテンツを変更する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php-master-changes 2019-04-04

今日は JIT の修正、ドキュメントの typo 修正と不使用変数の削除があった!

2019-04-04

dstogov: Avoid useless code generation for RECV opcodes

dstogov: Fixed crash

dstogov: jump optimization

dstogov: Avoid code generation for BB with single VM instruction

cmb69: Fix typos

dstogov: unused variable

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

【Laravel】コントローラの作成と編集

前提知識

MVCアーキテクチャ
- Model:データ処理。DBに関係。
- View:テンプレートのレンダリング。
- Controller:全体制御。<-ここの話

コントローラ作成

ターミナルにて下記コマンド。

php artisan make:controller HelloController

app/Http/Controllersフォルダ内にHellorController.phpが作成される。
これはApp/Http/Controllersという名前空間にクラスとして配置される。

コントローラにアクションを追加・ルーティング

HellorController.phpを開いてみるとデフォルトでいろいろ書いてある。

  • namespace~:名前空間
  • use~:Illuminate/Httpパッケージ内のRequestを使える状態にしている。
  • class~:HelloControllerはControllerというクラスを継承。
  • この下にアクションを追加していく。 なお、ルートパラメーターとして$id$unknownを渡す。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class HelloController extends Controller
{
    public function index($id='noname', $pass='unknown'){
        return <<< EOF

<html>
<head>
<title>Hello/Index</title>
<meta chraset="UTF-8">
<style>
body{font-size:16pt;}
h1{font-size:100px; color:#eee;}
</style>
</head>
<body>
<h1>Index</h1>
<p>これは、Helloコントーラのindexアクションです。</p>
<ul>
<li>ID: {$id}</li>
<li>PASS: {$pass}</li>
</ul>
</body>
</html>
EOF;

    }
}
?>

次にルートを編集。
routes/web.phpにて

Route::get('hello/{id?}/{pass?}', 'HelloController@index');

これでOK。

コントローラに複数のアクションを持たせる場合

(1)コントローラを記述

app/Http/Controllers/HelloController.phpを編集。
まず名前空間とRequestを設定(namespace~/use~)。
変数と関数の定義(global~/function~)。
そしてからクラス定義。
クラスの中にアクションを二つ以上持たせることで、コントーラに複数のアクションを持たせる。

<?php
// 名前空間とRequestの使用可能を設定。
namespace App\Http\Controllers;
use Illuminate\Http\Request;

// コントーラクラス作成時に必要な関数を自作
global $head, $head, $style, $body, $end;
$head = '<html><head>';
$style = <<<EOF
<style>
body{font-size:16pt;}
h1{font-size:100px; color:#eee;}
</style>
EOF;
$body = '</head><body>';
$end = '</body></html>';

function tag($tag, $txt){
    return "<{$tag}>".$txt."</{$tag}>";
}

// コントーラクラスの作成
class HelloController extends Controller
{
    // 第一アクション
    public function index(){
        global $head, $head, $style, $body, $end;

        $html=$head.tag('title', 'Hello/Index')
        .$style
        .$body.tag('h1', 'Index').tag('p', 'this is Index page').'<a href="/hello/other">go to Other page</a>'
        .$end;

        return $html;
    }

    // 第二アクション
    public function other(){
        global $head, $head, $style, $body, $end;

        $html=$head.tag('title', 'Hello/Other')
        .$style
        .$body.tag('h1', 'other').tag('p', 'this is Other page')
        .$end;

        return $html;
    }
}
?>

(2)ルート情報を編集

routes/web.phpを編集。
Route:getの第二引数でコントーラとアクション名を指定。

Route::get('hello', 'HelloController@index');
Route::get('hello/other', 'HelloController@other');

これでコントローラに複数のアクションを持たせることができた。

シングルアクションコントーラ

namespace,useは省略。
クラスにつきアクションを一つのみ持つ。
アクション定義部に__invoke()を足す。

class HelloControllerSingle extends Controller
{
    public function __invoke(){
    return <<<EOF
<html>
<head>
<meta charset="UTF-8">
<title>Hello/Single</title>
<style>body{font-size:16pt;} h1{font-size:100px; color:#eee;}</style>
</head>
<body>
<h1>Single Action</h1>
<p>これはシングルアクションコントーラのアクションです。</p>
</body>
</html>
EOF;
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Heroku(PHP+MySQL)でWebサービス公開手順

はじめに

localhostでPHP+MySQLで開発したものを、Herokuで公開するためのもの
OS: mac
PHP: フレームワーク無し
DB: MySQL

1.Herokuに登録

https://www.heroku.com/
クレジットカードを登録しておきます。

2.Heroku CLIをインストール

https://devcenter.heroku.com/articles/heroku-cli

$ heroku login

3.アプリ作成

ここではデスクトップにheroku-appというディレクトリを作ります。

$ cd Desktop
$ mkdir heroku-app
$ cd heroku-app

アプリを作成します。アプリ名を指定しない場合は自動で割り振られます。

$ heroku create アプリ名

4.composer

composer.jsonというファイルを作成します。

touch composer.json

phpのバージョン7を指定します。これが正しいのか分かりませんがこれで動きました。
"ext-mbstring": "*" とすることでmb_xxxの関数(mb_strlenとか)が使えるようになります。指定しないでmb_xxxの関数を使うとCall to undefined functionエラーになります。

composer.json
{
    "require": {
      "php": "~7.0",
      "ext-mbstring": "*"
    }
 }

デフォルトのPHPが入っていることが前提です。

$ curl -sS https://getcomposer.org/installer | php
$ mv composer.phar /usr/local/bin/composer

ターミナルを再起動して確認します。大丈夫ならインストールします。

$ cd Desktop/heroku-app
$ composer -v
$ composer install

5.Gitリポジトリ作成

$ git init
$ git remote add heroku https://git.heroku.com/アプリ名.git
$ git remote -v

6.ClearDBアドオンをインストール

herokuでmysqlを使うための設定です。

$ heroku addons:add cleardb:ignite

7.Workbenchをインストール、設定

Workbenchを使うことで、phpMyAdminのようにGUIでデータベースを管理できます。
インストールが完了したら赤枠で囲ったMySQL Connectionsの横の+をクリックして設定画面を開きます。
スクリーンショット 2019-04-05 18.18.34.png

heroku configコマンドでCLEARDB_DATABASE_URLを確認します。

$ heroku config

これを元に入力していきます。CLEARDB_DATABASE_URL: mysql://ユーザ名:パスワード@サーバ名/データベース?reconnect=true
Connection Name: 適当な名前
Hostname: サーバ名
Username: ユーザ名
Password: パスワード
Default Schema: データベース

入力を終えたら、Test Connectionをクリック→上で入力したパスワードを入力→Connection Warning画面が出る→Continue AnywaySuccessfully made the MySQL connectionOKOK

8.phpMyAdminからDBエクスポート→WorkbenchにDBインポート

phpMyAdminからエクスポート

phpMyAdminからエクスポートしたいDBを選択→詳細にチェック→出力をファイルに保存する実行

Workbenchにインポート

7.で設定したものに接続してAdministraionタブからData Import/RestoreImport from Self-Contained Fileにチェックを入れてphpMyAdminからエクスポートしたsqlファイルを選択します。
Default Target Schemaを指定します。newの方ではなくてセレクトメニューでデフォルトのものを指定します。
なぜかフリーズしたように?選択できないときがありますが、謎です。
最後にStart Importをクリックします。

9.PHPのDB接続設定を変更する

local用から本番用に書き換える必要があります。

function.php
function dbConnect(){
  $db = parse_url($_SERVER['CLEARDB_DATABASE_URL']);
  $db['dbname'] = ltrim($db['path'], '/');
  $dsn = "mysql:host={$db['host']};dbname={$db['dbname']};charset=utf8";
  $user = $db['user'];
  $password = $db['pass'];
  $options = array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY =>true,
  );
  $dbh = new PDO($dsn,$user,$password,$options);
  return $dbh;
}

10.herokuへデプロイ

$ git add -A
$ git commit -m "commit"
$ git push heroku master

heroku openでブラウザを開いて表示してくれます。localと同じようにDBのデータ等ちゃんと表示できていれば完了です。

$ heroku open

11.WorkbenchでDBの中身を確認する

赤枠で囲ったSchemasTables
スクリーンショット 2019-04-06 7.52.37.png
各テーブルにマウスホバーすると、右にアイコンが出る(赤枠で囲った所)ので一番右のカレンダーみたいなアイコンをクリックするとデータが確認できます。または、SQL文を打ち込んで雷アイコンを押してもデータ確認ができます。
スクリーンショット 2019-04-06 8.15.19.png

その他エラーなど

・herokuはphpログファイルとかエラーが見れないようなので、見るにはini_set('display_errors',1)を一時的に使います。

Error Code: 2013. Lost connection to MySQL server〜というエラーが出る場合
https://stackoverflow.com/questions/10563619/error-code-2013-lost-connection-to-mysql-server-during-query

・composer.jsonを編集したら作業ディレクトリで composer update を行う

$ composer update

参考:

herokuでPHPからMySQL(ClearDB)を使うhttp://blog.a-way-out.net/blog/2014/12/11/heroku-php-mysql/
herokuがcomposer.lock必須になったのでcomposerの入れ方をメモしておくhttp://kayakuguri.github.io/blog/2015/08/25/composer-lock-require/
Heroku データベースの管理(ClearDB アドオンによる MySQL の利用)http://www.ownway.info/Ruby/heroku/how/management/database/cleardb
MySQL WorkbenchをMacにインストールする方法https://programmingnavi.com/77/
heroku環境でmb_xxx関数を使うと、Fatal error: Call to undefined function …のエラーが表示されるhttps://qiita.com/taro-hida/items/f677abe2bc3b689002b3

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

test

1.Herokに登録

クレジットカードを登録しておきます。

2.Herok CLIをインストール

https://devcenter.heroku.com/articles/heroku-cli

$ heroku login

3.Gitリポジトリ作成

ここではデスクトップにheroku-appというディレクトリを作ります。

$ cd Desktop
$ mkdir heroku-app
$ cd heroku-app
$ git init
$ git remote add heroku https://git.heroku.com/アプリ名.git

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

下書き

  • mac
  • heroku
  • php(フレームワーク無し)
  • mysql
  • コマンド操作多め

1.Herokuに登録

クレジットカードを登録しておきます。

2.Heroku CLIをインストール

https://devcenter.heroku.com/articles/heroku-cli

$ heroku login

3.アプリ作成

ここではデスクトップにheroku-appというディレクトリを作ります。

$ cd Desktop
$ mkdir heroku-app
$ cd heroku-app

アプリを作成します。アプリ名を指定しない場合は自動で割り振られます。

$ heroku create アプリ名

4.composer

composer.jsonというファイルを作成します。

touch composer.json

phpのバージョン7を指定します。これが正しいのか分かりませんがこれで動きました。
"ext-mbstring": "*" とすることでmb_xxxの関数(mb_strlenとか)が使えるようになります。指定しないとCall to undefined functionエラーになります。

composer.json
{
    "require": {
      "php": "~7.0",
      "ext-mbstring": "*"
    }
 }

デフォルトのPHPが入っていることが前提です。

$ curl -sS https://getcomposer.org/installer | php
$ mv composer.phar /usr/local/bin/composer

ターミナルを再起動して確認します。大丈夫ならインストールします。

$ composer -v
$ composer install

5.ClearDBアドオンをインストール

herokuでmysqlを使うための設定です。

$ heroku addons:add cleardb:ignite

6.WorkBenchをインストール

phpMyAdminのようにGUIでデータベースを管理できます。
```
$ heroku config

```

phpMyAdminからデータベース エクスポート デフォルトはテキスト出力なのでsqlファイルに出力する
Workbenchで インポート

6.Gitリポジトリ作成

$ git init
$ git remote add heroku https://git.heroku.com/アプリ名.git
$ git remote -v

7.herokuへデプロイ

$ git add -A
$ git commit -m "commit"
$ git push heroku master

その他

herokuはphpログファイルとかエラー見れないようなのでini_set('display_errors',1)を一時的に使います。

参考:

herokuでPHPからMySQL(ClearDB)を使うhttp://blog.a-way-out.net/blog/2014/12/11/heroku-php-mysql/
herokuがcomposer.lock必須になったのでcomposerの入れ方をメモしておくhttp://kayakuguri.github.io/blog/2015/08/25/composer-lock-require/
Heroku データベースの管理(ClearDB アドオンによる MySQL の利用)http://www.ownway.info/Ruby/heroku/how/management/database/cleardb

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

HerokuでWebサービス公開(PHP+MySQL)

はじめに

localhostでPHP+MySQLで開発したものを、Herokuで公開するためのもの
OS: mac
PHP: フレームワーク無し
DB: MySQL

1.Herokuに登録

https://www.heroku.com/
クレジットカードを登録しておきます。

2.Heroku CLIをインストール

https://devcenter.heroku.com/articles/heroku-cli

$ heroku login

3.アプリ作成

ここではデスクトップにheroku-appというディレクトリを作ります。

$ cd Desktop
$ mkdir heroku-app
$ cd heroku-app

アプリを作成します。アプリ名を指定しない場合は自動で割り振られます。

$ heroku create アプリ名

4.composer

composer.jsonというファイルを作成します。

touch composer.json

phpのバージョン7を指定します。これが正しいのか分かりませんがこれで動きました。
"ext-mbstring": "*" とすることでmb_xxxの関数(mb_strlenとか)が使えるようになります。指定しないでmb_xxxの関数を使うとCall to undefined functionエラーになります。

composer.json
{
    "require": {
      "php": "~7.0",
      "ext-mbstring": "*"
    }
 }

デフォルトのPHPが入っていることが前提です。

$ curl -sS https://getcomposer.org/installer | php
$ mv composer.phar /usr/local/bin/composer

ターミナルを再起動して確認します。大丈夫ならインストールします。

$ cd Desktop/heroku-app
$ composer -v
$ composer install

5.Gitリポジトリ作成

$ git init
$ git remote add heroku https://git.heroku.com/アプリ名.git
$ git remote -v

6.ClearDBアドオンをインストール

herokuでmysqlを使うための設定です。

$ heroku addons:add cleardb:ignite

7.Workbenchをインストール、設定

Workbenchを使うことで、phpMyAdminのようにGUIでデータベースを管理できます。
インストールが完了したら赤枠で囲ったMySQL Connectionsの横の+をクリックして設定画面を開きます。
スクリーンショット 2019-04-05 18.18.34.png

heroku configコマンドでCLEARDB_DATABASE_URLを確認します。

$ heroku config

これを元に入力していきます。CLEARDB_DATABASE_URL: mysql://ユーザ名:パスワード@サーバ名/データベース?reconnect=true
Connection Name: 適当な名前
Hostname: サーバ名
Username: ユーザ名
Password: パスワード
Default Schema: データベース

入力を終えたら、Test Connectionをクリック→上で入力したパスワードを入力→Connection Warning画面が出る→Continue AnywaySuccessfully made the MySQL connectionOKOK

8.phpMyAdminからDBエクスポート→WorkbenchにDBインポート

phpMyAdminからエクスポート

phpMyAdminからエクスポートしたいDBを選択→詳細にチェック→出力をファイルに保存する実行

Workbenchにインポート

7.で設定したものに接続してAdministraionタブからData Import/RestoreImport from Self-Contained Fileにチェックを入れてphpMyAdminからエクスポートしたsqlファイルを選択します。
Default Target Schemaを指定します。newの方ではなくてセレクトメニューでデフォルトのものを指定します。
なぜかフリーズしたように?選択できないときがありますが、謎です。
最後にStart Importをクリックします。

9.PHPのDB接続設定を変更する

local用から本番用に書き換える必要があります。

function.php
function dbConnect(){
  $db = parse_url($_SERVER['CLEARDB_DATABASE_URL']);
  $db['dbname'] = ltrim($db['path'], '/');
  $dsn = "mysql:host={$db['host']};dbname={$db['dbname']};charset=utf8";
  $user = $db['user'];
  $password = $db['pass'];
  $options = array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY =>true,
  );
  $dbh = new PDO($dsn,$user,$password,$options);
  return $dbh;
}

10.herokuへデプロイ

$ git add -A
$ git commit -m "commit"
$ git push heroku master

heroku openでブラウザを開いて表示してくれます。localと同じようにDBのデータ等ちゃんと表示できていれば完了です。

$ heroku open

11.WorkbenchでDBの中身を確認する

赤枠で囲ったSchemasTables
スクリーンショット 2019-04-06 7.52.37.png
各テーブルにマウスホバーすると、右にアイコンが出る(赤枠で囲った所)ので一番右のカレンダーみたいなアイコンをクリックするとデータが確認できます。または、SQL文を打ち込んで雷アイコンを押してもデータ確認ができます。
スクリーンショット 2019-04-06 8.15.19.png

その他エラー

・herokuはphpログファイルとかエラーが見れないようなので、見るにはini_set('display_errors',1)を一時的に使います。

Error Code: 2013. Lost connection to MySQL server〜というエラーが出る場合
https://stackoverflow.com/questions/10563619/error-code-2013-lost-connection-to-mysql-server-during-query

参考:

herokuでPHPからMySQL(ClearDB)を使うhttp://blog.a-way-out.net/blog/2014/12/11/heroku-php-mysql/
herokuがcomposer.lock必須になったのでcomposerの入れ方をメモしておくhttp://kayakuguri.github.io/blog/2015/08/25/composer-lock-require/
Heroku データベースの管理(ClearDB アドオンによる MySQL の利用)http://www.ownway.info/Ruby/heroku/how/management/database/cleardb
MySQL WorkbenchをMacにインストールする方法https://programmingnavi.com/77/
heroku環境でmb_xxx関数を使うと、Fatal error: Call to undefined function …のエラーが表示されるhttps://qiita.com/taro-hida/items/f677abe2bc3b689002b3

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

メモ: laravel で trait を使って static method で Event 登録するような時に Mockery を使って UnitTest を書く方法

よく忘れるので自分用のメモ。

何を実現したいか

例えば、以下のような Trait が存在して、 static::deleted() の Closure も Mockery を使って UnitTest でカバーしたい時の方法。

trait SomeTrait
{
    /**
     * boot
     */
    public static function bootSomeTrait()
    {
        static::deleted(function ($model) {
            $model->someModelsMethod();
        });
    }
}

どうやって実現するか

以下のようにすると closure 部分も UnitTest でカバーできるようになる。

final class SomeTraitTest extends TestCase
{
    public function testSomeTrait()
    {
        $class = new class {
            use SomeTrait;

            public static function deleted($closure)
            {
                $mock = Mockery::mock(stdClass::class);
                $mock->shouldReceive('someModelsMethod')->once()->with();
                $closure($mock);
            }
        };

        $class::bootSomeTrait();
    }
}

anonymous class 内で use SomeTrait して、 SomeTrait が用いる public static function deleted($closure) を anonymous class に定義する。

この anonymous class の引数である $closureSomeTraitstatic::deleted(function ($model) {...} の function 部分が引数なので、 callable となる。

なので、ここで適切に Mockery 等を使って何らかの手続を確認するように書けば良い。

注意点とか

anonymous class(無名クラス)使ってるので、当然 PHP7 以上じゃないとダメ。

laravel で event を登録する処理を書くなら、素直に Observer 使うべきだと思う。 https://laravel.com/docs/5.8/eloquent#observers

ただ、外部のパッケージで便利なやつがあって、それが Trait で実現されてて…かつ、そのパッケージに少しだけオリジナルな処理を追加したい時とかに、 trait ベースで Event 登録が必要になったりする。

あと parent::otherMethod(); とかある場合は、このアプローチは使えない。(anonymous class 使ってるので親が居ないので当然。)

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

getterの命名について(PHPの場合)

TL;DR

getterの命名とか、悩みを吐き出して冷静になります

「これgetterだからgetHoge()の方がわかりやすくないですか?」と言われ、明確にNoと言えなかったので言語化しておきたいのです

※ setterにはあまり触れません

結論

hogeプロパティを参照する際は
* __get()を実装し
* @property-read hogeを定義し
* getterとしてのgetHoge()は撲滅

発端

ゲームとかで、IDと経験値を持った以下のPlayerクラスを考えます

class Player
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }
}

何も考えずにgetterをつけてやると、おそらく以下です

class Player
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getExp(): int
    {
        return $this->exp;
    }
}

要件

このPlayerクラスに「経験値を取得する」メソッドを定義したいですが、どういう命名が適切でしょうか?

いわゆるsetterとしてsetExp()を定義すべきでしょうか?

class Player
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getExp(): int
    {
        return $this->exp;
    }

    public function setExp(int $exp): self
    {
        $this->exp = $exp;
        return $this;
    }
}

あるいはプロパティのexpに対して加算するので、addExp()を定義すべきでしょうか?

class Player
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getExp(): int
    {
        return $this->exp;
    }

    public function addExp(int $exp): self
    {
        $this->exp += $exp;
        return $this;
    }
}

違和感

addExp()にしたとして、利用する側を考えると以下のようになりそうです

$player = new Player($id, $exp);
// 現在の経験値
echo $player->getExp() . PHP_EOL;
$addExp = someFunction(); // 獲得したい経験値
$player->addExp($addExp);
// 加算後の経験値
echo $player->getExp() . PHP_EOL;

でも「経験値を取得する」って、本来はPlayerから見た振る舞いではないでしょうか?

setExp()addExp()は誰かがPlayerに「取得させてる」って感じの振る舞いです

「経験値を取得する」をPlayerから見たら、それこそgetExp()なのでは?と思うわけです

class User
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getExp(): int
    {
        return $this->exp;
    }

    public function getExp(int $exp): self
    {
        $this->exp += $exp;
        return $this;
    }
}

でもこれは当然、本来のgetterとしてのgetExp()と重複してるのでエラーになります

そもそも

Playerの現在の経験値を参照するメソッドがgetExp()っていうのも、やっぱり「誰かから見た」振る舞いです

オブジェクト指向って、もっとオブジェクトが自身の振る舞いを定義するべきでは?と思うわけです

そう考えると、Playerの現在の経験値を参照するメソッドは、なんならtakeExp()とかshowExp()とかの方がよっぽどオブジェクト指向的だと思うわけです

でも文脈によってtakeshowは適切でない場合が多々あると思うので、プロパティそのものが参照できれば一番無難そうです

解決

PHPではマジックメソッド__get()を実装することで、存在しないプロパティへアクセスした際の挙動を制御できます

「経験値を取得する」をgetExp()として、合わせて定義すると以下です

/**
 * @property-read int $exp
 */
class User
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function __get(string $property)
    {
        if (!property_exists($this, $property)) {
            throw new \Exception("Invalid access: {$property}");
        }

        return $this->$property;
    }

    public function getExp(int $exp): self
    {
        $this->exp += $exp;
        return $this;
    }
}

利用する側は以下のようになります

$player = new Player($id, $exp);
// 現在の経験値
echo $player->exp . PHP_EOL;
$addExp = someFunction(); // 獲得したい経験値
$player->getExp($addExp);
// 加算後の経験値
echo $player->exp . PHP_EOL;

@property-readアノテーションを付けておくことで、PhpStorm等のIDEでも認識してくれるようになります

しかしこの場合、@property-readアノテーションを付けていないidプロパティも同様にアクセスできてしまいます

@property-readしたものしか見せたくないという場合は、__get()でバリデーションするしかなさそうです

/**
 * @property-read int $exp
 */
class User
{
    private $id;

    private $exp;

    private const VISIBLE_PROPERTIES = [
        'exp',
    ];

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function __get(string $property)
    {
        if (!property_exists($this, $property)) {
            throw new \Exception("Invalid access: {$property}");
        }

        if (!in_array($property, self::VISIBLE_PROPERTIES)) {
            throw new \Exception("Cannot access: {$property}");
        }

        return $this->$property;
    }

    public function getExp(int $exp): self
    {
        $this->exp += $exp;
        return $this;
    }
}

まとめ

さすがにgetExp()は紛らわしいですよ…という意見はありそうですが、それはそれとして

脳死でgetter定義せずに、もっともっとオブジェクティブな振る舞いを定義していきたいなと思う次第です

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