20190730のPHPに関する記事は17件です。

Atom利用時にのみ反映される環境変数

経緯

パスを通す事ができないけどPHPは手元にあるって状況でいろいろ探してたところ、Atomの環境変数にPHPを突っ込む対応ができたのでメモ。

他にも同じようなことをしているものはあったけど、ローカルのパスを指定して追加するって対応がなかなか見つからなかった、数日前の自分が見つけられるようになれば良いんだけど。

対応手順

init.coffee を開いて下部の内容を追加するとパスに追加が可能。

init.coffeeの開き方は「file→init script」

※japanese-menuで日本語化している場合、「ファイル→起動スクリプト」で開けます

process.env.PATH = [process.env.PATH,"C:\\xampp\\php",].join(";")
  • \(バックスラッシュ)はエスケープされるので2つ繋げて\\にする。
  • 追記後はリロードすること。「Ctrl+Shif+P → window: reload」

補足

Atom上でCtrl+Shift+Iで開発ツールを出して、コンソール上にprocess.env.PATHと打ち込むと現状の通っているパスリストが見れる。

上記対応後に打ち込んで、結果が望んでいた通り追加されていれば完了。

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

計算量O(n)の革命的なソートアルゴリズムをPHPでも

計算量O(n)の画期的なソートアルゴリズムであるスターリンソートをHaskell で実装してみた #Haskell - Qiita

スターリンソートは当然、PHPでも簡単に実装することができます。 https://3v4l.org/g9bR2

<?php

namespace stalin;

function sort(array $xs): array
{
    if (count($xs) <= 1) {
        return $xs;
    }

    $zs = $xs;
    $x = array_shift($zs);
    $y = array_shift($zs);

    if ($x <= $y) {
        return [$x, ...sort([$y, ...$zs])];
    }

    return sort([$x, ...$zs]);
}

動かしてみます。

echo json_encode(\stalin\sort([1, 2, 1, 1, 4, 3, 9]));
// [1,2,4,9]

完璧ですね。 PHP 7.3? そんな古いバージョンのことは知りません

参考

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

「ようこそ・・・『テストの世界』へ・・・」

はじめに

本記事では、テストをまだ書いたことのない人向けに

「今日からテストを書いてみようかな」

と思ってもらえるよう、チュートリアル風に簡単なテストの流れを説明します。

題材はLaravelですが、他のフレームワークでも同じようなことはできると思います。

前提

本記事の対象者

  • Laravel初心者で、
    • テストをまだ書いたことの無い人
    • テストで何ができるのか知らない人
    • テストに興味はあるが忙しくてどう書けば良いのかまだ調べられていない人

本記事で取り扱うこと

  • ごく簡単なHTTPリクエストのテストの書き方

本記事で取り扱わないこと

  • テストの全般的な話(利点、注意点など)
  • テストケースの作り方
  • CIツールによるテスト自動化

環境

  • Laravel 5.8.x

簡単な画面(HTTPレスポンス)のテスト

まずは、Laravelを触ったことのある人なら一度は見たことのある?Welcome画面をテストしてみます。

これが正常に表示されることを、目視ではなくテストコードで確認してみましょう。

localhost_8080_.png

Laravelをインストールすると、既にtests/Feature/ExampleTest.phpという、テストコードのサンプルが存在します。

tests/Feature/ExampleTest.php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testBasicTest()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

このExampleTestは、Tests\TestCaseを継承しており、テストに関する様々なメソッドが使えます。

$response = $this->get('/')で、'/'にアクセスした(GETリクエストした)結果のレスポンス1$responseに代入されます。
そして、$response->assertStatus(200)で、そのレスポンスが正常であること(ステータスコードが200 OKであること)をチェックしています。

ステータスコードについては以下を参考にしてください。

このExampleTestを実行するには、Laravelのルートフォルダで以下コマンドを実行してください。

$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php

すると、以下の結果が表示されました。

PHPUnit 7.5.12 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 448 ms, Memory: 14.00 MB

OK (1 test, 1 assertion)

OK、と表示されています。

つまり、'/'へアクセスした(GETリクエストした)結果、正常にレスポンスが返ってきた(HTTPレスポンスステータスコードが200 OKだった)ということです。

これをテストコードで確認することができました。

ビューのテスト

ただ、このテストでわかったのは、正常にレスポンスが返ってきた、ということまでです。

Welcome画面が表示されたのかどうかまでテストできていません。

現状のルーティングを見ると、'/'へアクセスすると、'welcome'というビューが表示されることがわかります。

routes/web.php
Route::get('/', function () {
    return view('welcome');
});

welcomeというビューは、具体的にはresources/views/welcome.blade.phpというビューのテンプレートです。

このresources/views/welcome.blade.phpこそがあのWelcome画面なので、このテンプレートが使われているかどうかをテストで確認してみます。

tests/Feature/ExampleTest.phpに以下のコードを追加します。

tests/Feature/ExampleTest.php
public function testBasicTest()
{
    $response = $this->get('/');

    $response->assertStatus(200)
        ->assertViewIs('welcome'); // 追加
}

このようにassertViewIsメソッドで、どんなビューが使われたのかをテストすることができます。

修正後のExampleTestを実行してみます。

$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php
// 略
OK (1 test, 2 assertions)

こちらも結果はOKとなりました。

あのWelcome画面が表示されていることを、テストコードで確認することができました。

さらにassertSeeメソッドを使うと、表示されている2文字列もテストできます。
Welcome画面にLaravelの文字が表示されていることを、テストコードで確認してみましょう。

tests/Feature/ExampleTest.phpに以下のコードを追加します。

tests/Feature/ExampleTest.php
public function testBasicTest()
{
    $response = $this->get('/');

    $response->assertStatus(200)
        ->assertViewIs('welcome')
        ->assertSee('Laravel'); // 追加
}

修正後のExampleTestを実行してみます。

$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php
// 略
OK (1 test, 3 assertions)

こちらも結果はOKとなりました!

ログイン中かどうかを絡めたテスト

ここから先は、ユーザーがログイン中かそうでないかによって結果が異なる画面をテストしていきます。

Laravelでは以下コマンドでユーザー登録画面やログイン画面等を作成できるので、これを実行します。

$ php artisan make:auth

このコマンドの詳細については、以下の記事を参考にしてください。

Welcome画面にユーザー登録画面とログイン画面へのリンクが追加されました。

localhost_8080_2.png

以下、テストではなく人力でユーザー登録画面にアクセスし、ユーザー登録を行なっています。

localhost_8080_register.png

ユーザー登録が完了すると、以下のホーム画面へ遷移します。なお、ログイン画面からログインした場合も、このホーム画面へ遷移します。

localhost_8080_home.png

ホーム画面は、ログイン中の時のみ表示されます。

ログインしていない状態で直接/homeへアクセスすると、ホーム画面は表示されず、ログイン画面へリダイレクトされます。

この「ホーム画面は、ログイン中の時のみ表示される」ということをテストコードで確認してみましょう。

ホーム画面の表示は、php artisan make:authコマンドによって作成された、app\Http\Controlles\HomeControllerで行われています。

このHomeControllerをテストするためのHomeControllerTestを作成します。

以下コマンドを実行すると、テストの雛形を作成できます。

$ php artisan make:test HomeControllerTest

作成されたテストの雛形の内容は、以下になります。

tests/Feature/HomeControllerTest.php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class HomeControllerTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testExample()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

このtests/Feature/HomeControllerTest.phpを以下の通り編集します。

tests/Feature/HomeControllerTest.php
public function testExample()
{
    $response = $this->get('/home'); // 変更(ホーム画面のパスに変更)

    $response->assertStatus(200)
        ->assertViewIs('home') // 追加(ここでの'home'は、ホーム画面で使われているビュー名)
        ->assertSee('You are logged in!'); // 追加(ホーム画面で表示されているメッセージ)
}

このHomeControllerTestを、以下コマンドで実行します。

$ ./vendor/bin/phpunit ./tests/Feature/HomeControllerTest.php

すると、以下の通り、OKではなくFAILURES!となりました。

PHPUnit 7.5.12 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 442 ms, Memory: 14.00 MB

There was 1 failure:

1) Tests\Feature\HomeControllerTest::testExample
Expected status code 200 but received 302.
Failed asserting that false is true.

/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:133
/var/www/tests/Feature/HomeControllerTest.php:20

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Expected status code 200 but received 302.と出力されています。

ログイン中でない状態でアクセスしたので、リダイレクトを示す302のステータスコードが返ってきた、ということです。

そこで、ログインした状態を作り出してテストしてみます。

DBには、先ほど人力でユーザー登録画面にアクセスして登録したユーザーデータが存在するので、テストではこのユーザーを使うことにします。

# select id, name from users;
 id |        name        
----+--------------------
  1 | shonansurvivors
(1 rows)

tests/Feature/HomeControllerTest.phpを以下の通り編集します。

tests/Feature/HomeControllerTest.php
<?php

namespace Tests\Feature;

use App\User; // 追加
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class HomeControllerTest extends TestCase
{
    public function testExample() 
    {
        $response = $this
            ->actingAs(User::find(1)) // 追加
            ->get(route('home'));

        $response->assertStatus(200)
            ->assertViewIs('home')
            ->assertSee('You are logged in!');
    }
}

actingAsメソッドで、そのユーザーとしてログイン済の状態になります。

また、User::find(1)で、DBのusersテーブルからid1であるユーザーを取ってきています。

改めてHomeControllerTestを、以下コマンドで実行します。

$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php

すると、OKとなりました。

OK (1 test, 3 assertions)

ログイン中の状態で/homeへアクセスすると、

  • ステータスコードが200となること
  • homeというビューが使われていること
  • You are logged in!が、html中に存在すること

が確認できました。

このようにログイン中かどうかが絡む機能も、テストコードで確認できます。

テストに必要なDBのデータを自動で準備する

今回はたまたま開発環境のDBにユーザーのデータが存在し、テストではこれを利用してログインを行いましたが、 テストに必要なDBのデータ準備もコードで自動化してみます。

Laravelではテスト用のDBのデータを作るのに、ファクトリというものを使う方法があります。

database/factories/UserFactory.phpが、ユーザーデータを作るためのファクトリです。

database/factories/UserFactory.php
<?php

use App\User;
use Illuminate\Support\Str;
use Faker\Generator as Faker;

$factory->define(User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // password

        'remember_token' => Str::random(10),
    ];
});

このファクトリの詳細については本記事では触れませんが、これを使うといい感じにダミーのデータを作ってくれます。

ファクトリについてもっと知りたい方は、以下記事を参考にしてください。

このファクトリを使うよう、tests/Feature/HomeControllerTest.phpを以下の通り変更します。

tests/Feature/HomeControllerTest.php
<?php

namespace Tests\Feature;

use App\User; // 追加
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class HomeControllerTest extends TestCase
{
    public function testExample()
    {
        $user = factory(User::class)->create(); // 変更(ファクトリでユーザーデータを作成)

        $response = $this
            ->actingAs($user) // 変更(ファクトリで作ったユーザーデータでログイン中状態を作る)
            ->get(route('home'));

        $response->assertStatus(200)
            ->assertViewIs('home')
            ->assertSee('You are logged in!');
    }
}

ファクトリで作成されたユーザーにて、ログイン中の状態を作っています。

以下コマンドでHomeControllerTestを実行すると、結果はOKとなりました。

$ ./vendor/bin/phpunit ./tests/Feature/HomeControllerTest.php
// 略
OK (1 test, 3 assertions)

ただ、これだとHomeControllerTest実行のたびにDBにダミーのユーザーデータが1件また1件...と作られてしまいます。

sample=# select id, name from users;
 id |        name        
----+--------------------
  1 | shonansurvivors
  2 | Dr. Jayce Wiegand
  3 | Miss Shayna Deckow
(3 rows)

2件目と3件目がファクトリで作られたダミーのユーザーデータです。

これに対しては、ひとつのやり方として、テスト用のDatabaseTransactionsを使うという方法があります。

これを使うと、テストの実行後にDBをロールバックし、テスト実行中にDBに作ったデータが残らなくなります。

tests/Feature/HomeControllerTest.phpで、DatabaseTransactionsを使うようにします。

tests/Feature/HomeControllerTest.php
<?php

namespace Tests\Feature;

use App\User;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseTransactions; // 追加
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class HomeControllerTest extends TestCase
{
    use DatabaseTransactions; // 追加

    public function testExample()
    {
        // 以下変更点なしにつき省略

以下コマンドでHomeControllerTestを実行すると、結果は引き続きOKとなりますが・・・

$ ./vendor/bin/phpunit ./tests/Feature/HomeControllerTest.php
// 略
OK (1 test, 3 assertions)

テスト終了後のDBを見ると、ユーザーデータはテスト開始前時点から増えてはいません(3件のまま)でした。

sample=# select id, name from users;
 id |        name        
----+--------------------
  1 | shonansurvivors
  2 | Dr. Jayce Wiegand
  3 | Miss Shayna Deckow
(3 rows)

これでDBにダミーデータが残ることなく、何度でもテストを実行できます。

最後に

以上で、本記事のテスト体験ツアーは終了です。

テストでどのようなことができるか、イメージはつかめたでしょうか。

本記事をきっかけに、テストを書き始めていただければ幸いです。

「納期は守る」「品質も確保する」
「両方」やらなくちゃあならないってのが「エンジニア」のつらいところだな

参考


  1. 正確には\Illuminate\Foundation\Testing\TestResponse 

  2. 正確にはhtml内にその文字列が含まれているかをテストします。htmlのタグなども確認の対象になります。 

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

AWS上でphpをrpmでインストールする時のrequireメッセージに注意

Error: Package: php-mysql-5.3.3-49.el6.x86_64 (/php-mysql-5.3.3-49.el6.x86_64)
           Requires: php-common(x86-64) = 5.3.3-49.el6
           Removing: php-common-5.3.3-49.el6.x86_64 (installed)

とか言ってるくせに、本当にいるのはphp-pdoと言うこの詐欺メッセージ。だって、既にこのcommonは入れてるから。

php-pdoとは

php-data-objectのことで、データベースの文字列をphpの扱うオブジェクトデータに変換するものです。これを引っ張ってきてください。

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

Docker学習メモ

Docker学習メモ

>>> 編集中

はじめに

自分でアプリケーションを開発していく上で、開発環境のWindowsと本番環境のLinuxで差分が出てしまうのを
解決したかったので、Dockerを実際に使ってみました

それに伴ってDockerの概要や使ってみた感想などをまとめてみました

Dockerについて

まず、Dockerとは仮想環境を提供するソフトウェアの事を指します
コンテナと呼ばれるアプリケーションを依存対象と共にカプセル化することが出来ます

コンテナは隔離されたOSを持ち、その上でアプリケーションを稼働させることが出来ます
またホストOSとリソースを共有出来ることが特徴です

Dockerを利用するうえで、イメージとコンテナの概念は重要で、下記リンクに大まかな概要が掲載されています

公式, Dockerアーキテクチャ, コンテナについて

Dockerの利点

  • フレームワークやミドルウェアを自分で構築する必要がない

例えばLaravelを利用した開発を行う場合に、ホストOSにPHPをインストールしなくても
Bitnamiが提供するLaravelのイメージがDocker Hubと呼ばれるリポジトリに配布されているため
自分でPHP、Laravelインストールをしなくても簡単に環境を構築できます

また新しいフレームワークや技術が登場しても、Docker Hubでイメージが配布されていれば、自分で環境構築をする必要はありません

  • 複数の開発環境の統一できる

DockerfileにOSやパッケージのバージョンを指定することが出来るため
開発環境ではUbuntuRHELなどのOSやRubyNodejsPHPGoなどの言語のバージョンを統一できます

またaptpipなどのパッケージマネージャ以外のライブラリをインストールする場合、自分でmsiをダウンロードしたり
install.shなどを作成することもあると思うのですが、Dockerの場合それは必要ありません

僕の認識ではseleniumは、ChromeDriverを手でインストールする必要がありますが
コンテナの場合、その作業は必要なく、ホストの\\Program Files/opt/varなどの汚染を気にすることもありません

  • 開発環境と本番環境の差分を埋めることが出来る

アプリケーションやミドルウェアではDocker Engine上で稼働するため
開発環境、本番環境のホストOS上でDocker Engineプロセスさえ起動していれば、アプリケーションは差分なく稼働することが出来ます

  • VMと比べて早い

リンクを参照すると早いかもしれません
ゲストOSがない分、コンテナの起動が早くキャッシュを利用したコンテナの再ビルドはとても高速です
vagrant upに比べて体感速度レベルで起動速度が違います

Dockerを理解する

まずDockerのアーキテクチャについて下記のリンクを参照してください

Dockerアーキテクチャ

簡単にコンテナでコマンドを実行する流れを紹介します
例えばCent OSコンテナでcat /etc/system-releaseコマンドを実行する場合は下記の手順を踏むことで行えます

  1. docker pull centosコマンドで、コンテナのひな形となるイメージファイルを取得
  2. docker run --name mycontainer centosコマンドで、イメージからコンテナを生成・起動・ログインを行う
  3. コンテナログイン後、cat /etc/system-releaseコマンドでOS情報を取得する // CentOSと表示されるはず
  4. exitコマンドでコンテナから抜ける

補足ですが、docker runの引数に指定されたimageがローカル環境に存在しない場合、Docker Hubから取得できるため
上記の作業はワンライナーで行えます

docker run -it centos cat /etc/system-release

上記の他にも、コンテナの停止、削除、コンテナのイメージ化などがあります

詳細などは下記のdockerチュートリアルなどを参照してください

Dockerコンテナの作成、起動〜停止まで // 再度掲載
サブコマンドリファレンス

Docker + Laravel

Docker + Nginx + MySQL

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

PHP で 配列の値を順番までランダムにして取得する関数

PHP で普通に配列から値を取得しようとすると array_rand() で取得したキーを元の配列に入れる微妙な面倒さがあり、ついでにこれだけだと順番がランダムになっていなくて、それもまた微妙に面倒。

ということで配列と取り出す数を指定してサクッとランダムに取り出せる関数を書いた

function array_rand_and_shuffle(array $array, int $num_req)
    {
        shuffle($array);
        // 配列の要素数が取得数に満たない場合はシャッフルだけする
        if(count($array) <= $num_req) return $array;

        $random_keys = array_rand($array, $num_req);
        $rand_array = [];
        foreach($random_keys as $key){
            $rand_array[] = $array[$key];
        }
        return $rand_array;
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ibm cloudにdeployしていたappが動かなくなったので対応した

いつの間にか、ibm cloudで動かしてたappが動かなくなってたので、その時やったこと。

A service instance was not found in this resource group and region us-south

このエラーは正直良くわからないけど、なんか適当にツールを追加したらいつの間にか、過去ツールが動くようになっていて、追加したツールを削除した。しばらくログインしてなかったからだと思う。

ibm cloudは、Web UIがコロコロ変更されるので、正直、よくわからん状態。

  1. Pipeline | Delivery PipelineDeploy Stageの構成をpipline img(latest:2.3)にする

IMPORTANT: The Continuous Delivery Pipeline is being updated to pull a specific Docker image to run each job, either one of the built-in ones or your own custom image. This job is being run with the oldest image. You should consider moving to a more recent one, to get more recent versions of the included tools.

バージョンが古かったらしいので、最新にしてみた。

  1. memory不足を言われるので、manifest.ymlで指定してcf pushする

Error restarting application: Server error, status code: 400, error code: 100005, message: You have exceeded your organization's memory limit: app requested more memory than available

WebのGUIからの操作で何故かメモリが足りないエラーが出て、デプロイできない。CUIからmanifest.ymlでmemory:256MBを指定して、pushしたらデプロイ成功した。

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

ibm cloudにdeployしていたappが動かなくなっていたので対応した

いつの間にか、ibm cloudで動かしてたappが動かなくなってたので、その時やったこと。

A service instance was not found in this resource group and region us-south

このエラーは正直良くわからないけど、なんか適当にツールを追加したらいつの間にか、過去ツールが動くようになっていて、追加したツールを削除した。しばらくログインしてなかったからだと思う。

ibm cloudは、Web UIがコロコロ変更されるので、正直、よくわからん状態。

  1. Pipeline | Delivery PipelineDeploy Stageの構成をpipline img(latest:2.3)にする

IMPORTANT: The Continuous Delivery Pipeline is being updated to pull a specific Docker image to run each job, either one of the built-in ones or your own custom image. This job is being run with the oldest image. You should consider moving to a more recent one, to get more recent versions of the included tools.

バージョンが古かったらしいので、最新にしてみた。

  1. memory不足を言われるので、manifest.ymlで指定してcf pushする

Error restarting application: Server error, status code: 400, error code: 100005, message: You have exceeded your organization's memory limit: app requested more memory than available

WebのGUIからの操作で何故かメモリが足りないエラーが出て、デプロイできない。CUIからmanifest.ymlでmemory:256MBを指定して、pushしたらデプロイ成功した。

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

【PHPUnit】phpunit.xmlを使ってグローバル変数を設定したい

PHPUnitを使ってテストコードを書くとき、
phpunit.xml を使って\$_POST, \$_GET, $_ENV (その他いろいろ)などが設定できる。

ディレクトリ構成
 App/
   src/
      Environment.php
   tests/
      EnvironmentTest.php
   vendor/
   composer.json
   phpunit.xml
phpunit.xml
<phpunit>
    <php>
        <env name="ENVIRONMENT" value="localhost" force="true"/>
    </php>
</phpunit>
Environment.php
<?php

class Environment
{
    public static function getEnvironment()
    {
        return \getenv('ENVIRONMENT');
    }
}

EnvironmentTest.php
<?php
require_once __DIR__.'/../src/Environment.php';

use PHPUnit\Framework\TestCase;

class EnvironmentTest extends TestCase
{
    /**
     * @test
     */
    public function xmlで環境変数が設定できている(): void
    {
        $this->assertSame('localhost', Environment::getEnvironment());       
    }
}

phpunit.xmlはカレントディレクトリにないとデフォルトでは読み込まれない

カレントディレクトリにphpunit.xmlがある場合

C:\App> vendor/bin/phpunit tests\EnvironmentTest.php
PHPUnit 8.0.6 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 813 ms, Memory: 4.00 MB

OK (1 test, 1 assertion)

testsディレクトリに移動して実行

C:\App> cd tests
C:\App\tests> ../vendor/bin/phpunit EnvironmentTest.php
PHPUnit 8.0.6 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 1.66 seconds, Memory: 4.00 MB

There was 1 failure:

1) EnvironmentTest::xmlで環境変数が設定できている
Failed asserting that false is identical to 'localhost'.

C:\App\tests\EnvironmentTest.php:13

カレントディレクトリにないXML設定ファイルを読み込む

https://phpunit.readthedocs.io/ja/latest/textui.html#textui-clioptions

--configuration, -c

設定を XML ファイルから読み込みます。 詳細は XML 設定ファイル を参照ください。

phpunit.xml あるいは phpunit.xml.dist (この順番で使用します) が現在の作業ディレクト>リに存在しており、かつ --configuration が使われていない場合、設定が自動的にそのファイルから読み込まれます。

指定されているのがディレクトリで、 phpunit.xml あるいは phpunit.xml.dist (この順番で使用します) がそのディレクトリ内に存在する場合、設定が自動的にそのファイルから読み込まれます。

C:\App\tests> ../vendor/bin/phpunit --configuration ../phpunit.xml EnvironmentTest.php
PHPUnit 8.0.6 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 796 ms, Memory: 4.00 MB

OK (1 test, 1 assertion)

XML設定ファイルを読み込まない

https://phpunit.readthedocs.io/ja/latest/textui.html#textui-clioptions

--no-configuration

現在の作業ディレクトリにある phpunit.xml および phpunit.xml.dist を無視します。

C:\App\tests> cd ..
C:\App\> vendor/bin/phpunit --no-configuration tests/EnvironmentTest.php
PHPUnit 8.0.6 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 597 ms, Memory: 4.00 MB

There was 1 failure:

1) EnvironmentTest::xmlで環境変数が設定できている
Failed asserting that false is identical to 'localhost'.

C:\App\tests\EnvironmentTest.php:13

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CocでPHP定義元辿ろうとすると PHP Fatal error: Uncaught Error: Class 'MessagePackUnpacker' not found in が起きる。

https://qiita.com/masakuni-ito/items/d7a6c2de47344f0bbd1c の続き。

NeovimでCoc入れて、PHPの定義元移動試してみたら、PHPでエラーが起きたので、それ解決したら違うエラーが起きた。

PHP Fatal error:  Uncaught Error: Class 'MessagePackUnpacker' not found in /Users/ito_masakuni/.cache/dein/repos/github.com/lvht/phpcd.vim/vendor/lvht/msgpack-rpc/src/MsgpackMessenger.php:18
Stack trace:
#0 /Users/ito_masakuni/.cache/dein/repos/github.com/lvht/phpcd.vim/php/main.php(30): Lvht\MsgpackRpc\MsgpackMessenger->__construct(Object(Lvht\MsgpackRpc\StdIo))
#1 {main}
  thrown in /Users/ito_masakuni/.cache/dein/repos/github.com/lvht/phpcd.vim/vendor/lvht/msgpack-rpc/src/MsgpackMessenger.php on line 18

まぁ……たぶん、msgpack、ないんだな……。

入れる。

$ pecl install msgpack
downloading msgpack-2.0.3.tgz ...
Starting to download msgpack-2.0.3.tgz (45,769 bytes)
.............done: 45,769 bytes
20 source files, building
running: phpize
Configuring for:
PHP Api Version:         20180731
Zend Module Api No:      20180731
Zend Extension Api No:   320180731
・
・
・

これでCocの定義元移動ができた。Coc凄まじい。

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

PHP実行すると dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.62.dylib のエラーが起きる。

NeovimでCoc入れて、PHPの定義元移動試してみたら、下記のようなエラー。

dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.62.dylib
  Referenced from: /usr/local/bin/php
  Reason: image not found

最初Cocかnodeかで起きてるのかと思ってicu4cをアップデートも考えたんだけど、phpが参照元って書いてあるので、phpを叩いてみると同じエラーが起きる。

$ /usr/local/bin/php
dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.62.dylib
  Referenced from: /usr/local/bin/php
  Reason: image not found

何だお前、最近使わなかったけど、動いてなかったんだな……。

$ brew upgrade php
php 7.2.12 -> 7.3.7
==> Upgrading php
==> Installing dependencies for php: apr, openssl, argon2, brotli, c-ares, libidn, libmetalink, libssh2, jansson, libev, libevent, nghttp2, openldap, rtmpdump, curl-openssl, freetds, libpng, freetype, pcre, readline, sqlite, python, glib, libpq, libsodium, libzip, libtiff and webp
==> Installing php dependency: apr
・
・
・
$ /usr/local/bin/php --version
PHP 7.3.7 (cli) (built: Jul  5 2019 12:44:05) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.7, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.7, Copyright (c) 1999-2018, by Zend Technologies

PHP新しくしたら このエラーは 起きなくなった。Cocの定義元移動は後編に続く。

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

PHPで生のリクエストボディーを読む

PHPではリクエストからパラメータを取得する場合$_GET$_POSTが用意されていますが、これはContent-Typeapplication/x-www-form-urlencodedの場合の話です。

JSON-RPCのようにContent-Typeapplication/jsonの場合にパラメーターを取得するにはPHPに頼らずリクエストボディーのJSONをPHPに頼らず自力でパースする必要があります。

stream を使う

リクエストボディーを取得するにはWEBサーバとして動かす場合は php://input を使います。

var_dump(file_get_contents('php://input'));

下記を短く書いています。

$stream = fopen('php://input');
$data = stream_get_contents($stream);
fclose($stream);
var_dump($data);

コード

リクエストボディーのJSONをパースしてダンプするスクリプトを test.php としてファイルにします。

test.php
<?php

var_dump(json_decode(file_get_contents('php://input'), true));

動作確認

PHPのビルトインサーバでホストし、curlでtest.phpを呼び出します。

$ php -S localhost:8080 # test.php の存在するディレクトリで実行する
$ curl -X POST \
    -d '{"hello":"world", "こんにちは":"世界"}' \
    -H "Content-Type: application/json" \
    http://localhost:8080/test.php

array(2) {
  ["hello"]=>
  string(5) "world"
  ["こんにちは"]=>
  string(6) "世界"
}

例ではapplication/jsonが送られてくる場合を仮定しましたが、application/x-www-form-urlencodedでも、パースされていない生データを取得することができます。 (もちろんjson_decodeしてはいけません)

ヒント

php://inputphp://stdin の違いがよくわかっていなくて、 php://stdin を使用すると、ブロッキングしてしまってWEBサーバがレスポンスを返さなくなります。CLIだとエンターを押すまでブロッキングするからでしょうかね。

アプリケーションサーバとして使う場合はphp://inputを使いましょう。そしてブロッキングしてるWEBサーバは再起動しましょう。

CLIの場合はSTDIN定数 (STDINはfopen('php://stdin')と等価です) やphp://stdin を使うのだと思います。

$HTTP_RAW_POST_DATA 変数を使う

PHP7 より前は $HTTP_RAW_POST_DATA に生のPOSTデータがセットされていましたが、PHP7以降では使えません。

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

Laravel 5.8 primaryKeyをint(AUTO_INCREMENT)からUUIDに変える

外部ライブラリを使う

webpatser/laravel-uuid

$ composer require webpatser/laravel-uuid

Str::orderedUuidを使う(Laravel 5.6+)

Laravelのヘルパ関数だが外部ライブラリmoontoast/mathが必要

$ composer require moontoast/math

idの型を変える

こちらを参考に
[Laravel] テーブルのプライマリキーにuuidを使う

databases/migrations/XXX_users_table.php
- $table->bigIncrements('id');
+ $table->uuid('id'); // string('id', 36)と同じ
+ $table->primary('id');

モデルを修正

プライマリキーの名称を変える場合は、プロパティ$primaryKeyで設定
型がデフォルトでintかつautoincrementのため、以下の定義を追加

app/User.php
protected $primaryKey = 'hoge_id' // 任意設定
public $incrementing = false;  // AUTO_INCREMENT不可
protected $keyType = 'string'; // int -> string

レコード作成時にidを自動生成する処理を追加する。useを忘れないように。

app/User.php
// webpatserの場合
use Illuminate\Database\Eloquent\Model;
use Webpatser\Uuid\Uuid;
// Str::orderedUuidの場合
use Illuminate\Support\Str;
()
    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
// webpatserの場合
            $model->{$model->getKeyName()} = (string) Uuid::generate()->string;
// Str::orderedUuidの場合
            $model->{$model->getKeyName()} = (string) Str::orderedUuid();
        });
    }

Seeder経由で追加する場合

そのままではINSERTで失敗するのでidも自前で定義する

databases/seeders/UserTableSeeder.php
        DB::table('users')->insert([
+            'id' => (string) Str::orderedUuid(),
            'name' => 'admin',
        ]);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP簡易自作フレームワーク(version1)

自作フレームワーク実装例

非常に適当ですが、自分の頭の整理のために、開発途上を投稿します。
まずは、コントロールとビューを動的に読み込んでみました。

/index.php
<?php
$controller="";
$action="";
$para=array();
if (!empty($_SERVER['PATH_INFO'])) {
    $para=explode('/',$_SERVER['PATH_INFO']);
    $controller=array_shift($para);
    if($controller==""){$controller=array_shift($para);}
    $action=array_shift($para);
}

if($controller=="" || $action==""  || substr($action,0,1)=="_"){
    $fname='./views/default.php';
    if(file_exists($fname)){
        include($fname);
    }else{echo "file [$fname] not found. <br>";}
    exit;
}

$fname='./controllers/'.$controller.'.php';
if(file_exists($fname)){
    include($fname);
    if(class_exists($controller)){
        $class = new $controller();
        if(method_exists($class,$action)){
            $data = $class->$action($para);
            if(!is_array($data)){$data=array();}
        }else{echo "method [$action()] not found. <br>";}
    }else{echo "class [$controller] not found. <br>";}
}else{echo "file [$fname] not found. <br>";}

$fname='./views/'.$controller."_".$action.'.php';
if(file_exists($fname)){
    include($fname);
}else{echo "file [$fname] not found. <br>";}
/controllers/abc.php
<?php
class abc{
    function xyz(){
        return array("head"=>"象が空を","body"=>"眺めていた。");
    }
}
/views/abc_xyz.php
<!DOCTYPE html>
<html>
<body>
<h1><?php echo $data["head"]; ?></h1>
<?php echo $data["body"]; ?>
</body>
</html>

実行例

[URL] http://~/index.php/abc/xyz
20190730elephant.png

参考

参考にさせていただきました。
https://qiita.com/kahirokunn/items/175b82295ab683ffb624
http://choilog.com/katty0324/blog/6

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

自分の勉強記録を分析しよう!~新米エンジニアの個人開発~

自分の勉強記録を分析しよう!~新米エンジニアの個人開発~

プログラマー歴1年目のじょーだいです。

プログラマーになって一年、そろそろ新しいことを学んでみたかったので、使ったことのない技術を使用して簡単なwebアプリケーションを開発してみました。

どんなアプリ?

僕は勉強したことをTwitterでつぶやいています。

他にも多くの方々が自分の勉強したことをつぶやいているのをよく見かけます。

せっかく勉強しているならその記録をしっかり残したい。。。

そこで今回は自分の勉強を記録しておくことのできる「Stacker」というアプリを開発しました。

使用している技術は?

  • Laravel
  • Vue.js
  • Chart.js
  • MySQL
  • Docker
  • XSERVER
  • Git

各画面の説明

ダッシュボード

01.png

この画面の作成が一番時間をとられました。

上部に1週間のグラフ、下部に日毎の入力したデータが表示されます。

また右上のボタンによって、1週間のグラフを総合計のグラフに切り替えることでカテゴリーごとの合計時間を知ることができます。

勉強している日、してない日がすぐわかるので結構気に入ってます。

カテゴリ―毎の詳細

02.png

ダッシュボードの左側のカテゴリー一覧からカテゴリ―を選択すると画像のような画面が開きます。

今まで入力したコメントが一覧表示されるので、どんなことを勉強してきたのか一目瞭然です。

カテゴリ―詳細ページは他のユーザーが覗くこともできます。

知らない語句や勉強の順序、気になるエンジニアがどんなことを学んでいるのかを知ることができます。

ホーム

02.png

各ユーザーの投稿が一覧表示されています。

とても簡易的なSNSのような感じです。

フォロー機能等は実装していませんが、他のユーザーの勉強量を知ることで、モチベーションを上げることができます。

現在会社の後輩とこのアプリを使用しているのですが、とても会話が盛り上がります。

特に相手が何を勉強しているのかがわかるので、気になったことをすぐに聞けるし、話のきっかけづくりにもなります。

感想

一応完成はしたものの、まだまだ実装したい機能が盛りだくさんです。
例えば
 フォロー機能
 、円グラフでの分析
 、カテゴリ―毎の表示順序、色の切り替え
など、他にも細かいところでまだまだたくさんあります。

他にもリファクタリングやテストコードの作成などすることは盛りだくさん。

これからも勉強を記録していくためにこのアプリを使用し、改善を続けていきます。

自分自身が毎日使用していくので、改善、メンテナンスを無理なく続けていくことができると思います。

個人開発でも作りっぱなしではなく、しっかりと運用していくので、よければ皆様も使ってみてください。

一言

コードが汚すぎる。リーダブルコード読んだはずなんだけどな~

➡Stackerはこちら

➡Twitterはこちら

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

PHP 自分用まとめ 8

ジェネレータ

ジェネレータを使えば、シンプルな イテレータを簡単に実装できます

ジェネレータを使った range() の実装

function xrange($start, $limit, $step = 1) {
    if ($start < $limit) {
        if ($step <= 0) {
            throw new LogicException('Step must be +ve');
        }

        for ($i = $start; $i <= $limit; $i += $step) {
            yield $i;
        }
    } else {
        if ($step >= 0) {
            throw new LogicException('Step must be -ve');
        }

        for ($i = $start; $i >= $limit; $i += $step) {
            yield $i;
        }
    }
}

/*
 * 次の例で、range() と xrange() が同じ結果を返すことに注目しましょう
 */

echo 'Single digit odd numbers from range():  ';
foreach (range(1, 9, 2) as $number) {
    echo "$number ";
}
echo "\n";

echo 'Single digit odd numbers from xrange(): ';
foreach (xrange(1, 9, 2) as $number) {
    echo "$number ";
}




//上の例の出力は以下となります。
Single digit odd numbers from range():  1 3 5 7 9 
Single digit odd numbers from xrange(): 1 3 5 7 9 

ジェネレータの構文

yield キーワード

ジェネレータ関数の肝となるのが yield キーワードです。
最もシンプルな書きかたをすると、yield 文の見た目は return 文とほぼ同じになります。
ただ、return の場合はそこで関数の実行を終了して値を返すのに対して
yield の場合はジェネレータを呼び出しているループに値を戻して
ジェネレータ関数の実行を一時停止します。

値を yield する単純な例

function gen_one_to_three() {
    for ($i = 1; $i <= 3; $i++) {
        // yield を繰り返す間、$i の値が維持されることに注目しましょう
        yield $i;
    }
}

$generator = gen_one_to_three();
foreach ($generator as $value) {
    echo "$value\n";
}


//上の例の出力は以下となります。
1
2
3

値とキーの yield

キー/値 のペアの yield

/*
 * 入力は各フィールドをセミコロンで区切ったものです
 * 最初のフィールドが ID となり、これをキーとして使います
 */

$input = <<<'EOF'
1;PHP;$が大好き
2;Python;インデントが大好き
3;Ruby;ブロックが大好き
EOF;

function input_parser($input) {
    foreach (explode("\n", $input) as $line) {
        $fields = explode(';', $line);
        $id = array_shift($fields);

        yield $id => $fields;
    }
}

foreach (input_parser($input) as $id => $fields) {
    echo "$id:\n";
    echo "    $fields[0]\n";
    echo "    $fields[1]\n";
}


//上の例の出力は以下となります。
1:
    PHP
    $が大好き
2:
    Python
    インデントが大好き
3:
    Ruby
    ブロックが大好き

null 値の yield

function gen_three_nulls() {
    foreach (range(1, 3) as $i) {
        yield;
    }
}

var_dump(iterator_to_array(gen_three_nulls()));


//上の例の出力は以下となります。
array(3) {
  [0]=>
  NULL
  [1]=>
  NULL
  [2]=>
  NULL
}

参照による値の yield

function &gen_reference() {
    $value = 3;

    while ($value > 0) {
        yield $value;
    }
}

/*
 * $number をループ内で変更していることに注目しましょう。
 * このジェネレータは参照を yield するので、
 * gen_reference() 内の $value が変わります。
 */
foreach (gen_reference() as &$number) {
    echo (--$number).'... ';
}


//上の例の出力は以下となります。
2... 1... 0... 

yield from によるジェネレータの委譲
PHP 7 では、ジェネレータの委譲ができるようになりました。

yield from の基本的な使いかた

function count_to_ten() {
    yield 1;
    yield 2;
    yield from [3, 4];
    yield from new ArrayIterator([5, 6]);
    yield from seven_eight();
    yield 9;
    yield 10;
}

function seven_eight() {
    yield 7;
    yield from eight();
}

function eight() {
    yield 8;
}

foreach (count_to_ten() as $num) {
    echo "$num ";
}


//上の例の出力は以下となります。
1 2 3 4 5 6 7 8 9 10 

yield from の返す値

function count_to_ten() {
    yield 1;
    yield 2;
    yield from [3, 4];
    yield from new ArrayIterator([5, 6]);
    yield from seven_eight();
    return yield from nine_ten();
}

function seven_eight() {
    yield 7;
    yield from eight();
}

function eight() {
    yield 8;
}

function nine_ten() {
    yield 9;
    return 10;
}

$gen = count_to_ten();
foreach ($gen as $num) {
    echo "$num ";
}
echo $gen->getReturn();



//上の例の出力は以下となります。
1 2 3 4 5 6 7 8 9 10


ジェネレータと Iterator オブジェクトとの比較

function getLinesFromFile($fileName) {
    if (!$fileHandle = fopen($fileName, 'r')) {
        return;
    }

    while (false !== $line = fgets($fileHandle)) {
        yield $line;
    }

    fclose($fileHandle);
}

// これを、下のクラスと比べてみると……

class LineIterator implements Iterator {
    protected $fileHandle;

    protected $line;
    protected $i;

    public function __construct($fileName) {
        if (!$this->fileHandle = fopen($fileName, 'r')) {
            throw new RuntimeException('Couldn\'t open file "' . $fileName . '"');
        }
    }

    public function rewind() {
        fseek($this->fileHandle, 0);
        $this->line = fgets($this->fileHandle);
        $this->i = 0;
    }

    public function valid() {
        return false !== $this->line;
    }

    public function current() {
        return $this->line;
    }

    public function key() {
        return $this->i;
    }

    public function next() {
        if (false !== $this->line) {
            $this->line = fgets($this->fileHandle);
            $this->i++;
        }
    }

    public function __destruct() {
        fclose($this->fileHandle);
    }
}

リファレンスの説明

リファレンスとは同じ変数の内容を異なった名前で コールすることを意味します。
これは C のポインタとは異なります。
リファレンスを使ってポインタの演算をすることはできませんし
リファレンスは実メモリのアドレスでもありません。

リファレンスはシンボルテーブルのエイリアスです。

リファレンスの代入

$a =& $b;

//この場合、$a と $b は同じ内容を指します。

//ここで、$a と $b は完全に 同じで
//$a が $b を 指しているわけではなく、その逆でもありません。
//$a と $b は同じ場所を指しているのです。

厳密にリファレンスでの代入をしなくても
array() で作成した式は配列の要素の先頭に & を追加したのと同じような動作をします。
たとえば次のようになります。

$a = 1;
$b = array(2, 3);
$arr = array(&$a, &$b[0], &$b[1]);
$arr[0]++; $arr[1]++; $arr[2]++;
/* $a == 2, $b == array(3, 4); */

しかし、配列の内部のリファレンスは危険もあるということに気をつけましょう。

/* スカラー変数への代入 */
$a = 1;
$b =& $a;
$c = $b;
$c = 7; // $c はリファレンスではないので $a や $b の値は変わりません

/* 配列変数への代入 */
$arr = array(1);
$a =& $arr[0]; // $a および $arr[0] は同じリファレンスセットになります
$arr2 = $arr; // これは、リファレンス代入ではありません!
$arr2[0]++;
/* $a == 2, $arr == array(2) */
/* $arr の内容は、たとえリファレンスでなくても変わります! */

リファレンス渡し

function foo(&$var) 
{
  $var++;
}

$a=5;
foo($a);

//この結果、$a は 6 となります。
//これは、関数 foo の中では、変数 $var は $a と同じ内容を指しているためです。

リファレンスが行わないこと

リファレンスはポインタではありません。このため次の例は期待通りに動作しません。

function foo(&$var)
{
    $var =& $GLOBALS["baz"];
}
foo($bar); 

リファレンス渡し

リファレンスにより関数に変数を渡すことが可能です。
この場合、関数内で その引数を修正可能になります。

function foo(&$var)
{
    $var++;
}

$a=5;
foo($a);
// $a はここでは 6 です

次のものはリファレンスで渡すことが可能です。

変数、すなわち、foo($a)
new 命令、すなわち、foo(new foobar())
関数から返されるリファレンスは、次のようになります。

function foo(&$var)
{
    $var++;
}
function &bar()
{
    $a = 5;
    return $a;
}
foo(bar());

他の式は、結果が未定義となるため、リファレンスで渡すべきではありません。

function foo(&$var)
{
    $var++;
}
function bar() // & がないことに注意
{
    $a = 5;
    return $a;
}
foo(bar()); // PHP 5.0.5 以降、致命的なエラーが発生する
            // PHP 5.1.1 以降では strict notice、そして PHP 7.0.0 以降では notice となる

foo($a = 5); // 式、変数ではない
foo(5); // 致命的なエラーが発生する

リファレンスを返す

class foo {
    public $value = 42;

    public function &getValue() {
        return $this->value;
    }
}

$obj = new foo;
$myValue = &$obj->getValue(); // $myValue は $obj->value へのリファレンス、つまり 42 となります
$obj->value = 2;
echo $myValue;                // $obj->value の新しい値である 2 を表示します


//この例では、関数 getValue により返された オブジェクトのプロパティが、設定されます。
//リファレンス構文を 使用しない場合のようにコピーとなるわけではありません。

返されたリファレンスを使うには、リファレンスを代入しなければなりません。

function &collector() {
  static $collection = array();
  return $collection;
}
$collection = &collector();
$collection[] = 'foo';

返されたリファレンスを、リファレンスを要求する別の関数に渡すには、 この構文を使います。

function &collector() {
  static $collection = array();
  return $collection;
}
array_push(collector(), 'foo');

リファレンスの解除

$a = 1;
$b =& $a;
unset($a);

//は、$b を削除せず、$a のみを 削除します。

リファレンスの適用範囲

global リファレンス
変数を global $var として宣言した場合
実際には グローバル変数へのリファレンスを作成したことになります。

$var =& $GLOBALS["var"];

//これは、例えば、$var を削除してもグローバル変数は 削除されないことを意味します。

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

PHP 自分用まとめ 7

名前空間

名前空間の宣言

namespace MyProject;

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }

名前空間の宣言より前に書くことが許されているコードは declare 文のみです。

<html>
<?php
namespace MyProject; // fatal error - namespace must be the first statement in the script
?>

サブ名前空間の宣言

階層つきの名前空間の宣言

namespace MyProject\Sub\Level;

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }

//上の例は、
//定数 MyProject\Sub\Level\CONNECT_OK と
//クラス MyProject\Sub\Level\Connection、 
//そして関数 MyProject\Sub\Level\connect を作成します。

同一ファイル内での複数の名前空間の定義

シンプルな組み合わせ方式による複数の名前空間の宣言

namespace MyProject;

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }

namespace AnotherProject;

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }


//この構文は、複数の名前空間をひとつのファイルに含める場合の方法としてはお勧めしません。 
//かわりに、次の波括弧構文を使うことを推奨します。

波括弧構文による複数の名前空間の宣言

namespace MyProject {

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
}

namespace AnotherProject {

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
}

複数の名前空間および名前空間に属さないコードの宣言

namespace MyProject {

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
}

namespace { // グローバルコード
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}

namespace の波括弧の外側に書くことができる PHP コードは、最初の declare 文だけです。
複数の名前空間および名前空間に属さないコードの宣言

declare(encoding='UTF-8');
namespace MyProject {

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
}

namespace { // global code
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}

名前空間の使用法: 基本編

これら 3 つの構文を実際のコードで使う例を次に示します。

file1.php

<?php
namespace Foo\Bar\subnamespace;

const FOO = 1;
function foo() {}
class foo
{
    static function staticmethod() {}
}
?>

file2.php

<?php
namespace Foo\Bar;
include 'file1.php';

const FOO = 2;
function foo() {}
class foo
{
    static function staticmethod() {}
}

/* 非修飾名 */
foo(); // Foo\Bar\foo 関数と解釈されます
foo::staticmethod(); // Foo\Bar\foo クラスの staticmethod メソッドと解釈されます
echo FOO; // 定数 Foo\Bar\FOO と解釈されます

/* 修飾名 */
subnamespace\foo(); // Foo\Bar\subnamespace\foo 関数と解釈されます
subnamespace\foo::staticmethod(); // Foo\Bar\subnamespace\foo クラスの
                                  // staticmethod メソッドと解釈されます
echo subnamespace\FOO; // 定数 Foo\Bar\subnamespace\FOO と解釈されます

/* 完全修飾名 */
\Foo\Bar\foo(); // Foo\Bar\foo 関数と解釈されます
\Foo\Bar\foo::staticmethod(); // Foo\Bar\foo クラスの staticmethod メソッドと解釈されます
echo \Foo\Bar\FOO; // 定数 Foo\Bar\FOO と解釈されます
?>

グローバルなクラス、関数あるいは定数にアクセスするには
完全修飾名を使用して \strlen()、\Exception あるいは \INI_ALL などとすることができます。

グローバルなクラス、関数および定数への名前空間内からのアクセス

namespace Foo;

function strlen() {}
const INI_ALL = 3;
class Exception {}

$a = \strlen('hi'); // グローバル関数 strlen をコールします
$b = \INI_ALL; // グローバル定数 INI_ALL にアクセスします
$c = new \Exception('error'); // グローバルクラス Exception のインスタンスを作成します

名前空間と動的言語機能
次の例のようなコードを名前空間を使って書き直すには

要素への動的なアクセス

example1.php:

<?php
class classname
{
    function __construct()
    {
        echo __METHOD__,"\n";
    }
}
function funcname()
{
    echo __FUNCTION__,"\n";
}
const constname = "global";

$a = 'classname';
$obj = new $a; // classname::__construct と表示します
$b = 'funcname';
$b(); // funcname と表示します
echo constant('constname'), "\n"; // global と表示します
?>

名前空間つき要素への動的なアクセス

<?php
namespace namespacename;
class classname
{
    function __construct()
    {
        echo __METHOD__,"\n";
    }
}
function funcname()
{
    echo __FUNCTION__,"\n";
}
const constname = "namespaced";

/* ダブルクォートを使う場合は "\\namespacename\\classname" としなければなりません */
$a = '\namespacename\classname';
$obj = new $a; // namespacename\classname::__construct と表示します
$a = 'namespacename\classname';
$obj = new $a; // これも namespacename\classname::__construct と表示します
$b = 'namespacename\funcname';
$b(); // namespacename\funcname と表示します
$b = '\namespacename\funcname';
$b(); // これも namespacename\funcname と表示します
echo constant('\namespacename\constname'), "\n"; // namespaced と表示します
echo constant('namespacename\constname'), "\n"; // これも namespaced と表示します
?>

<?php
namespace MyProject;

echo '"', __NAMESPACE__, '"'; // "MyProject" と出力します
?>

//名前空間内のコードでの __NAMESPACE__ の例
<?php

echo '"', __NAMESPACE__, '"'; // "" と出力します
?>

//グローバルなコードでの __NAMESPACE__ の例
<?php
namespace MyProject;

function get($classname)
{
    $a = __NAMESPACE__ . '\\' . $classname;
    return new $a;
}
?>

//__NAMESPACE__ による動的な名前の作成

名前空間内での namespace 演算子

<?php
namespace MyProject;

use blah\blah as mine; // "名前空間の使用法: エイリアス/インポート" を参照ください

blah\mine(); // MyProject\blah\mine() 関数をコールします
namespace\blah\mine(); // MyProject\blah\mine() 関数をコールします

namespace\func(); // MyProject\func() 関数をコールします
namespace\sub\func(); // MyProject\sub\func() 関数をコールします
namespace\cname::method(); // MyProject\cname クラスの静的メソッド "method" をコールします
$a = new namespace\sub\cname(); // MyProject\sub\cname クラスのオブジェクトのインスタンスを作成します
$b = namespace\CONSTANT; // 定数 MyProject\CONSTANT の値を $b に代入します
?>

グローバルコードでの namespace 演算子

<?php

namespace\func(); // func() 関数をコールします
namespace\sub\func(); // sub\func() 関数をコールします
namespace\cname::method(); // cname クラスの静的メソッド "method" をコールします
$a = new namespace\sub\cname(); // sub\cname クラスのオブジェクトのインスタンスを作成します
$b = namespace\CONSTANT; // 定数 CONSTANT の値を $b に代入します
?>

名前空間の使用法: エイリアス/インポート

PHP でのエイリアス作成には use 演算子を使用します。

use 演算子によるインポート/エイリアス

<?php
namespace foo;
use My\Full\Classname as Another;

// これは use My\Full\NSname as NSname と同じです
use My\Full\NSname;

// グローバルクラスをインポートします
use ArrayObject;

// 関数をインポートします (PHP 5.6+)
use function My\Full\functionName;

// 関数のエイリアスを定義します (PHP 5.6+)
use function My\Full\functionName as func;

// 定数をインポートします (PHP 5.6+)
use const My\Full\CONSTANT;

$obj = new namespace\Another; // foo\Another クラスのオブジェクトのインスタンスを作成します
$obj = new Another; // My\Full\Classname クラスのオブジェクトのインスタンスを作成します
NSname\subns\func(); // My\Full\NSname\subns\func 関数をコールします
$a = new ArrayObject(array(1)); // ArrayObject クラスのオブジェクトのインスタンスを作成します
// "use ArrayObject" がなければ、foo\ArrayObject クラスのオブジェクトのインスタンスを作成することになります
func(); // 関数 My\Full\functionName を呼びます
echo CONSTANT; // 定数 My\Full\CONSTANT の値を表示します
?>

use 演算子によるインポート/エイリアスで、複数の use 文を組み合わせる例

<?php
use My\Full\Classname as Another, My\Full\NSname;

$obj = new Another; // My\Full\Classname クラスのオブジェクトのインスタンスを作成します
NSname\subns\func(); // My\Full\NSname\subns\func 関数をコールします
?>

インポートと動的名

<?php
use My\Full\Classname as Another, My\Full\NSname;

$obj = new Another; // My\Full\Classname クラスのオブジェクトのインスタンスを作成します
$a = 'Another';
$obj = new $a;      // Another クラスのオブジェクトのインスタンスを作成します
?>

インポートと完全修飾名

<?php
use My\Full\Classname as Another, My\Full\NSname;

$obj = new Another; // My\Full\Classname クラスのオブジェクトのインスタンスを作成します
$obj = new \Another; // Another クラスのオブジェクトのインスタンスを作成します
$obj = new Another\thing; // My\Full\Classname\thing クラスのオブジェクトのインスタンスを作成します
$obj = new \Another\thing; // Another\thing クラスのオブジェクトのインスタンスを作成します
?>

use 宣言のグループ化

<?php

// 以前のバージョンのコード
use some\namespace\ClassA;
use some\namespace\ClassB;
use some\namespace\ClassC as C;

use function some\namespace\fn_a;
use function some\namespace\fn_b;
use function some\namespace\fn_c;

use const some\namespace\ConstA;
use const some\namespace\ConstB;
use const some\namespace\ConstC;

// PHP 7 以降のコード
use some\namespace\{ClassA, ClassB, ClassC as C};
use function some\namespace\{fn_a, fn_b, fn_c};
use const some\namespace\{ConstA, ConstB, ConstC};

グローバル空間
名前空間の定義がない場合、すべてのクラスや関数の定義はグローバル空間に配置されます。
名前の先頭に \ をつけると
名前空間の内部からであってもグローバル空間の名前を指定することができます。

グローバル空間を指定する方法

<?php
namespace A\B\C;

/* この関数は A\B\C\fopen です */
function fopen() { 
     /* ... */
     $f = \fopen(...); // グローバルな fopen をコールします
     return $f;
} 
?>

名前空間の使用法: グローバル関数/定数への移行

名前空間内からのグローバルクラスへのアクセス

<?php
namespace A\B\C;
class Exception extends \Exception {}

$a = new Exception('hi'); // $a は A\B\C\Exception クラスのオブジェクトです
$b = new \Exception('hi'); // $b は Exception クラスのオブジェクトです

$c = new ArrayObject; // fatal error, class A\B\C\ArrayObject not found
?>

関数や定数の場合
名前空間内にその関数や定数が見つからなければ PHP はグローバル関数/定数を探します。

名前空間内からのグローバル関数/定数への移行

<?php
namespace A\B\C;

const E_ERROR = 45;
function strlen($str)
{
    return \strlen($str) - 1;
}

echo E_ERROR, "\n"; // "45" と表示します
echo INI_ALL, "\n"; // "7" と表示します - グローバルの INI_ALL に移行しました

echo strlen('hi'), "\n"; // "1" と表示します
if (is_array('hi')) { // "is not array" と表示します
    echo "is array\n";
} else {
    echo "is not array\n";
}
?>

名前解決の例

<?php
namespace A;
use B\D, C\E as F;

// 関数のコール

foo();      // まず名前空間 "A" で定義されている "foo" のコールを試み、
            // 次にグローバル関数 "foo" をコールします

\foo();     // グローバルスコープで定義されている関数 "foo" をコールします

my\foo();   // 名前空間 "A\my" で定義されている関数 "foo" をコールします

F();        // まず名前空間 "A" で定義されている "F" のコールを試み、
            // 次にグローバル関数 "F" をコールします

// クラスの参照

new B();    // 名前空間 "A" で定義されているクラス "B" のオブジェクトを作成します
            // 見つからない場合は、クラス "A\B" の autoload を試みます

new D();    // インポートルールを使用し、名前空間 "B" で定義されているクラス "D" のオブジェクトを作成します
            // 見つからない場合は、クラス "B\D" の autoload を試みます

new F();    // インポートルールを使用し、名前空間 "C" で定義されているクラス "E" のオブジェクトを作成します
            // 見つからない場合は、クラス "C\E" の autoload を試みます

new \B();   // グローバルスコープで定義されているクラス "B" のオブジェクトを作成します
            // 見つからない場合は、クラス "B" の autoload を試みます

new \D();   // グローバルスコープで定義されているクラス "D" のオブジェクトを作成します
            // 見つからない場合は、クラス "D" の autoload を試みます

new \F();   // グローバルスコープで定義されているクラス "F" のオブジェクトを作成します
            // 見つからない場合は、クラス "F" の autoload を試みます

// 別の名前空間から使用する静的メソッド/関数

B\foo();    // 名前空間 "A\B" の関数 "foo" をコールします

B::foo();   // 名前空間 "A" で定義されているクラス "B" のメソッド "foo" をコールします
            // クラス "A\B" が見つからない場合はクラス "A\B" の autoload を試みます

D::foo();   // インポートルールを使用し、名前空間 "B" で定義されているクラス "D" のメソッド "foo" をコールします
            // クラス "B\D" が見つからない場合はクラス "B\D" の autoload を試みます

\B\foo();   // 名前空間 "B" の関数 "foo" をコールします

\B::foo();  // グローバルスコープのクラス "B" のメソッド "foo" をコールします
            // クラス "B" が見つからない場合はクラス "B" の autoload を試みます

// 現在の名前空間から使用する静的メソッド/関数

A\B::foo();   // 名前空間 "A\A" のクラス "B" のメソッド "foo" をコールします
              // クラス "A\A\B" が見つからない場合はクラス "A\A\B" の autoload を試みます

\A\B::foo();  // 名前空間 "A" のクラス "B" のメソッド "foo" をコールします
              // クラス "A\B" が見つからない場合はクラス "A\B" の autoload を試みます
?>

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