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

require_onceでファイルの読み込み、色々あるけど結局どう書けばいいのか

結論

index.php
require_once __DIR__.'/foo/A.php';

これが一番てっとり早くて、設定による差異もなさそう。

https://php.net/manual/ja/language.constants.predefined.php

そのファイルの存在するディレクトリ。include の中で使用すると、 インクルードされるファイルの存在するディレクトリを返します。 つまり、これは dirname(__FILE__) と同じ意味です。 ルートディレクトリである場合を除き、ディレクトリ名の末尾にスラッシュはつきません。

ディレクトリ構成

/src/をドキュメントルートに指定している想定

src/
  foo/
   A.php
   B.php
  index.php

相対パスでの指定

A.php
require_once 'B.php';
index.php
require_once 'foo/A.php';

相対パスでもindex.phpからA.phpを読み込むことは可能ですが、
A.phpで同じく相対パスを使ってB.phpを読み込んでいた場合エラーになります。

相対パスで記述した場合はPHPファイルを実行したファイルがある位置からの相対パス、
つまりこの場合であればsrc/foo/B.phpではなく、存在しないsrc/B.phpをよんでいることになります。

ドキュメントルートからの指定

index.php
require_once $_SERVER['DOCUMENT_ROOT'].'/foo/A.php';

https://www.php.net/manual/ja/reserved.variables.server.php

現在実行されているスクリプトが存在するドキュメントルート ディレクトリです。サーバーのコンフィグレーションファイルで 定義されています。

$_SERVER['DOCUMENT_ROOT']はApacheの場合httpd.confで設定されています。
ローカルと本番環境など設定によっては差異があることがあります。

サーバーのルートからの指定

index.php
require_once '/var/www/html/src/foo/A.php';

ドキュメントルートとサーバーのルートは別物です。
現在は/src/をドキュメントルートに設定しているという想定ですが、
サーバーのルートからパスを指定する場合はドキュメントルートより上のディレクトリも記述しないといけません。
/var/www/html/はサーバーの設定によって異なります。

include_pathでの指定

index.php
require_once 'foo/A.php';

https://www.php.net/manual/ja/function.include.php

ファイルのインクルードは、指定されたパスから行います。パスを指定しない場合は、 include_path の設定を利用します。 ファイルが include_path に見つからないときは、include は呼び出し元スクリプトのディレクトリと現在の作業ディレクトリも探します。

相対パスと同じ記述方法ですが、include_pathが設定されている場合、include_pathから先にファイルを探してくれるようです。
include_pathはphp.iniや.htaccess、set_include_path関数などで設定できます。

dirname(__FILE__)での指定

__DIR__と同じ意味です。

__DIR__が追加されたのが5.3.0以降なのでそれ以前のバージョンではこちらを使用します。

https://php.net/manual/ja/language.constants.predefined.php

5.3.0 __DIR__ と __NAMESPACE__ が追加されました。

以下余談

include, requireの違い

処理失敗時にエラーを発するか、警告を発するか。

require : エラー(処理がストップする)
include : 警告(処理は続行)

https://www.php.net/manual/ja/function.require.php

require_onceとrequireの違い

require_onceは一度しか呼び出さない。

foo.php
$foo = 1;
bar.php
$bar = 1;
index.php
require_once 'foo.php';
$foo ++;
var_dump($foo);  // 2

/*
  foo.phpは一度しか呼び出されないので$fooが初期化されない
*/
require_once 'foo.php';
var_dump($foo);  // 2


require 'bar.php';
$bar ++;
var_dump($bar);  // 2

/*
  bar.phpがもう一度呼び出されるのでbar.phpが初期化される
*/
require 'bar.php';
var_dump($bar);  // 1

オートローダを使う

いちいちrequire_onceを記述するのも面倒なのでオートローダを使った方が良さそう。
オートローダーはspl_autoload_registerを使って自作することもできますが、
Composerを使うのが楽でよさそうです。

Composerでautoload.phpを作成する

参考

【PHP】include, requireの安全なパスの書き方と違い
【PHP】include, requireの違いと使い分け

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

フラットphpをテストする

まえがき

phpunitを用いればphpのテストで困ることはありません。

今夜までだ
ただし、今夜までだ!

テスト対象はフラットphp。classもvendorもなく、DBアクセスもありません。
もちろんcomposerも使用していません。

テスト対象のphp
<?php
namespace honya;

[$status, $res] = fncHonya();

header('HTTP/1.1 '.$status);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($res);

function fncHonya(){
  // ちょこっとビジネスロジック
  return [200, ['result'=>'ok']];
}

phpunitはフラットphpをテストできない!

初めて知りました。フラットphpで開発したことがなかったので。
テストコードを作る方が面倒でしたが、知恵を絞ってテストできるようにしてみました。

元のファイル構成:

app
└ honya.php

テストできるようにしたファイル構成:

app
└ honya.php
└ compser.json
└ phpunit.xml
└ tests
 └ HonyaTestClass.php
 └ HonyaTest.php
 └ fixtures

テスト用classを作る

HonyaTestClassを作成し、honya.phpをrequireしたメソッドを作成しました。

ポイントは3点

テスト用class
<?php
namespace honya;

class HonyaTestClass {

    private $header = [];
    private $http_status = null;
    private $body = null;

    /**
     * @runInSeparateProcess
     */
    public function request(){
        ob_start();
        require 'honya.php';
        $this->body = ob_get_clean();
        $this->http_status = http_response_code();
        $this->header = xdebug_get_headers(); //@TODO php7 xdebug
    }

    public function getHttpStatus(){ return $this->http_status; }
    public function getHeader(){ return $this->header; }
    public function getBody(){ return $this->body; }
}

namespace はテスト時に必要と分かりましたが、実際は不要です。
性能に影響がないので入れました。
echo で bodyを返すため、ob_start、ob_get_clean で echo 出力を奪い取っています。
@runInSeparateProcess を付けることでphpunitに邪魔されず、テスト対象の echo だけ取得できます。
header は header_list() で取得できないので(原因不明)、xdebug_get_headers() を使用しています。php5では機能しましたが、php7では機能しません(原因不明)

テストを作る

classさえ作ればテストコードは普通です。

HonyaTest.php(テスト本体)
<?php
namespace honya;

require_once 'tests\HonyaTestClass.php';
require_once 'tests\HonyaTestUtil.php';

class HonyaTest extends \PHPUnit\Framework\TestCase {

    use HonyaTestUtil;

    /** @runInSeparateProcess */
    public function test1() {

        $body = 'honya body';
        $this->init_function(200, $body);

        $_POST['honya_param1'] = 'honya1';
        $_SERVER['REQUEST_URI'] = '/honya';

        $clazz = new \honya\HonyaTestClass();
        $clazz->request();

        $this->assertEquals('honya2 body', $clazz->getBody());
        $this->assertEquals('200', $clazz->getHttpStatus());
//      $this->assertSame([], $clazz->getHeader()); //@TODO php7 xdebug
    }
}

テスト用utilを作る

ポイントは2点

  • namespace
  • trait
HonyaTestUtil.php()
<?php
namespace honya;

use phpmock\phpunit\PHPMock;

trait HonyaTestUtil {

    use PHPMock;

    public function init_function($http_status, $body){
        $this->getFunctionMock(__NAMESPACE__, 'getenv')->expects($this->any())->willReturn('');
    }
}

getFunctionMock は namespace が空のときエラーになるります。
テスト対象(honya.php)に namespace を入れたのは、そういう訳です。

HonyaTestUtil はclassにしていましたが、new でも static でもエラーになるので、trait にしました。

./vendor/bin/phpunit

で、実行できます。
phpunit.xml のデフォルトで autoload.php と テスト対象フォルダを指定されてるためです。
カバレッジも表示できるみたいですが、表示するまでもないので、暇があったらやります。

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

Laravelで画像ファイルを保存したい

今参加しているプログラミングスクールでの課題で、LaravelでECsiteを作っているのですが、Laravelへの画像の保存に苦戦したので、備忘録ようにメモします

作りたいもの

  • Ecsite(の一部)
  • 商品の一覧を表示した際に、画像ファイルが保存されるようにしたい

どこに画像を保存するか?

初めはDBにそのまま画像をぶち込もうと思っていたのですが、どうもそれはスマートではないらしい。。。
terateil:データベースに画像を保存するのはありでしょうか?

すると、LaravelではStorage/app/public内に保存するのが基本のようです。そして、各々の画像へのパスをDBに保存することにしました。

保存するフォームを作る

今まで、お問い合わせフォームを作っていたので、それを使いまわして画像アップロード画面のひな形を作ります。bladeについては省略です。
具体的には、input(入力画面)->confirm(確認画面)->complete(完了画面)の形にして、input->confirmの際に一度Storage/app/public内のtempディレクトリに仮保存し、confirm->completeの際にStorage/app/public内のproductImageディレクトリに移動します。

web.php
Route::get('/image_input', 'ImageController@getImageInput');
Route::post('/image_confirm', 'ImageController@postImageConfirm');
Route::post('/image_complete', 'ImageController@postImageComplete');
image_input.blade.php
@section('body')
    <form action="image_confirm" method="post" enctype="multipart/form-data" id="form">
        @csrf
        ファイル
        <input type="file" name="imagefile" value=""/><br /><br />

        商品名<br />
        <input type="text" name="product_name" size="50" value="{{ old('name') }}"/><br /><br />

        <input type="submit" name="confirm" id="button" value="確認" />
    </form>
@endsection

formタグ内で

enctype="multipart/form-data

を指定しないと、controllerでfileメソッドを使ってファイルを取り出そうとしてもnullになります。

image_confirm.blade.php
@section('body')
    <form action="image_complete" method="post">
        @csrf
        <table border="1">
            <tr>
                <td>画像</td>
                <td><img src="{{ $data['read_temp_path'] }}" width="200" height="130"></td>
            </tr>
            <tr>
                <td>商品名</td>
                <td>{{ $data['product_name'] }}</td>
            </tr>
        </table>
        <input type="submit" name="action" value="送信" />
    </form>
@endsection
@section('body')
    <p>商品のアップロードが完了しました</p>
@endsection

また、DBは次のmigrationファイルを基にして作り、Modelも作成しておきます

xxxx_xx_xx_xxxxxx_create_products_table.php
public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->bigIncrements('product_id');
            $table->string('path');
            $table->string('product_name');
            $table->timestamps();
        });
    }

問題はcontroller...

取り敢えず、フォームから画像と名前を受け取って、画像には一意のファイル名としてtempディレクトリに保存します。また、sessionに保存して、confirm画面から読み込めるようにします。

ImageController.php
public function getImageInput(){
    return view('image_input');
}

public function postImageConfirm(ImageUploadRequest $request){
    $post_data = $request->except('imagefile');
    $imagefile = $request->file('imagefile');

    $temp_path = $imagefile->store('public/temp');

    $product_name = $post_data['product_name'];

    $data = array(
        'temp_path' => $temp_path,
        'product_name' => $product_name,
    );
    $request->session()->put('data', $data);

    return view('image_confirm', compact('data') );

storeメソッドは、引数内のディレクトリに一意のファイル名として保存し、そこへのパスを返します。

画像を読み込むディレクトリ

Laravelの場合、画像を読み込むディレクトリはpublic/storageディレクトリです。
ややこしいのでもう一度書きます。
保存:storage/app/public
読込:public/storage
名前を変えてほしいですね。なので、public/storageから、storage/app/public内を読み込めるよう(シンボリックリンクを張る)にします。

php artisan storage:link

これで、storage/app/public内を覗けるようになりました。

ディレクトリが違えばパスも違う

では、いざconfirm画面を表示! させても画像が表示されません...
それもそのはず、読み込みに行っているパスが
(storage/app/)public/temp/xxx.jpeg
となっているからですね。でも先ほど書いた通り、読み込むのは
(public/)storage/temp/xxx.jpeg
でないといけないですね。
よって、controllerを次のように変えます。

ImageController.php
    $temp_path = $imagefile->store('public/temp');
    $read_temp_path = str_replace('public/', 'storage/', $temp_path); //追加

    $product_name = $post_data['product_name'];

    $data = array(
        'temp_path' => $temp_path,
        'read_temp_path' => $read_temp_path, //追加
        'product_name' => $product_name,
    );
    $request->session()->put('data', $data);

str_replaceメソッドで、public/をstorage/に置き換えました。
いざ表示させると、無事できました!

後は移動させてDBへ保存

次に、confirm->completeにおいて、tempディレクトリからproductImageディレクトリに移動させ、DBに保存します

public function getImageComplete(Request $request) {
    $data = $request->session()->get('data');
    $temp_path = $data['temp_path'];
    $read_temp_path = $data['read_temp_path'];

    $filename = str_replace('public/temp/', '', $temp_path);
    //ファイル名は$temp_pathから"public/temp/"を除いたもの
    $storage_path = 'public/productimage/'.$filename;
    //画像を保存するパスは"public/productimage/xxx.jpeg"

    $request->session()->forget('data');

    Storage::move($temp_path, $storage_path);
    //Storageファサードのmoveメソッドで、第一引数->第二引数へファイルを移動

    $read_path = str_replace('public/', 'storage/', $storage_path);
    //商品一覧画面から画像を読み込むときのパスはstorage/productimage/xxx.jpeg"
    $product_name = $data['product_name'];

    $this->productcontroller->path = $read_path;
    $this->productcontroller->product_name = $product_name;
    $this->productcontroller->save();
    return view('image_complete');
}

無事、画像が保存されました!
後は読み込むところで

<img src="{{ $path }}" width="200" height="130">

とすればOKです!

参考にしたサイト

Laravel学習帳:画像アップロード(基本)
Qiita:Laravel5.6でファイルアップロードの実装と躓いたところまとめ
Qiita:[Laravel] ユーザーのアイコン画像を投稿、表示させる機能の実装したのでメモ(画像の保存場所は?シンボリックリンクって?)

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

vs code insiders のクイックスタートを色々な言語で試してみた。

vs code insidersにIDEの未来の姿を見たので、勉強を兼ねて構築手順をまとめてみました。

クイックスタートの言語毎で若干差異があるようで、かじった事のある言語を試してみました。

vs code insidersそのものの詳しい説明については他のわかりやすい記事を参照してください。
簡単に言うと、今までは開発環境と実際の動く環境は別々で、トラブルが起こりやすい状況でのプログラミングが普通だったんですが、実際の動く環境でプログラミングができる。を提供するのがvs code insidersです。

2019-05-23時点では以下の言語のクイックスタート(以下チュートリアル)があります。
https://code.visualstudio.com/docs/remote/containers

  • node
  • python
  • go
  • java
  • dotnetcore
  • php
  • rust
  • cpp

この中でnode、python、go、phpを今回試していきます。

事前準備

チュートリアルを始める前に、git や vs code insiders&extention(remote development)、docker等をインストールしてください。
dockerはshared driveの設定も忘れずに。

チュートリアル ダウンロード

任意のディレクトリで以下のコマンドを実行

sh
git clone https://github.com/Microsoft/vscode-remote-try-node
git clone https://github.com/Microsoft/vscode-remote-try-python
git clone https://github.com/Microsoft/vscode-remote-try-go
git clone https://github.com/Microsoft/vscode-remote-try-php

チュートリアルの順番について

説明しやすさの観点から go → node → php → python の順番で進めます。

go

  1. vs code insidersを立ち上げて任意のディレクトリにダウンロードしたvscode-remote-try-goを開く
  2. 右下に「Folder contains〜」というポップアップが出るので「Reopen in Container」のボタンを押す
  3. 「Installing Dev Container [details]〜」に切り替わる
  4. しばらく待ち ※進捗状況は[detail]から
  5. 左下の緑の枠が「Dev Container: GO」になったら環境構築終了
  6. メニューの表示→ターミナルを開いて、go run server.goを実行
  7. Server listening on port 9000が出力されればwebサーバ立ち上げ完了
  8. http://localhost:9000/ にアクセス

出力文字を変えたい場合はserver.go"Hello remote world!"を任意の文字列に変更し、ターミナルからCTRL+c で停止した後、再度go run server.goを実行してページをリロードすると反映されていると思います。

node

環境構築はディレクトリvscode-remote-try-nodeを開いた後はgoと途中まで一緒(上記4まで)なので割愛します。
5. 左下の緑の枠が「Dev Container: Node.js Sample」になったら環境構築終了
6. 表示→ターミナルを開いて、node server.jsを実行
7. Running on http://0.0.0.0:3000が出力されればwebサーバ立ち上げ完了
8. http://localhost:3000/ にアクセス

出力文字を変えたい場合は server.js の対象箇所を変更できます。
goと同様に停止後→再実行で反映されます。

PHP

1〜5 まで割愛
6. 表示→ターミナルを開いて、php -S localhost:8000を実行
7. F1ボタンを押してRemote-Containers: Forward Port from Container〜を選択後、「Forward 8000」をクリック
8. http://localhost:8000/ にアクセス

phpはgo、node の手順+ポート転送の手順が必要となります。

出力文字を変えたい場合は index.phpを変更します。
また、変更にサーバの再起動は必要なく、ページのリロードのみで反映されます。

python

1〜5まで割愛
6 以降では、デフォルトの設定では動かなかった&ポート番号(9000)がgoとカブるので以下の変更をします。

.devcontainer/devcontainer.json
"appPort": 9000,

"appPort": 5000,
.vscode/devcontainer.json
"appPort": 9000,

"appPort": 5000,
app.py
#以下をファイル末尾に追加
if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=5000)

表示→ターミナルを開いて、python app.pyを実行

出力文字を変えたい場合は static/index.htmlを変更します。
また、変更にサーバの再起動は必要なく、ページのリロードのみで反映されます。
※goとnodeも別ファイルとしてindex.htmlを読み込むように変更すればサーバ再起動は必要ありません。

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

PHPでXML形式のAPIレスポンスを受け取る際の注意事項

XML形式でレスポンスを返却するAPIに対して、PHP側で連想配列への変換を行う際には、通常

// curlの実行
$url = 'http://www.xxxxxxxxxx.yy.zz/getFruits/';
$curl = curl_init($url);
$xml = curl_exec($curl);

// 受け取ったXMLレスポンスをPHPの連想配列へ変換
$xmlObj = simplexml_load_string($xml);
$json = json_encode($xmlObj);
$response = json_decode($json, true);

このようなフローを踏むことになるが、上記処理を行うとXMLの特性である同名要素を複数持てるという性質に対応した際に問題が発生する。

パターン1:

<?xml version="1.0" encoding="utf-8"?>
<data>
    <fruits>
        <name>apple</name>
        <price>100</price>
    </fruits>
</data>

// PHP変換後
$response = array('fruits' => array('name' => 'apple',
                                    'price' => '100'
                              )
            );

パターン2:

<?xml version="1.0" encoding="utf-8"?>
<data>
    <fruits>
        <name>apple</name>
        <price>100</price>
    </fruits>
    <fruits>
        <name>orange</name>
        <price>80</price>
    </fruits>
</data>

// PHP変換
$response = array('0' => array('fruits' => array('name' => 'apple',
                                                 'price' => '100'
                                           )
                         ),
                  '1' => array('fruits' => array('name' => 'orange',
                                                 'price' => '80'
                                           )
                         )
            );

パターン1のように、同名要素が存在しない場合は、連想配列に変換されるが
パターン2のように、同名要素が複数存在する場合は、配列が作られた上でその配下に格納されてしまい、参照の仕方が変わってきてしまう。

したがって自分は以下のような対応を行い、データが1種類しか返却されないパターンでも必ず配列になるように対応をした

function addNumberToArray($arr)
{
    // 連想配列の時は配列へ変更
    if (!empty($arr) && array_values($arr) !== $arr) {
        $arr = array($arr);
    }

    return $arr;
}

$fruits = addNumberToArray($response['fruits']);

foreach ($fruits as $fruit) {
    echo $fruit['name'] . 'は' . $fruit['price'] . '円です';
}

こうすることで同名要素が一つの場合でも複数の場合でも同様の対応を行うことができた

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

PHPで非同期+プロセス管理? PHP-FPMより15倍速いというPHP-PMというモノを知った(PHP-fpmじゃないよ!)

An example of how PHP Process Manager helps handling requests
ref: https://tsh.io/blog/php-pm-guide-getting-started-with-the-process-manager/

先日、色々調べ物をしていたら、ReactPHPという変なのを見つけました。
https://reactphp.org/

ここで言うReactはJavascriptのReact.jsじゃないです。Reactパターン?スタイル?のReactです。(ReactJSも元はPHPのHTMLコンポーネントライブラリのXHPがベースらしいんですが https://qiita.com/ytake/items/2ee2c0e10c6ef6f040fa
PHPで非同期でマルチプロセスでってやつです。驚いたことにPHPで書かれています。
正直、「コレPHPで書くか?」って思ったんですが、面白そうだったので色々調べたらPHP-PMというものを見つけました。

PPM - PHP Process Manager

https://github.com/php-pm/php-pm
PPMと略すみたいです。
ReactPHPを使って、SymfonyやLaravelが動くPHPのmiddleware(PSR-15)として実装されている感じです。SymfonyのHttpKernelのAdapterとして動作するとのことです。なので、nginxはPPMのプロキシとして動作します。
PPMは実行されると8080番ポートとかでずっとListenし続け、PPMがPHPを子プロセスとしてcliで動かしてプロセス管理をするとの事で、利用するphp.iniはcli用の設定になります。

なので、利用するためにはPCNTLが必要であり、configureオプション--enable-pcntlが必要になります。
https://www.php.net/manual/ja/book.pcntl.php

Nginx + PHP-fpmの15倍速いらしい

PHP Process Manager in faster than Apache and FMP

メモリリークは健在

公式ページでは、主な課題に「メモリリーク!メモリリーク!メモリリーク! まあ、プロセス再起動されるからきっと大丈夫だよ」的な事が書いてあり、引用元の記事でも

PHP-PM could cause big memory leaks (there’s probably some issue with restarting workers); it consumed all of my memory and crashed my PC; it happened twice,

と「メモリリーク凄くてPCクラッシュしたわ」的な事が書いてあるので、ここは注意が必要そうです。

まとめ

正直、「えー!?本当に速くなんの?」という疑念が振り払えないし、PHPでプロセス制御するってのに感覚的な恐怖を覚えます。
メモリリークもPCクラッシュするほどって話だし、まだまだ発展途上なのかなぁという感じです。

が、きちんと検証せずに感情だけで判断してはダメなので、近いうちに色々試してみようと思います。

ちなみに、PHPで非同期処理ってのだとSwooleってのがあり、こっちはC++で書かれていてPECLモジュールにもなっているので、感覚的にはこっちの方が安心できる。
https://qiita.com/gorogoroyasu/items/44119f8b2ba725859018

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

Dockerでlaravelの環境構築

Dockerを使用したlaravel環境構築

laradockを使用することで簡単にlaravelの環境構築をすることができるので、以下に手順をまとめる。

開発環境

MacOS Mojave
既にDocker GitはMacに導入済として下記記述します。

laradockのダウンロード

Gitを使用して、任意のフォルダにlaradockをダウンロードします。

git clone https://github.com/Laradock/laradock.git
// laradockへ移動
cd laradock
// 公式ページの記載通り、下記コマンドを入力
cp env-example .env

Laradock公式サイト

Mysqlのバージョンを指定(現行最新版の8だと、通常とは違う認証になり、詰まる可能性大。指定がなければ、5.7を使用したほうが無難)

// .envを開き、"MYSQL_VERSION=latest" を以下のように変更
MYSQL_VERSION=5.7

コンテナの作成

開発に必要なプログラムを指定し、コンテナを作成。なお、プログラムを指定しないとすべてのコンテナを作成しようとするため、時間がかかります。
通常のlaravel開発であれば、下記コマンドの指定で問題ないかと思います。

docker-compose up -d nginx mysql workspace phpmyadmin

終了後、http://localhostにアクセスすると、404 Not Foundが表示されます。
これにより、Nginxに無事アクセスできたことが確認できます。

laravelアプリの作成

コンテナのworkspace内にて、laravelアプリを作成します。

// docker ps にて、workspaceのコンテナID確認後、下記コマンドでコンテナ内に入る
docker-compose exec -it workspaceのコンテナID bash
// コンテナに入り、"/var/www$"にて、下記コマンドを実行。バージョンは5.5以外でも変更できます。
composer create-project laravel/laravel sample --prefer-dist "5.5.*"

これによって、sampleというアプリファイルが作成されます。
次に、laradockディレクトリの.envに、このアプリを作成したことを伝えるため、下記のように記述します。

### Paths #################################################

# Point to the path of your applications code on your host
APP_CODE_PATH_HOST=../sample

設定を反映させるため、dockerを再起動させます。

// 停止
docker-compose stop
// 再起動。以後は、nginxとmysqlのみを指定すればよい
docker-compose up -d nginx mysql

再度ブラウザでhttp://localhostにアクセスするとlaravelの画面が表示されます。

MySQLとの接続

MySQLコンテナと、laravelのDB設定を合わせます。
sampleフォルダ内の.envファイルを開き、下記のように記述。
この内容はlaradockフォルダの.env内のMySQLの記述と合わせたものになります。

// laravelアプリ内の.envファイル
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=default
DB_USERNAME=default
DB_PASSWORD=secret

参考:laradockフォルダ.envのMySQL記述内容

### MYSQL #################################################

MYSQL_VERSION=5.7
MYSQL_DATABASE=default
MYSQL_USER=default
MYSQL_PASSWORD=secret
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root
MYSQL_ENTRYPOINT_INITDB=./mysql/docker-entrypoint-initdb.d

再度、workspaceコンテナに入り、migrateを実行

php artisan migrate

MySQLコンテナに入り、テーブルが確認できれば完了です。

mysql -u default -p -h 127.0.0.1
show tables from default

参照

Laravel & Docker 環境構築 with Laradock
Laradockを使ったLaravel開発環境構築のやさしい解説
Laradock公式サイト

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

いまさらLaravel4 Tips:Artisan::addしたくない

概要

commands クラス作成する度に、app/start/artisan.php に Artisan::add 追加するのめんどくさいな~と思って、commands ディレクトリ配下のphpファイル見て自動で追加することにしました。

もっとちゃんと考えて書けば namespace とかも ちゃんと取り よいか書き方あるんだと思いますが 取り急ぎで必要だったのでご勘弁ください。

artisan.php
<?php

$command_files = File::glob(app_path().'/commands/*.php');
if ($command_files !== false) {
    foreach($command_files as $file){
      $fp = fopen($file, 'r');
      $class = $buffer = '';
      $i = 0;
      while (!$class) {
          if (feof($fp)) break;
          $buffer .= fread($fp, 512);
          if (preg_match('/class\s+(\w+)(.*)?\{/', $buffer, $matches)) {
              $class = $matches[1];
              break;
          }
      }
      Artisan::add(new $class); 
    }
}

namespace とか取らないなら phpファイルの中身見なくてもファイル名でいいじゃんとか今更思ってますが...。

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

php-master-changes 2019-05-22

今日は opcache 最適化のバグ修正、curl_multi_select()で 1 秒未満のタイムアウト指定が 0 になっていた問題の修正、unixODBC の検出の pkg-config への移行、spl で arg_info がドキュメントと乖離していた部分があったのの修正、テストの修正、mysqli.allow_local_infile が off の際に LOAD DATA LOCAL INFILE しようとすると出るエラーメッセージの修正があった!

2019-05-22

nikic: Fix null check in sccp add_array_elem chain handling

nikic: Don't evaluate functions with partial array arguments

rlerdorf: Fix precedence issue causing sub-second timeouts to be 0 in

hughmcmaster: Use PKG_CHECK_MODULES to detect unixODBC

kukulich: Fixed some arg infos to match documentation

nikic: Fix bug #78034

dstogov: .text segment may be not the first one

nikic: Use a different URL in bug44811.phpt

nikic: Mark bug44811.phpt as an online test

cmb69: Fix test case for cURL 7.65.0

SjonHortensius: Fix #77956 - When mysqli.allow_local_infile = Off, return a client error

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

PHPの標準関数を使ってユニーク(一意)なトークン(文字列)を生成する

PHPの標準関数のみでユニークなトークンを作りたかったので、メモとして残します。

目的

メールアドレス認証時に使用するアクティベーションキー用のトークンを発行したく、
実行する度に被らないランダムな文字列を取得するのが目的です。

TL;DR

uniqid(bin2hex(random_bytes(1)))

環境

PHP7系

乱数に使えそうな関数を探す

uniqid

uniqid ([ string $prefix = "" [, bool $more_entropy = FALSE ]] ) : string

マイクロ秒単位の現在時刻にもとづいた、接頭辞つきの一意な ID を取得します。
この関数は、戻り値の一意性を保証するものではありません。

uniqid() // 5ce3d9f085632
uniqid() // 5ce3d9f154c7a
uniqid() // 5ce3d9f25823a

13桁の16進数の文字列が返ってくるようです。
現在時刻に基づいてるので実行するたびにカウントアップされていくようです。

uniqid には第一引数で接頭辞を付けられます。

uniqid('ucan-') // ucan-5ce3dabb38549
uniqid('ucan-') // ucan-5ce3dabbe1c38
uniqid('ucan-') // ucan-5ce3dabc5407c

接頭辞を付けられるのは便利だったのですが、
固定文字列を付けてしまうと結局一意性は保証されません。

dechex

10 進数を 16 進数に変換する

dechex ( int $number ) : string

$number で指定した符号なし整数値を 16 進数表現した文字列を返します。

dechex(0) // 0
dechex(100) // 64
dechex(255) // ff

random_int

暗号学的に安全な疑似乱数整数を生成する。

random_int ( int $min , int $max ) : int

$min$max の間で偏りのない結果が重要な場合に使用するのに適した暗号化された乱数整数を生成します。

random_int(0, 255) // 4
random_int(0, 255) // 158
random_int(0, 255) // 184

乱数を生成する

16進数2桁(0 〜 ff)の乱数を生成して接頭辞を付ける。

uniqid(dechex(random_int(0, 255))) // 865ce3dddb0e35b
uniqid(dechex(random_int(0, 255))) // 85ce3dddb13e1d
uniqid(dechex(random_int(0, 255))) // b25ce3dddb198dc

乱数を生成する(固定桁数)

もし桁数を固定したい場合は sprintf を使って0埋めすると良さそうかな?

uniqid(sprintf('%02x', random_int(0, 255))) // 4e5ce3dd8d7e2de
uniqid(sprintf('%02x', random_int(0, 255))) // 0a5ce3dd8d83477
uniqid(sprintf('%02x', random_int(0, 255))) // 4f5ce3dd8d8925d

2019.05.23 追記

@suin さんから random_bytes もオススメのコメントいただきました!

random-bytes

random_bytes - 暗号学的に安全な疑似乱数バイトを生成する

random_bytes ( int $length ) : string

salt、鍵、または初期化ベクトルを生成するときなど、暗号用途に適した任意の長さの暗号ランダムバイトの文字列を生成します。

random_bytes(1) とすると1byteのバイナリ文字列が返ります。
バイナリ文字列から16進数に bin2hex 関数で変換すれば 00ff を取得できます。

uniqid(bin2hex(random_bytes(1))) // 085ce642e0803c3
uniqid(bin2hex(random_bytes(1))) // 555ce642e2cb9fa
uniqid(bin2hex(random_bytes(1))) // eb5ce642e463215

2019.05.24 追記

@go12lim さんから uniqid の第二引数使うとシンプルに書けるとアドバイスいただきました?‍♂️

uniqid('', true) // "5ce7b766c4b586.26307700"
uniqid('', true) // "5ce7b767d85ab4.09025400"
uniqid('', true) // "5ce7b7687dd943.84230183"

これが一番シンプルで良さそうです!
ドット付きが気にならなければこれで良さそう!

参考

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

LaravelでRequestのプロパティを加工する方法いろいろ

Requestのプロパティを加工したい

「Modelのミューテータを使えばいいじゃん!」
て言われるかもしれないけどまぁRequestの状態でとりあえず色々やりたいみたいな、ね。
ありますよね。

プロパティをなかったことにしたい

例えば$request->divisionというプロパティをなかったことにしたい場合はこう。

$request->offsetUnset('division');

フィールド名(文字列)をぶち込めば消えてくれます。
ちなみにunsetしてもだめだったのでこれ一択かと。(というかオブジェクトのプロパティ消すのにunsetは使えないのか?)

プロパティを上書きしたい

フィールド名をKeyに持つ連想配列で上書きできます。
というか存在しないフィールド名を入れると新たなプロパティが作れちゃいます。

$request->merge([
    'start_time' => $start.':'.'00',
    'end_time' => $end.':'.'00',
]);

replaceなんていうパラメータを一回空にしてからmergeするメソッドもあるみたいですね。(要検証)

おわりに

Requestクラスの中身のぞいてたんですけどまだまだワクワクするようなメソッドいっぱいありましたので機会があったら随時更新していきます。
というかRequestをこうやって加工しまくるの設計的にはどうなんですかね。。

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

php 5.6 から 7.2 に上げ phpunit を動かす

php7.2, zend で phpunit

php 5.6 の時に動いていた phpunit
php 7.2 したらエラーが…(´;◞౪◟; )
https://github.com/sebastianbergmann/comparator/pull/30/commits/f4ec922b3e514ae27d2c9693bc68222a93ceab34

rlerdorf committed on 29 Jan 2017

(7.1 で ArgumentCountErrorの追加があったけどこれは違いそうな…↓だよなぁ…7.2 関係あったのか...?)
http://www.objective-php.net/basic/interface

php 7.2 に互換性がある一番低いものかつ
修正入ってそうなので
phpunit 6 に

Support ended on February 1, 2019

https://phpunit.de/supported-versions.html

phpunit 6.0 から名前空間化されていて zend 死亡(古すぎぃ…
https://qiita.com/mt-kage/items/7630484c571680df1140

phpunit 5 ならいけるはず (7.2 互換性ないけど...)
修正も入ってるはず

Support ended on February 2, 2018

https://phpunit.de/supported-versions.html

うっうごいた…( ◜௰◝ )

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