- 投稿日:2020-06-01T23:53:01+09:00
[PHP]引数で受け取った無名関数(クロージャ)の結果の型を指定する方法
引数で受け取った無名関数の返り値が型チェックでエラーになるようにするというのが目的。
お題
phpの引数は型指定できますが、受け取った無名関数の返り値の指定はできません。
例えば下記は配列のフィルタを行うクラスの実装例です。<?php class ArrayObj { private $values = [1, 2, 3, 4]; public function filter(callable $filter): array { $filteredValue = []; foreach ($this->values as $key => $value) { if ($filter($value)) { $filteredValue[] = $value; } } return $filteredValue; } } $arrayObj = new ArrayObj(); // array(3) { [0]=> int(1) [1]=> int(3) [2]=> int(4) } var_dump($arrayObj->filter(function($value) { return $value <> 2; })); // array(4) { [0]=> int(1) [1]=> int(2) [2]=> int(3) [3]=> int(4) } var_dump($arrayObj->filter(function($value) { return "test"; }));
ArrayObj
クラスのfilter
メソッドはフィルタリングの判定用の無名関数を受け取ります。bool
型を返す無名関数がほしいところですが、文字列を返す無名関数も受け取り処理できてしいます。これをbool
型以外を返す無名関数が渡されたときはエラーにするようにします。型チェック関数を利用する
単純に実装するには型チェック関数を利用するという方法があります。
bool
型をチェックするので`is_bool
関数を使用します。<?php class ArrayObj { private $values = [1, 2, 3, 4]; public function getValues() { return $this->values; } public function filter(callable $filter): array { $filteredValue = []; foreach ($this->values as $value) { // 型チェック関数を追加 if (! is_bool($filter($value))) { throw new \TypeError(); } if ($filter($value)) { $filteredValue[] = $value; } } return $filteredValue; } } $arrayObj = new ArrayObj(); // array(3) { [0]=> int(1) [1]=> int(3) [2]=> int(4) } var_dump($arrayObj->filter(function($value) { return $value <> 2; })); // Fatal error: Uncaught TypeError var_dump($arrayObj->filter(function($value) { return "test"; }));文字列を返す無名関数を渡すとエラーになりました。しかし
- 型チェックがバリバリロジックに組み込まれてる
- エラーハンドリングを自前で実装しなきゃいけない
ていう問題があります。この辺はPHPの型チェックに任せたいところ。
引数の型指定をインターフェースにする
引数の型指定を無名関数ではなく、インターフェースにしてしまうという方法。Javaでいう関数型インターフェースのような使い方。
<?php // 厳格な型チェックを追加 declare(strict_types=1); // フィルタリング用のインターフェース定義 interface FilterInterface { function filter($value): bool; } class ArrayObj { private $values = [1, 2, 3, 4]; public function getValues() { return $this->values; } // フィルタリング用のインターフェースを実装したクラスを受け取るように変更 public function filter(FilterInterface $filter): array { $filteredValue = []; foreach ($this->values as $value) { // フィルタリング用のクラスでフィルタリング if ($filter->filter($value)) { $filteredValue[] = $value; } } return $filteredValue; } } $arrayObj = new ArrayObj(); // array(3) { [0]=> int(1) [1]=> int(3) [2]=> int(4) } var_dump($arrayObj->filter(new class implements FilterInterface { public function filter($value): bool { return $value <> 2; } })); // Fatal error: Uncaught TypeError: Return value of class@anonymous::filter() must be of the type bool var_dump($arrayObj->filter(new class implements FilterInterface { public function filter($value): bool { return "test"; } }));一応、型チェックをロジックと自前のエラーハンドリングを実装せずにすみました。しかし、利用側がいちいちインターフェースを実装したクラスを実装しなければならないので面倒臭いです。Javaのラムダ式のように省略して記述できればいいのですが、、、
っていうかこれ「無名関数の返り値が型チェックでエラーになるようにする」っていう趣旨からずれまくっているような気がする。
戻り値の型宣言をした無名関数を通す
無名関数の受け取り側で、返り値の型を指定した無名関数に受け取った無名関数を実行させて、返り値の型チェックを行うという方法。ちょっと言ってることがよくわからないですが、具体的にはこんな感じ。
<?php // 厳格な型チェックを追加 declare(strict_types=1); class ArrayObj { private $values = [1, 2, 3, 4]; public function getValues() { return $this->values; } public function filter(callable $filter): array { // 戻り値の型を指定した無名関数 $test = function (callable $filter, $value): bool { return $filter($value); }; $filteredValue = []; foreach ($this->values as $key => $value) { // 戻り値の型を指定した無名関数で引数の無名関数を実行 if ($test($filter, $value)) { $filteredValue[] = $value; } } return $filteredValue; } } $arrayObj = new ArrayObj(); // array(3) { [0]=> int(1) [1]=> int(3) [2]=> int(4) } var_dump($arrayObj->filter(function($value) { return $value <> 2; })); // PHP Fatal error: Uncaught TypeError: Return value of class@anonymous::filter() must be of the type bool, string returned var_dump($arrayObj->filter(function($value) { return "test"; }));これで一応利用側にも負担をかけずに実装できました。まあ、これが現実的なラインかなぁと。
一応、戻り値の型宣言ができるようになったとは言えども、関数の結果を見て判断されるので、一回は実行されてしまいますのでそのへんは注意が必要。
- 投稿日:2020-06-01T23:10:53+09:00
PHP7.0 × Zend Framework1でWebアプリケーションを作る
1.PHPのインストール -XAMPP
XAMPP = ザンプ
X:クロスプラットフォーム
A:Apatch
M:MariaDB/MySQL
P:PHP
P:PerlPHPを用いたWebアプリケーションに必要なソフトウェア群を一度にインストールできるパッケージ。
公式サイトからダウンロード、インストール。
(Nextをポチポチしていれば問題ないはず)完了したらXAMPPのコントロールパネルが表示される。ここで各ソフトウェアの起動・終了を行う。
赤いエラーが出ている場合
使用するポート番号が既に使用されている可能性。
https://norakura-jyoko.com/xampp-local-setup を参照2.Zend Framework1を導入する
最新版は3だが業務の都合で1。
だいたいこの通りにやればできる。
http://wiki.tmd45.jp/wiki.cgi?page=XAMPP%A4%CBZend+Framework%A4%F2%C6%B3%C6%FEWebアプリケーションを作成する
ZendFramewodkの推奨ディレクトリ構造
<project name>/ application/ configs/ application.ini controllers/ helpers/ forms/ layouts/ filters/ helpers/ scripts/ models/ modules/ services/ views/ filters/ helpers/ scripts/ Bootstrap.php data/ cache/ indexes/ locales/ logs/ sessions/ uploads/ docs/ library/ public/ css/ images/ js/ .htaccess index.php scripts/ jobs/ build/ temp/ tests/https://framework.zend.com/manual/1.12/ja/project-structure.project.html
- 投稿日:2020-06-01T22:46:26+09:00
phpredisインストール時に、PHPの設定でひさしぶりにハマったのでメモ
PHP Warning: PHP Startup: Unable to load dynamic library '/usr/local/src/phpredis/modules/redis.so' (tried: /usr/local/src/phpredis/modules/redis.so (/usr/local/src/phpredis/modules/redis.so: undefined symbol: php_json_decode_ex), /usr/lib64/php/modules//usr/local/src/phpredis/modules/redis.so.so (/usr/lib64/php/modules//usr/local/src/phpredis/modules/redis.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0こんなエラーを見かけたら、ポイントは
php_json_decode_ex
これは、jsonをパースするモジュールより前にロードしようとして失敗しているということ。
/etc/php.d
の中に、適当なファイル(アルファベット順でjson.iniより後に読み込まれるようなファイル名)を作ってextension=redis.so
と書けば解決。
- 投稿日:2020-06-01T20:48:51+09:00
symphonyを覚えようとしたら英語だらけだった
はじめに
現場が2020年5月末で終りを迎えた。コロナ影響による無業期間の始まりである。ほんとうに仕事がない期間は初めてなのでちょっとドキドキするが向こう1年ほどの生活資金はある。そしていままでの人間関係を総動員して、広告系のWeb現場にいる友達(元同僚)からphpの案件にチャレンジしてみないか、と面談オファーが来た(持つべきものは酒を酌み交わした友である)。で、使われてるフレームワークがsymphonyって言われたんだけど、、、
おおん?日本語の資料がめっちゃすくねぇー!また英語なの!?
文句言ってるヒマはねぇや、やるしかねぇぇぇ
https://www.udemy.com/share/101WEaAkQadFdURHg=/
Symfony
インストール
terminal(ver.4.2をインストールする場合)> composer create-project symfony/website-skeleton ./ "4.2.*"Djangoでいうロケット画面を出せ
まず、phpってフォルダをテキトーに作って基点とする。そこに my-project を作成
my-projectを作成し、サーバーを起動composer create-project symfony/skeleton my-project cd my-project php -S 127.0.0.1:8000 -t public
http://localhost:8000/
まぁ下の図がDjangoでいう、いわゆるロケット画面だな。
不足している拡張機能などがないか確認する
my-project> composer require symfony/requirements-checkerhttp://localhost:8000/check.php
check.phpを見てみると、どうやら
1. PHP accelerator を入れろ!
2. php.ini のキャッシュサイズを5M以上にしろ!
と書いてあるようだPHP accelerator
https://www.php.net/manual/ja/opcache.installation.php#opcache.installation.bundled
Windows の場合は zend_extension=C:\path\to\php_opcache.dll を使います。
php.ini(変更前);opcache.enable=1 ;opcache.enable_cli=0php.ini(変更後)opcache.enable=1 opcache.enable_cli=1
php.ini(最終行に追記)zend_extension=C:\php\ext\php_opcache.dll
キャッシュサイズを5M以上に
php.ini(変更前);realpath_cache_size = 4096kphp.ini(変更後)realpath_cache_size = 5M
確認
> php -v PHP 7.4.6 (cli) (built: May 12 2020 11:38:54) ( ZTS Visual C++ 2017 x64 ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Zend OPcache v7.4.6, Copyright (c), by Zend Technologieshttp://localhost:8000/check.php
twig
インストール
> composer require twig > composer require doctrinehello world
名前空間について
Appと打ち込むとsrcフォルダをのぞきにいくようにcomposer.jsonは初期設定されている
composer.json"autoload": { "psr-4": { "App\\": "src/" } },127.0.0.1:8000でindexを呼ぶ
ルーティング
ドキュメントルートへのアクセスで my-project/src/Controller のなかの DefaultController に飛んで、index関数を実行せよ、ということになる
routes.yamlindex: path: / controller: App\Controller\DefaultController::indexコントローラファイルの作成
DefaultController<?php namespace App\Controller; use Symfony\Component\HttpFoundation\Response; class DefaultController { public function index() { return new Response('<p>Hello!</p>'); } }確認
Consolephp -S 127.0.0.1:8000 -t public
確認2:127.0.0.1:8000/homeでindexを呼ぶ
ルーティング
routes.yaml(変更)index: path: /home controller: App\Controller\DefaultController::indexドキュメントルートのページはwelcomeページに
helloがドキュメントルートからhomeに変わった!
コントローラファイルを消す
上記がコントローラの一般的な作り方だが、レクチャーはこう言っている、いったんDefaultController.phpを消す。
maker
インストール
> composer require maker > php bin/console make:controller DefaultControllerコントローラファイルの作成
php bin/console make:controller DefaultController
確認:127.0.0.1:8000/defaultでindexを呼ぶ
なるほどね。これがアノテーション(注釈)式というらしい。緑色のコメントされている場所を "/default" から "/default1" とかにしてみると、たしかに url が移動する。
JSONを返したいときは
<?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class DefaultController extends AbstractController { /** * @Route("/default", name="default") */ public function index() { // 変更前 // return $this->render('default/index.html.twig', [ // 'controller_name' => 'DefaultController', // ]); // 変更後 return $this->json(['username'=>'john.doe']); } }確認
Consolephp -S 127.0.0.1:8000 -t public
引数取り込み式にもできる
<?php namespace App\Controller; use Symfony\Component\Routing\Annotation\Route; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; // 追加 class DefaultController extends AbstractController { /** * @Route("/default/{name}", name="default") */ public function index($name) { // 変更前 // return $this->render('default/index.html.twig', [ // 'controller_name' => 'DefaultController', // ]); // 変更後 return new Response("Hello! $name"); } }確認
Consolephp -S 127.0.0.1:8000 -t public
リダイレクトしたい
<?php namespace App\Controller; use Symfony\Component\Routing\Annotation\Route; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; class DefaultController extends AbstractController { /** * @Route("/default/{name}", name="default") */ public function index($name) { // 変更前 // return $this->render('default/index.html.twig', [ // 'controller_name' => 'DefaultController', // ]); // 変更後 return $this->redirectToRoute('default2'); } /** * @Route("/default2", name="default2") */ public function index2() { return new Response('I am from default2 route!'); } }確認
Consolephp -S 127.0.0.1:8000 -t public
テンプレートエンジンについて
コントローラ
DefaultController.php(引数にusersの配列を追加しました)<?php namespace App\Controller; use Symfony\Component\Routing\Annotation\Route; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; class DefaultController extends AbstractController { /** * @Route("/default", name="default") */ public function index() { return $this->render('default/index.html.twig', [ 'controller_name' => 'DefaultController', 'users' => ['Adam', 'Robert', 'John', 'Susan'] // add ]); } }テンプレート
index.html.twig(配列をループで回します){% extends 'base.html.twig' %} {% block title %}Hello DefaultController!{% endblock %} {% block body %} <style> .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } </style> <div class="example-wrapper"> <h1>Hello {{ controller_name }}! ✅</h1> This friendly message is coming from: <ul> <li>Your controller at <code><a href="{{ 'D:/OneDrive/ドキュメント/Project/Php/my-project/src/Controller/DefaultController.php'|file_link(0) }}">src/Controller/DefaultController.php</a></code></li> <li>Your template at <code><a href="{{ 'D:/OneDrive/ドキュメント/Project/Php/my-project/templates/default/index.html.twig'|file_link(0) }}">templates/default/index.html.twig</a></code></li> </ul> {# add #} <ul> {% for user in users %} <li>{{ user }}</li> {% endfor %} </ul> </div> {% endblock %}確認
Consolephp -S 127.0.0.1:8000 -t public
モデル
> composer require ormデータベース作成
.envの設定
my-project/.env###> doctrine/doctrine-bundle ### # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" # For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8" # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml DATABASE_URL=mysql://root:{password}@127.0.0.1:3306/symphonydb?serverVersion=8.0.19 ###< doctrine/doctrine-bundle ###コマンドを打ち込むとテーブル名は?と聞かれるのでUserと入力> php bin/console make:entity Class name of the entity to create or update (e.g. VictoriousKangaroo): // テーブル名は? > User created: src/Entity/User.php created: src/Repository/UserRepository.php Entity generated! Now let's add some fields! You can always add more fields later manually or by re-running this command. // 列を追加するかい? New property name (press <return> to stop adding fields): > name // 列の型は? Field type (enter ? to see all types) [string]: > string // Null許容する? Can this field be null in the database (nullable) (yes/no) [no]: > no // (つぎの)列を追加するかい?(ここでEnter押して終わった) Add another property? Enter the property name (or press <return> to stop adding fields): >Migration> php bin/console doctrine:migrations:diff > php bin/console doctrine:migrations:migrate Success!コントローラ
いったんデータをテーブルにInsertしましょう<?php namespace App\Controller; use App\Entity\User; use Symfony\Component\Routing\Annotation\Route; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class DefaultController extends AbstractController { /** * @Route("/default", name="default") */ public function index() { $entityManager = $this->getDoctrine()->getManager(); $user = new User; $user->setName('Adam'); $user2 = new User; $user2->setName('Robert'); $user3 = new User; $user3->setName('John'); $user4 = new User; $user4->setName('Susan'); $entityManager->persist($user); $entityManager->persist($user2); $entityManager->persist($user3); $entityManager->persist($user4); exit($entityManager->flush()); // いわゆるコミット(で、プログラムはここで停止) return $this->render('default/index.html.twig', [ 'controller_name' => 'DefaultController', 'users' => ['Adam', 'Robert', 'John', 'Susan'] // add ]); } }確認
Consolephp -S 127.0.0.1:8000 -t public
URLにアクセスすると?
ははぁ、なるほどね
オッケー!テーブルに入ってんじゃーん!俺は天才だー!
(って言わないとやってらんない)
テンプレート
index.html.twig(user.nameに変わってます){% extends 'base.html.twig' %} {% block title %}Hello DefaultController!{% endblock %} {% block body %} <style> .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } </style> <div class="example-wrapper"> <h1>Hello {{ controller_name }}! </h1> This friendly message is coming from: <ul> <li>Your controller at <code><a href="{{ 'D:/OneDrive/ドキュメント/Project/Php/my-project/src/Controller/DefaultController.php'|file_link(0) }}">src/Controller/DefaultController.php</a></code></li> <li>Your template at <code><a href="{{ 'D:/OneDrive/ドキュメント/Project/Php/my-project/templates/default/index.html.twig'|file_link(0) }}">templates/default/index.html.twig</a></code></li> </ul> <ul> {% for user in users %} <li>{{ user.name }}</li> {# modify #} {% endfor %} </ul> </div> {% endblock %}確認
Consolephp -S 127.0.0.1:8000 -t public
ログ出力したい!
Consolecomposer require logger
[2020-06-02T17:05:35.534551+09:00] request.INFO: Matched route "index". {"route":"index","route_parameters":{"_route":"index","_controller":"App\\Controller\\DefaultController::index"},"request_uri":"http://127.0.0.1:8000/","method":"GET"} [] [2020-06-02T17:05:35.537066+09:00] app.INFO: Gifts were randomized! [] [] [2020-06-02T17:05:35.578394+09:00] doctrine.DEBUG: SELECT t0.id AS id_1, t0.name AS name_2 FROM user t0 [] []いろんなURLルーティング
コメントアウトに見える「@Route」の中の文字は特別な意味を持っている。DefaultControllerがナンダ?とかは特に気にしないで。ビデオのなかの題材に過ぎない。
DefaultController.php<?php namespace App\Controller; use App\Entity\User; use App\Services\GiftsService; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class DefaultController extends AbstractController { /** * @Route("/default", name="default") */ public function index(GiftsService $gifts) { $users = $this->getDoctrine()->getRepository(User::class)->findAll(); return $this->render('default/index.html.twig', [ 'controller_name' => 'DefaultController', 'users' => $users, 'random_gift' => $gifts->gifts ]); } /** * @Route("/blog/{page?}", name="blog_list", requirements={"page": "\d+"}) */ public function index2() { return new Response('Optional parameters in url and requirements for parameters'); } /** * @Route("/articles/{_locale}/{year}/{slug}/{category}", * defaults={"category": "computers"}, * requirements={ * "_locale": "en|fr", * "category": "computers|rtv", * "year": "\d+" * }) */ public function index3() { return new Response('An advanced route example'); } /** * @Route({"nl": "/over-ons", "en": "/about-us"}, name="abount_us") */ // 国際化対応(nlはオランダ、enは英語) public function index4() { return new Response('Translated routes'); } }フラッシュメッセージ
一発だけの揮発性メッセージ
DefaultController.php(index関数そのものにメッセージを仕込める)/** * @Route("/default", name="default") */ public function index(GiftsService $gifts) { $users = $this->getDoctrine()->getRepository(User::class)->findAll(); // これ! $this->addFlash('notice', 'Your changes were saved!'); return $this->render('default/index.html.twig', [ 'controller_name' => 'DefaultController', 'users' => $users, 'random_gift' => $gifts->gifts ]); }index.html.twig{% extends 'base.html.twig' %} {% block title %}Hello DefaultController!{% endblock %} {% block body %} <style> .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } </style> <div class="example-wrapper"> <h1>Hello {{ controller_name }}! </h1> This friendly message is coming from: <ul> <li>Your controller at <code><a href="{{ 'D:/OneDrive/ドキュメント/Project/Php/my-project/src/Controller/DefaultController.php'|file_link(0) }}">src/Controller/DefaultController.php</a></code></li> <li>Your template at <code><a href="{{ 'D:/OneDrive/ドキュメント/Project/Php/my-project/templates/default/index.html.twig'|file_link(0) }}">templates/default/index.html.twig</a></code></li> </ul> <ul> {% for user in users %} <li>{{ user.name }} - you won {{ random_gift[loop.index0] }}</li> {# modify #} {% endfor %} </ul> {# これ! #} {% for message in app.flashes('notice') %} <div class="flash-notice"> {{ message }} </div> {% endfor %} </div> {% endblock %}確認
Consolephp -S 127.0.0.1:8000 -t public
ははぁ、なるほどね。addFlash~を消すと出力されないわけだ。
おまけ(複数の揮発性メッセージ)
addFlashを複数にすると配列に収納される$this->addFlash('notice', 'Your changes were saved!'); $this->addFlash('warning', 'Your changes were saved!');テンプレートでループ処理すると{% for csslabel, messages in app.flashes %} {% for message in messages %} <div class="flash-{{ csslabel }}"> {{ message }} </div> {% endfor %} {% endfor %}確認
Consolephp -S 127.0.0.1:8000 -t public
Cookie
DefaultController/** * @Route("/default", name="default") */ public function index(GiftsService $gifts) { $users = $this->getDoctrine()->getRepository(User::class)->findAll(); // これ! $cookie = new Cookie('my_cookie', 'cookie value', time() + 60); // now() + 60sec $res = new Response(); $res->headers->setCookie($cookie); $res->send(); return $this->render('default/index.html.twig', [ 'controller_name' => 'DefaultController', 'users' => $users, 'random_gift' => $gifts->gifts ]); }確認
Consolephp -S 127.0.0.1:8000 -t public
ははぁ、なるほどね。っていうかchromeでcookieも見れたんやな...
Cookieのクリア
Cookieを消すとき$res = new Response(); $res->headers->clearCookie('my_cookie'); $res->send();セッション
「セッションIDの表示アンドSTOP」のところで処理が止まっちゃう(※exit)なので、手でコメントアウトして、コメントアウト外して、ってパチパチやるとセッションに入った値とIDが確認できる
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; public function index(..., Request $request, SessionInterface $session) { // セッションIDの表示アンドSTOP exit($request->cookies->get('PHPSESSID')); // セッションに値を仕込んで表示 $session->set('name', 'session value'); if($session->has('name')){ exit($session->get('name')); } // おまけ(セッション削除) $session->remove('name'); // おまけ(セッション全消し) $session->clear(); }GET
public function index(GiftsService $gifts, Request $request, SessionInterface $session) { // クエリストリングに page があれば page の数字を返しなければ 'default' を返す exit($request->query->get('page', 'default')); }確認
Consolephp -S 127.0.0.1:8000 -t public
おまけ
// アップロードされたファイル foo の取得 $request->files->get('foo');404
- 投稿日:2020-06-01T19:28:09+09:00
【Laravel】php artisan migrateエラーへの対応 : 1 PDOException::("SQLSTATE[HY000] [2002] Connection refused") /Applications/MAMP/htdocs/task_test/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70
【Laravel】php artisan migrateエラーへの対応 : 1 PDOException::("SQLSTATE[HY000] [2002] Connection refused") /Applications/MAMP/htdocs/task_test/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70
上記エラーを対応した際の内容を記録します.
目次
動作環境
OS : macOS Mojave 10.14.6
MAMP : 5.7
MySQL : 5.7.26
PHP : 7.2.31
laravel : v6.18.16
解決法
先に解決した方法を記載します.
.envファイルにDB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock
を追加します..envDB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel_blog DB_USERNAME=blog_user DB_PASSWORD=mnhgtr54 //追加 DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sockキャッシュをクリアし、再度、マイグレーション
$ php artisan cache:clear #キャッシュをクリア $ php artisan migrate #再度実行 Migration table created successfully.こちらの記事を参考にしました.
https://teratail.com/questions/183824調べたこと
MAMPのページを見るとUnixソケットのパスが記載されている.
またUnixソケットを利用する場合はSocketを使用する必要があるとのこと
ここでそもそもUnixソケットとは何かについて調べたところUNIXドメインソケット通信という通信プロトコルということが分かった
UNIXドメインソケット(英: UNIX domain socket)とは、単一のオペレーティングシステム内で実行されるプロセス間でデータを交換するためのデータ通信の終点.UNIXドメインソケットは、アドレス・名前空間としてファイルシステムを使用している。これらは、ファイルシステム内のinodeとしてプロセスから参照される。これは、2つのプロセスが通信するために、同じソケットを開くことができる。しかし、コミュニケーションは、完全にオペレーティングシステムのカーネル内で発生する
エラー原因の考察
今回のエラーの件をまとめると
MacのターミナルとMAMPのMySQLは同一のマシン上にインストールされているため,
Unixドメインソケット通信を利用して接続する必要があった.そのため,MAMPのヘルプにあるようにドメインソケットのアドレスとなる
/Applications/MAMP/tmp/mysql/mysql.sock
を指定することでターミナルとMySQLで通信ができるようになった
- 投稿日:2020-06-01T18:42:28+09:00
【PHP8.0】PHPにオブジェクト初期化子が導入される
これまで何度も塩漬けにされたり却下されたりしていたオブジェクト初期化子ですが、ついにPHP8.0で導入されることになりました。
オブジェクト初期化子が何かというとこれです。class HOGE{ public function __construct( private int $x ){ // $HOGE->xが生える } }これはオブジェクト初期化子でいいのか?
日本語で何と表すのか適切な単語が思いつかなかったのでとりあえずオブジェクト初期化子としておきます。
愚直に訳すと"コンストラクタ引数昇格"ですが、そんな単語は無いうえに型昇格と紛らわしいです。
引数プロパティ宣言もパラメータプロパティ宣言もほぼ使われてないし何と表現すればいいのだろう。
きっと誰かが適切な語をプルリクしてくれるはず。以下は該当のRFC、PHP RFC: Constructor Property Promotionの日本語訳です。
PHP RFC: Constructor Property Promotion
Introduction
PHPでは現在のところ、オブジェクトにプロパティを定義するだけでも同じことを複数回書かなければならないため、多くの無駄が必要です。
以下の単純なクラスを考えてみましょう。class Point { public float $x; public float $y; public float $z; public function __construct( float $x = 0.0, float $y = 0.0, float $z = 0.0, ) { $this->x = $x; $this->y = $y; $this->z = $z; } }プロパティの表記は、1:プロパティの宣言、2:コンストラクタの引数、3:プロパティの代入で3回も繰り返されます。
さらにプロパティの型も2箇所に書かなければなりません。プロパティ宣言とコンストラクタ以外には何も含まれていないバリューオブジェクトでは特に、多くの重複によって変更が複雑になり、エラーを起こしやすいものとなります。
このRFCでは、プロパティの定義とコンストラクタを組み合わせるショートハンド構文の導入を提案します。
PHP8class Point { public function __construct( public float $x = 0.0, public float $y = 0.0, public float $z = 0.0, ) {} }このショートハンド構文は、前述の例と厳密に同じで、より短く書くことができます。
構文は姉妹言語Hackから採用しています。Proposal
コンストラクタの引数に
public/protected/private
何れかが記述されている場合、その引数は"promoteされた引数"とします。
promoteされた引数には、同じ名前のプロパティが追加され、値が割り当てられます。Constraints
promoteはabstractではないクラスのコンストラクタでのみ記述可能です。
従って、以下のような構文は使用不能です。// エラー:コンストラクタではない function test(private $x) {} abstract class Test { // エラー:abstractなので駄目 abstract public function __construct(private $x); } interface Test { // エラー:interfaceも駄目 public function __construct(private $x); }一般的でない使い方ですが、トレイトでは使用可能です。
対応する可視性キーワードは
public/protected/private
のみです。class Test { // エラー:varはサポートしてない public function __construct(var $prop) {} }promoteされた引数によるプロパティは、通常のプロパティと全く同じ扱いになります。
特に注意点として、同じプロパティを二度宣言することはできません。class Test { public $prop; // Error: Redeclaration of property. public function __construct(public $prop) {} }また、プロパティにすることのできないcallable型は使用することができません。
class Test { // Error: Callable type not supported for properties. public function __construct(public callable $callback) {} }promoteされたプロパティはプロパティ宣言と同義であるため、デフォルトがNULLの場合はNULL許容型を明示しなければなりません。
class Test { // Error: Using null default on non-nullable property public function __construct(public Type $prop = null) {} // こっちはOK public function __construct(public ?Type $prop = null) {} }可変長引数をpromoteすることはできません。
class Test { // エラー public function __construct(public string ...$strings) {} }理由としては、明示する引数の型(ここではstring)と、実際に渡される引数の型(ここではstringの配列)が異なるからです。
$strings
プロパティをstringの配列にすることも可能ですが、それではわかりづらくなります。promoteプロパティと明示的なプロパティ宣言を組み合わせることは可能です。
またpromoteプロパティとpromoteされない引数を同時に渡すことも可能です。// 正しい class Test { public string $explicitProp; public function __construct(public int $promotedProp, int $normalArg) { $this->explicitProp = (string) $normalArg; } }Desugaring
promoteプロパティはただのシンタックスシュガーであり、全てのpromoteプロパティに対して以下の変換が適用されます。
// シンタックスシュガー class Test { public function __construct(public Type $prop = DEFAULT) {} } // こう展開される class Test { public Type $prop; public function __construct(Type $prop = DEFAULT) { $this->prop = $prop; } }自動的に宣言されるプロパティの可視性と型は、promoteプロパティの可視性および型と同じになります。
注目すべき点は、プロパティにデフォルト値は適用されず(つまり、未初期化で始まります)、コンストラクタ引数でのみ指定されるところです。プロパティ宣言時にもデフォルト値を指定したほうがよいようにも思えますが、将来的にデフォルト値で指定することが望ましくなるであろう理由が存在します。
ひとつめは、プロパティのデフォルト値に任意の式を利用できるようにする拡張の可能性です。
// FROM class Test { public function __construct(public Dependency $prop = new Dependency()) {} } // TO class Test { public Dependency $prop /* = new Dependency() */; public function __construct(Dependency $prop = new Dependency()) { $this->prop = $prop; } }こうなると、プロパティ宣言時とデフォルト値でオブジェクトを2回構築することとなるため望ましくありません。
また、新潟アクセス修正子のルールではプロパティでデフォルト値を宣言すると、コンストラクタで代入することもできなくなります。
promote引数が参照であった場合、プロパティも参照になります。
// FROM class Test { public function __construct(public array &$array) {} } // TO class Test { public array $array; public function __construct(array &$array) { $this->array =& $array; } }promoteプロパティへの引数の割り当ては、コンストラクタの冒頭で行われます。
従って、コンストラクタ内でも引数とプロパティの両方にアクセスすることが可能です。// 動作する class PositivePoint { public function __construct(public float $x, public float $y) { assert($x >= 0.0); assert($y >= 0.0); } } // こっちも動作する class PositivePoint { public function __construct(public float $x, public float $y) { assert($this->x >= 0.0); assert($this->y >= 0.0); } }Reflection
リフレクションおよびその他の解析機構で見ると、シンタックスシュガーを解除した後の状態になります。
すなわち、promoteプロパティは明示的に宣言されたプロパティのように見え、promote引数は通常のコンストラクタ引数のように見える、ということです。PHPは引数に関するDocコメントを公開していませんが、promoteプロパティのDocコメントも保持されます。
class Test { public function __construct( /** @SomeAnnotation() */ public $annotatedProperty ) {} } $rp = new ReflectionProperty(Test::class, 'annotatedProperty'); echo $rp->getDocComment(); // "/** @SomeAnnotation */"この例のように、promoteプロパティではDocコメントベースのアノテーションを使用することができます。
また、2メソッドが追加されます。
・
ReflectionProperty::isPromoted()
は、promoteプロパティであればtrueを返します。
・ReflectionParameter::isPromoted()
は、promote引数であればtrueを返します。プロパティがpromoteされたかどうかを気にする場面はほとんど存在しないと思われますが、この情報によって元のコードをより簡単に再構築することができます。
Inheritance
オブジェクト初期化子は継承することができますが、特に特筆すべきようなことはありません。
abstrautを含む典型的な継承のユースケースを以下に示します。abstract class Node { public function __construct( protected Location $startLoc = null, protected Location $endLoc = null, ) {} } class ParamNode extends Node { public function __construct( public string $name, public ExprNode $default = null, public TypeNode $type = null, public bool $byRef = false, public bool $variadic = false, Location $startLoc = null, Location $endLoc = null, ) { parent::__construct($startLoc, $endLoc); } }ParamNodeクラスでいくつかのpromoteプロパティを宣言し、さらに二つの普通の引数を親コンストラクタに転送しています。
これは以下のように展開されます。abstract class Node { protected Location $startLoc; protected Location $endLoc; public function __construct( Location $startLoc = null, Location $endLoc = null, ) { $this->startLoc = $startLoc; $this->endLoc = $endLoc; } } class ParamNode extends Node { public string $name; public ExprNode $default; public TypeNode $type; public bool $byRef; public bool $variadic; public function __construct( string $name, ExprNode $default = null, TypeNode $type = null, bool $byRef = false, bool $variadic = false, Location $startLoc = null, Location $endLoc = null, ) { $this->name = $name; $this->default = $default; $this->type = $type; $this->byRef = $byRef; $this->variadic = $variadic; parent::__construct($startLoc, $endLoc); } }プロパティへの代入は、親コンストラクタが呼ばれる前に行われることに注意してください。
これはコーディングスタイルとして一般的ではありませんが、動作に影響が出るようなことはほぼありません。Attributes
PHP8ではアトリビュートも導入されるため、相互作用を考慮する必要があります。
アトリビュートは、プロパティと引数の両方で使用することができます。class Test { public function __construct( <<ExampleAttribute>> public int $prop, ) {} }このコードがどのように解釈されるか決める必要があります。
1. アトリビュートは引数にのみ適用する。
2. アトリビュートはプロパティにのみ適用する。
3. アトリビュートは引数とプロパティの両方に適用する。
4. 曖昧さを避けるためエラーにする// Option 1: アトリビュートは引数にのみ適用する class Test { public int $prop; public function __construct( <<ExampleAttribute>> int $prop, ) {} } // Option 2: アトリビュートはプロパティにのみ適用する class Test { <<ExampleAttribute>> public int $prop; public function __construct( int $prop, ) {} } // Option 3: アトリビュートは引数とプロパティの両方に適用する class Test { <<ExampleAttribute>> public int $prop; public function __construct( <<ExampleAttribute>> int $prop, ) {} } // Option 4: 曖昧さを避けるためエラーにするこのRFCでは3番目、つまり引数とプロパティの両方に適用することを提案しています。
これが最も柔軟性の高い方法だからです。ただし、これは実装に依ると考えています。
PHP8の実装に関わる作業で、アトリビュートをプロパティにのみ配置した方がよいと判明した場合は、そのように変更される場合があります。Coding Style Consideration
このセクションではコーディングスタイルの推奨について解説します。
規程ではありません。promoteプロパティを使用する場合、コンストラクタをクラス最初のメソッドとして、明示的なプロパティ宣言の直後に配置することをお勧めします。
これにより、全ての全てのプロパティが先頭にまとめられ、一目でわかるようになります。
静的メソッドを最初に配置することを要求しているコーディング規約は、コンストラクタを最初に配置するよう規約を調整する必要があります。promoteプロパティに
@param
アノテーションを使用している場合、ドキュメントツールは@var
アノテーションも含まれているものとして解釈されるべきです。// 元のコード class Point { /** * Create a 3D point. * * @param float $x The X coordinate. * @param float $y The Y coordinate. * @param float $z The Z coordinate. */ public function __construct( public float $x = 0.0, public float $y = 0.0, public float $z = 0.0, ) {} } // こう解釈する class Point { /** * @var float $x The X coordinate. */ public float $x; /** * @var float $y The Y coordinate. */ public float $y; /** * @var float $z The Z coordinate. */ public float $z; /** * Create a 3D point. * * @param float $x The X coordinate. * @param float $y The Y coordinate. * @param float $z The Z coordinate. */ public function __construct( float $x = 0.0, float $y = 0.0, float $z = 0.0, ) { $this->x = $x; $this->y = $y; $this->z = $z; } }最後に、promoteプロパティは、あくまで一般的なケースをカバーするための便利な省略記法であるに過ぎないことに注意してください。
promoteプロパティはいつでも明示的なプロパティに書き換えることができます。
そのため、この変更は下位互換性を壊すことはありません。Backward Incompatible Changes
下位互換性のない変更はありません。
Future Scope
Larryが、この機能と他の機能を組み合わせることによってオブジェクト初期化を改善する方法について、より深いビジョンを提供しています。
Prior Art
この機能、あるいは類似した機能は多くの言語でサポートされています。
・Hack
・TypeScript
・Kotlin先行するRFCが存在します。
・Automatic property initialization プロパティ宣言は必要とする、より弱い形です。
・Constructor Argument Promotion このRFCとほとんど同じです。
・Code free constructor Kotlinの文法に基づいています。Vote
投票期間は2020/05/29まで、2/3+1の賛成が必要です。
このRFCは賛成46反対10で受理されました。感想
プロパティを書くのが格段に楽になりますね。
後から見るときにプロパティが宣言されているのかどうかちょっとわかりにくそうですが、この機能が使えるのはコンストラクタだけなのでそこだけ抑えていれば大丈夫でしょう。
コンストラクタだけではなく任意のメソッドで使えると便利では、と一瞬思ったものの、これを無制限に使えると完全に収拾が付かなくなってしまうので、やはりコンストラクタだけに留めておくのが賢明そうですね。
- 投稿日:2020-06-01T17:32:50+09:00
Laravelのタスクスケジューラを使う
前提条件
eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っていますLaravelでコマンドライン処理を行う
本記事は上記の内容を理解している前提で書かれていますCommandクラス作成
コマンドラインで
cd sample
php artisan make:command SampleSchedule
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
eclipseプロジェクトを右クリック→リフレッシュ
/sample/app/Console/Commands/SampleSchedule.phpが現れますCommandクラス修正
さきほど作成したSampleSchedule.phpを下記に修正します
SampleSchedule.php<?php namespace App\Console\Commands; use Illuminate\Console\Command; class SampleSchedule extends Command { protected $signature = 'sample:name2 {arg1}'; protected $description = 'SampleSchedule'; public function __construct() { parent::__construct(); } public function handle() { $arg1 = $this->argument('arg1'); $this->line('arg1:' . $arg1); if ($arg1 === 'a') { return 1; } return 0; } }Commandクラス登録
/sample/app/Console/Kernel.php修正
Kernel.php‥‥ protected $commands = [ ‥‥ ,Commands\SampleSchedule::class ]; ‥‥Kernel.phpに定義されている$commands配列に先ほど作成したSampleScheduleクラス名を追記します
スケジュール作成
(1) /sample/app/Console/Kernel.phpに下記use文を追記
use Illuminate\Support\Facades\Log;
(2) /sample/app/Console/Kernel.phpのscheduleメソッドを修正します
Kernel.phpprotected function schedule(Schedule $schedule) { $schedule->command('sample:name2 0') ->everyMinute() ->appendOutputTo(dirname(dirname(dirname(__FILE__))) . '/storage/logs/SampleSchedule.log') ->onSuccess(function () { Log::info('成功'); }) ->onFailure(function () { Log::error('エラー'); }) ; $schedule->command('sample:name2 a') ->everyMinute() ->appendOutputTo(dirname(dirname(dirname(__FILE__))) . '/storage/logs/SampleSchedule.log') ->onSuccess(function () { Log::info('成功'); }) ->onFailure(function () { Log::error('エラー'); }); $schedule->command('sample:name2 z') ->dailyAt('10:00') ->appendOutputTo(dirname(dirname(dirname(__FILE__))) . '/storage/logs/SampleSchedule.log') ->onSuccess(function () { Log::info('成功'); }) ->onFailure(function () { Log::error('エラー'); }) ; }command関数は実行するコマンドクラスを指定します。引数はCommandクラスのsignature変数に定義した通りに書きます。php artisanコマンドでCommandクラスを実行するときと同じものになります[Laravelでコマンドライン処理を行う]。
everyMinute関数は毎分実行する指定になります
appendOutputTo関数はCommandクラスからの出力先を指定したファイルにするものです
onSuccess関数はCommandクラスから戻り値が0の場合に実行される処理を指定します。ただし、動作確認したLaravelバージョン7.1.1ではCommandクラスをバックグラウンド実行すると戻り値が0でもonSuccessが実行されないです。バックグラウンド実行はメソッドチェーンでrunInBackground関数を指定するとできるようになります。
onFailure関数はCommandクラスから戻り値が1の場合に実行される処理を指定します。ただし、動作確認したLaravelバージョン7.1.1ではCommandクラスをバックグラウンド実行すると戻り値が何であってもonFailureが実行されます。バックグラウンド実行はメソッドチェーンでrunInBackground関数を指定するとできるようになります。
dailyAt関数は毎日決まった時間にCommandクラスを実行する指定になります
everyMinute関数やdailyAt関数といった実行日時を指定する関数はいろいろ用意されています。下記で確認できます
Laravel 7.x タスクスケジュール 繰り返しのスケジュールオプション
動作確認
コマンドラインで
cd sample
php artisan schedule:run
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します/sample/storage/logs/SampleSchedule.logにSampleSchedule.phpからの出力が書き込まれています
/sample/storage/logs/laravel.logに/sample/app/Console/Kernel.phpのscheduleメソッド内にonSuccess、onFailureで指定したクロージャからの出力が書き込まれていますまた、
php artisan schedule:run
を実行したウインドウを見ると
php "artisan" sample:name2 0
php "artisan" sample:name2 a
が実行されたことがわかります
さらに、実行した時刻が10:00の場合、php "artisan" sample:name2 zも実行されているでしょうcron登録とスケジューラとしての使い方
先ほどコマンドラインで/sample/app/Console/Kernel.phpのscheduleメソッドを実行してみました
これをcronに指定します
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
/path-to-your-projectというのはご自身のLaravelプロジェクトへの絶対パスに書き換えてください
この記事であればsampleフォルダになります
このcronの設定を見ると分、時、日、月、曜すべてアスタリスクになっていることがわかります
これは1分に1回、要は毎分php artisan schedule:run実行されるという設定になります
つまり、毎分php artisan schedule:run
が実行されて、その際、実行日時に合うタスクスケジュール繰り返しのスケジュールオプションが設定されているcommandだけが実行されるという仕組みです先ほどコマンドラインで
php artisan schedule:run
を実行した際、everyMinute指定したものが実行されて(ちょうど10:00に実行しなければ)dailyAt指定したものは実行されなかったと思います
これはphp artisan schedule:runが毎分実行されるようにつくられているからです
- 投稿日:2020-06-01T16:24:08+09:00
【Laravel + GraphQL】Lighthouseを使ってみる【その1:チュートリアル】
Lighthouseの設計思想が何もわからん&そもそもディレクティブ全然わからん状態を脱却したいので、練習として公式のドキュメントを上から順番にやっていくことにしました。
環境
PHP: 7.2.5
Laravel: 7.0
Lighthouse: 4.13チュートリアル
デフォルトのスキーマを実行できるようにする
何はともかくインストールします。
composer require nuwave/lighthouseそうしたらまずはデフォルトのスキーマを試してみましょう。
php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=schemaこのコマンドを実行すると以下のように
users
とuser
スキーマが生成されます。src/graphql/schema.graphql"A date string with format `Y-m-d`, e.g. `2011-05-23`." scalar Date @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date") "A datetime string with format `Y-m-d H:i:s`, e.g. `2018-05-23 13:43:32`." scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime") "A datetime and timezone string in ISO 8601 format `Y-m-dTH:i:sO`, e.g. `2020-04-20T13:53:12+02:00`." scalar DateTimeTz @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTimeTz") type Query { users: [User!]! @paginate(defaultCount: 10) user(id: ID @eq): User @find } type User { id: ID! name: String! email: String! created_at: DateTime! updated_at: DateTime! }スキーマが生成されたので適当にusersテーブルにデータを入れて以下のようにcurlを叩いてみましょう。
curl --request POST \ --url http://localhost:10080/graphql \ --header 'content-type: application/json' \ --data '{"query":"query {\n users {\n paginatorInfo {\n count\n currentPage\n }\n data {\n id\n name\n email\n created_at\n updated_at\n }\n }\n}"}'するとたったこれだけでデータベースの情報を取得することができました。
しかもペジネータも自動でついてます。{ "data": { "users": { "paginatorInfo": { "count": 1, "currentPage": 1 }, "data": [ { "id": "1", "name": "hgoe", "email": "hoge@example.com", "created_at": "2020-05-31 14:04:07", "updated_at": "2020-05-31 14:04:11" } ] } } }CORSの有効化
CORSを有効化するのを忘れないようにしましょう。
config/cors.phpreturn [ - 'paths' => ['api/*'], + 'paths' => ['api/*', 'graphql'], 'allowed_methods' => ['*'], 'allowed_origins' => explode(',', env('ALLOWED_CORS_ORIGINS', [])), ... ];開発用ツール
Lighthouseを便利に開発するために、公式ではGraphQL Playgroundの使用を推奨しています。
しかしこのツール、Dockerを使っているとめっちゃメモリ食ってまともに動かなくなることが稀によくあります。
なので個人的にはGraphQLをサポートしているInsomniaやAltair GraphQL Clientがおすすめです。
特にInsomniaは無料にも関わらず多彩な機能が搭載されており、とても便利なので現状使用している Rest Clientに不満がある方は是非とも触ってみることをおすすめします。
IDEサポート
Lighthouseでは独自定義されたディレクティブが多用されているようです。
なのでこれらをIntelliJ(PhpStrom)やVS Codeでも認識できるようにするために、IDEヘルパーを導入します。composer require --dev haydenpierce/class-finder php artisan lighthouse:ide-helperこれによって
schema-directives.graphql
と_lighthouse_ide_helper.php
が生成されるので、それぞれ.gitignore
に入れておきましょう。ディレクティブ
ここからはLighthouseの主機能となるディレクティブについて説明していきます。
基本的にLaravelのModelとGraphQLのオブジェクト(type)は一対一で自動でマッピングされます。
またtypeを定義する際は主キーに対応するフィールドにはid
という命名をすることが推奨されています。type User { id: ID! name: String! email: String! }@all
@allディレクティブはEloquentの
all()
と同じ役割をします。type Query { users: [User!]! @all }以下のようにスキーマを叩くと、以下のようにUserモデルに紐付けられているテーブル(今回はLaravelデフォルトのusersテーブル)の全ての情報が返ってきます。
query { user_all { id name email } }{ "data": { "users": [ {"id": "1", "name": "hoge", "email": "hoge@example.com"}, {"id": "2", "name": "fuga", "email": "fuga@example.com"} ] } }@paginate
paginateディレクティブは名前の通り、ペジネーションの役割を果たします。
type Query { users: [User!]! @paginate }例えば上記のpaginateディレクティブを付与したスキーマは内部的には自動で次のように変換されます。
type Query { users(first: Int!, page: Int): PostPaginator } type PostPaginator { data: [User!]! paginatorInfo: PaginatorInfo! }そして以下のように叩くことができるようになります。
{ users(first: 10) { data { id name } paginatorInfo { currentPage lastPage } } }またpaginatorには他にもいくつかの機能があります。
デフォルト数の指定
一度のリクエストで返すデフォルトのアイテム数をクエリ時に
count
引数で指定することなく、デフォルトで指定できるようになります。type Query { users: [User!]! @paginate(defaultCount: 10) }最大数の制限
ページ分割する際に要求できるアイテムの最大数を制限することができます。
type Query { users: [User!]! @paginate(maxCount: 10) }対象モデルの上書き
デフォルトではスキーマ定義で指定されたtypeと同じ名前のEloquentモデルを検索しますが、model引数を指定することでそれを上書きすることができます。
type Query { users: [User!]! @paginate(model: "App\\Models\\ActiveUser") }@find
findディレクションは渡された引数からモデル内を検索します。
Eloquentでいうwhereとfindですね。type Query { user(id: ID @eq): User @find }注意点として複数の結果が得られた場合例外が投げられるので、確実ではない場合は
@first
の使用が推奨されています。またこちらもpaginateと同様にmodelの上書きをすることが可能です。
type Query { user(id: ID @eq): User @find(model: "App\\Models\\ActiveUser") }@eq
Eloquentクエリに等号演算子を配置するディレクティブです。
以下の例だと引数のidの値がusersテーブルのidと一致するものを検索します。
type Query { user(id: ID @eq): User @find }また引数の名前がデータベースのカラム名と異なる場合は実際の列名を
key
に渡します。type Query { user(id: ID @eq(key: "user_id")): User @find }@create
新規のデータを登録するにはcreateディレクティブを使用することができます。
type Mutation { createUser(name: String!, email: String!, password: String!): User! @create }Mutation typeの中でスキーマを作成し、その引数に保存したいデータを指定するだけでデータベースに保存することができます。
mutation { createUser( name: "hogehoge" email: "hogehoge@example.com" password: "hogehoge" ) { id name email created_at updated_at } }ただし注意点として、作成や更新を許可するカラムに対してModelに
fillable
を指定する必要があります。(updateディレクティブに関しても同様)User.phpclass User extends Authenticatable { ... protected $fillable = [ 'name', 'email', 'password', ]; }またcreateディレクティブでも使用するmodelを変更することが可能です。
type Mutation { createUser(input: CreateUserInput! @spread): User! @create(model: "App\\Models\\ActiveUser") }独自のリクエスト型を定義する
特定のリクエスト型の定義は
input
を指定することで可能です。引数として単一のオブジェクトを使用する場合はリゾルバーに適応する前にネストした値を展開するように@spreadを使用する必要があります。
type Mutation { createUser(input: CreateUserInput! @spread): User! @create } input CreateUserInput { name: String! email: String! password: String! }@update
データの更新にはupdateディレクティブを使用します。
id
で指定されたデータに対して第二引数以降の値で更新します。type Mutation { updateUser(id: ID!, name: String, email: String): User @update }ちなみに動作的には引数を指定しないと更新されず、nullや空文字を送信するとそれぞれの値で上書きされます。
上書きされたくない場合はnot nullやバリデーションを設定するようにしましょう。またGraphQLでは一部のデータのみを更新するかをクライアント側が選択できるので、サーバ側ではオプションの引数を除く全ての引数を指定することが推奨されています。
もちろんupdateディレクティブでも独自のリクエスト型を定義して使用することが可能です。
type Mutation { updateUser(id: ID!, input: UpdateUserInput @spread): User @update } input UpdateUserInput { name: String email: String password: String }@upsert
upsertは
id
で指定されたデータが存在すればupdateし、なければ指定されたid
で新規にデータを作成します。
またid
が指定されていない場合は自動生成されたIDを使用してデータを作成します。type Mutation { upsertUser(id: ID!, name: String!, email: String!, password: String!): User @upsert }この時DB上でnot nullなカラムをGraphQLのスキーマ上でnullableにしても新規作成時にnullの値を入れてリクエストできてしまいますが、エラーになるので注意しましょう。(updateは問題なく動きます。)
@delete
deleteは
id
を指定するだけで簡単にデータを削除できる、ある意味危険なディレクティブです。type Mutation { deleteUser(id: ID!): User @delete }返り値は削除したデータがあればそれを返し、指定した
id
が対象となるデータがない場合はnullを返します。複数削除
複数データを一度に削除したい場合はIDをリストにします。
type Mutation { deleteUser(id: [ID!]!): [User!]! @delete }まとめ
今回は基本的なCRUD操作に必要なディレクティブについて学習しました。
自分でスキーマを定義していると返り値や引数周りの設計で悩むことが結構多かったのですが、そこらへんがサンプルで明示的に示されていてとても勉強になりました。ここまでやった感想としては複雑なビジネスロジックなどが必要のない簡単なアプリならSQLやModelなどを意識する必要がほとんどなく、ほとんどサーバの技術を触ったことがない人でもさっくり作れそうな印象を受けました。
流石に複雑なロジックを入れようとするとディレクティブだけでは難しいのでResolverを使っていくことになりますが、それでもペジネータなどが準備されているのは嬉しいですね。
(キャッシュ周りに関して把握してないので、どのようにデータを持つのかは気になりますが…)次回はResolverやリレーションなどについて調べていきたいと思います。
参考文献
- 投稿日:2020-06-01T12:10:19+09:00
Google Photos APIをPHPで使う
Google Photos APIを使用して、Google Photoにアクセスするアプリを作成しています。
その過程でのAPIを利用するまでを記載します。基本的にオフィシャルのリファレンスを参照していますが、そこから読み取りにくい部分などを具体的に紹介できたらと思っております。
Laravelを使用したコードを記載していますが、Laravelに特化した部分は少ないと思いますので、
Laravel以外のフレームワークを使っている場合もご参考になればと思います。準備
Laravelプロジェクト作成
$ composer create-project laravel/laravel googlephoto_sample --prefer-distGoogle Photos APIを有効にする
- Google APIsのダッシュボードからプロジェクト作成 → APIとサービスを有効化 → Google Photos APIを選択
- Google APIsのプロジェクトが作成済みなら Google Photos API このページから直接もできます
クライアントID作成
- Google APIsのダッシュボードから「認証情報」を選択 → 認証情報を作成 → OAuthクライアントID
- アプリケーションの種類は、ウェブアプリケーション
- 承認済みのリダイレクトURIは、今回は取り合えず http://localhost/photo/redirect
- 作成したクライアントIDの画面からJSONをダウンロード → credential.jsonにリネームしておく
- ダウンロードしたcredential.jsonをLaravelプロジェクトのフォルダに入れておきます。
Google APIs Client, Google Photos API ライブラリのインストール
composer.json"require": { "google/apiclient": "^2.0", "google/photos-library": "^1.5" },$ composer update
composer.jsonを編集せずに、直接コマンドラインかr
composer require google/apiclient:"^2.0"
みたいにやるとなぜか失敗します。Google Photos APIを使ってみる
PhotoControllerという名前のコントローラーと、コントローラーから呼び出されるGooglePhotoServiceという名前のクラスを作成していきます。
認証URLの生成、表示
app\Services\GooglePhotoService.php<?php namespace App\Services; use Google\Auth\Credentials\UserRefreshCredentials; use Google\Photos\Library\V1\PhotosLibraryClient; class GooglePhotoService { private function createGoogleClient() { $client = new \Google_Client(); $client->setApplicationName('GooglePhotoSample'); $client->setScopes(\Google_Service_PhotosLibrary::PHOTOSLIBRARY_READONLY); $client->setAuthConfig(base_path("credentials.json")); $client->setAccessType('offline'); return $client; } public function createAuthUrl($redirectUri) { $client = $this->createGoogleClient(); $client->setRedirectUri($redirectUri); return $client->createAuthUrl(); } }app\Http\Controllers\PhotoController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Services\GooglePhotoService; class PhotoController extends Controller { public function start() { $service = new GooglePhotoService(); $url = $service->createAuthUrl("http://localhost/photo/redirect"); return view(start, [ 'url' => $url ]); } }resources\views\start.blade.php<html> <body> <div class="flex-center position-ref full-height"> <div class="content"> Step.1<br/> <a href="{{$url}}">{{$url}}</a> </div> </div> </body> </html>routes\web.phpRoute::get('/photo/start, 'PhotoController@start);OAuthのリダイレクト先を作成
リダイレクト時に受け取ったauthCodeをもとにアクセストークンを取得する。
取得したアクセストークンを保存する処理は$saveTokenFunction
という名前のコールバック関数として定義する。app\Services\GooglePhotoService.phppublic $saveTokenFunction = null; public function getAccessToken($authCode) { $client = $this->createGoogleClient(); $accessToken = $client->fetchAccessTokenWithAuthCode($authCode); if ($this->saveTokenFunction != null) { call_user_func($this->saveTokenFunction, $accessToken); } return $accessToken; }app\Http\Controllers\PhotoController.phppublic function saveToken($accessToken) { // 保存の処理は省略。データベースなどに保存 } public function redirect(Request $request) { $authCode = $request->input('code'); $service = new GooglePhotoService(); $service->saveTokenFunction = [$this, 'saveToken']; $service->getAccessToken($authCode); return view('redirect', [ ]); }resources\views\redirect.blade.php// 省略
routes\web.phpRoute::get('/photo/redirect, 'PhotoController@redirect);PhotosLibraryClientを生成
取得したアクセストークンで、PhotosLibraryClientを生成
アクセストークンの期限が切れたときのリフレッシュの処理も一緒に実装app\Services\GooglePhotoService.phppublic function getPhotosLibraryClient($accessToken) { $client = $this->createGoogleClient(); $client->setAccessToken(json_decode($accessToken, true)); if ($client->isAccessTokenExpired()) { $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken()); if ($this->saveTokenFunction != null) { call_user_func($this->saveTokenFunction, $$client->getAccessToken()); } } $authCredentials = new UserRefreshCredentials(\Google_Service_PhotosLibrary::PHOTOSLIBRARY_READONLY, [ "client_id" => $client->getClientId(), "client_secret" => $client->getClientSecret(), "refresh_token" => $client->getRefreshToken() ]); return new PhotosLibraryClient(['credentials' => $authCredentials]); }PhotosLibraryClientを使用してGoogle Photoからアルバム情報を取得してみる
app\Http\Controllers\PhotoController.phppublic function listAlbums() { $accessToken = [] // 保存してあるアクセストークンを取得する処理 // 例) $accessToken = UserToken::where('user_id', auth()->user()->id)->value('token'); $service = new GooglePhotoService(); $service->saveTokenFunction = [$this, 'saveToken']; $client = $service->getPhotosLibraryClient($accessToken); $pagedResponse = $client->listAlbums(); $data = []; foreach ($pagedResponse->iterateAllElements() as $album) { $data[] = $album->getTitle(); } return response()->json([ "title" => $data ]); }routes\web.phpRoute::get('/photo/albums', 'PhotoController@listAlbums');以上です
authCodeからAccessTokenを取得する処理を、なんとかAPIだけで完結させたかったのですが
できませんでした。今回は、このような形でウェブページを一度経由してその後APIで利用するという形になりました。
- 投稿日:2020-06-01T11:52:51+09:00
#MacでLaravelの環境構築をした件
MacでLaravelの環境構築をした件
MacでLaravelの環境構築をした際の内容をまとめます.
目次
動作環境
OS : macOS Mojave 10.14.6
HOMEBREW_VERSION: 2.2.17
Composer version 1.10.6
Laravel Installer 2.3.0
PHP : 7.2.31
構築手順
homebrewのインストール
Homebrewはmacのパッケージマネージャです。
パスワードを求められたらMacのログインパスワードを適宜入力してください$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"インストールが完了したらアップデートをして完了です.
$ brew upgrade
composerのインストール
brew install composer
インストールが完了したらPATHを追加し、インストールできているか確認します.
PATHの追加とバージョン確認$ echo export PATH=\"$HOME/.composer/vendor/bin:\$PATH\" >> ~/.bash_profile $ source .bash_profile $ composer -V Composer version 1.10.6 2020-05-06 10:28:10phpのインストール
PHPのバージョンを確認
$ php -v PHP 7.1.23 (cli) (built: Feb 22 2019 22:19:32) ( NTS )注意点
7.1系でLaravel newを行うと以下のエラーが出たため7.2以上をインストールします.
Your requirements could not be resolved to an installable set of packages. Problem 1 - This package requires php ^7.2.5 but your PHP version (7.1.23) does not satisfy that requirement.7.2系のインストール
brew install php@7.2
PATHの追加とバージョン確認$ echo 'export PATH="/usr/local/opt/php@7.2/bin:$PATH"' >> ~/.bash_profile $ echo 'export PATH="/usr/local/opt/php@7.2/sbin:$PATH"' >> ~/.bash_profile $ source ~/.bash_profile $ php -v PHP 7.2.31 (cli) (built: May 14 2020 10:55:21) ( NTS )laravelのインストール
laravelのインストール$ composer global require "laravel/installer"注意点
WEBサイトで以下のコマンドを書いていることがありましたが、このコマンドではエラーになります.詳細はこちらをご参考ください
誤$ composer global require “laravel/install”
動作確認
最後にアプリを作成するディレクトリに移動し実行
$ laravel new dg_laravel_test
サーバーを起動
$ php artisan serve Laravel development server started: http://127.0.0.1:8000 [Tue May 19 16:52:04 2020] 127.0.0.1:56892 [200]: /favicon.icoブラウザから
http://127.0.0.1:8000
にアクセス
以下の画面がでれば成功おわりに
今回の件で以下のことを学びました。
1. Mac OSでのLaravelの環境構築
- 投稿日:2020-06-01T11:38:32+09:00
containすると全件表示されない。
DBを直接みると見れるデータが関連ターブルをcontainするとfindで取得できない時の解決法。
/src/Model/Table内の「****Table.php」を修正することで解決しました。
bakeを使ってモデルを生成したときに、joinTypeがINNERになっているのでLEFTに書き換え。$this->belongsTo('Authorities', [ 'foreignKey' => 'authority_id', 'joinType' => 'INNSER', ]);↓↓
$this->belongsTo('Authorities', [ 'foreignKey' => 'authority_id', 'joinType' => 'LEFT', ]);
- 投稿日:2020-06-01T11:14:46+09:00
PHP Slim3フレームワークのサンプルアプリを作ろう(2-1. First Application Walkthrough Getting Set Upまで)
はじめに
PHP Slim3フレームワークの勉強のため、
Slim公式のユーザーガイドにあるFirst Application Walkthroughのサンプルアプリを作成します。
まずは、ビルトインWebサーバーで表示させるところまで。今回は、skeleton projectを使用しないバージョンです。
前提
下記記事で構築した環境を前提とします。
- Windows10にVagrantをを入れてCentOS7をインストールしよう(1、2、3、4、5、6)
- ローカルでLAMP環境を構築しよう(0、1、2、3、4)
- CentOS7にComposerをインストールしよう
使用ツール
- Tera Term
手順
1. 専用のユーザーを作成
2. プロジェクトディレクトリを作成
3. Slim Frameworkをインストール
4. アプリケーションを作成
5. アプリケーションを動作させてみるやってみよう
1. 専用のユーザーを作成
前回の記事と同じ環境で行う方はこの工程はスルーでOKです。
Composerを
root
ユーザーで操作することは推奨されていないので、
以下のコマンドでslimuser
というユーザーを作成します。useradd slimuser作成した
slimuser
に以下のコマンドで切り替えます。su slimuser2. プロジェクトディレクトリを作成
slimuserユーザーが作成されると
/home/
ディレクトリ内にslimuser
ディレクトリが作成されます。
その中に以下のプロジェクトディレクトリを作成します。/home/slimuser/projects/slim/FirstApplicationFirstApplicationの下に以下のようにディレクトリを作成します。
必ずしもこのディレクトリ構造ではなくてもいいようですが、
今回はユーザーガイドに倣います。. ├── FirstApplication │ └── src │ └── public3. Slim Frameworkをインストール
上記で作成した
src
ディレクトリまで移動します。cd /home/slimuser/projects/slim/FirstApplication/src以下のコマンドを実行します。
composer require slim/slim:3.*
src
ディレクトリ内には以下のようなファイル群が生成されます。[slimuser@localhost src]$ ls -la total 16 drwxrwxr-x 4 slimuser slimuser 76 Jun 1 00:30 . drwxrwxr-x 3 slimuser slimuser 17 Jun 1 00:28 .. -rw-rw-r-- 1 slimuser slimuser 54 Jun 1 00:30 composer.json -rw-rw-r-- 1 slimuser slimuser 9700 Jun 1 00:30 composer.lock drwxrwxr-x 2 slimuser slimuser 23 Jun 1 01:10 public drwxrwxr-x 7 slimuser slimuser 92 Jun 1 00:30 vendor4. アプリケーションを作成
src
ディレクトリの下のpublic
ディレクトリに、index.php
ファイルを作成します。vi public/index.php以下のように記述してください。
First Application Walkthroughでは、
5行目をrequire '../vendor/autoload.php';
と記述していましたが、
同じようにするとなぜかエラーが出てしまったので、
require '/home/slimuser/projects/slim/FirstApplication/src/vendor/autoload.php';
のようにフルパスで記述しました。index.php<?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; require '/home/slimuser/projects/slim/FirstApplication/src/vendor/autoload.php'; $app = new \Slim\App; $app->get('/hello/{name}', function (Request $request, Response $response, array $args) { $name = $args['name']; $response->getBody()->write("Hello, $name"); return $response; }); $app->run();5. アプリケーションを動作させてみる
今回は、PHPのビルトインWebサーバーを使用して表示します。
以下のコマンドを実行します。
192.168.33.60
部分は各自の環境に置き換えてください。php -S 192.168.33.60:8080 -t public public/index.php下記が表示されたらブラウザで
http://192.168.33.60:8080/hello/world
を開いてみてください。[slimuser@localhost src]$ php -S 192.168.33.60:8080 -t public public/index.php PHP 7.1.33 Development Server started at Mon Jun 1 02:01:37 2020 Listening on http://192.168.33.60:8080 Document root is /home/slimuser/projects/slim/FirstApplication/src/public Press Ctrl-C to quit."Hello, world"と表示されました。
"world"を"cat"に書き換えて表示すると、表示が変わります。
http://192.168.33.60:8080/hello/cat
参考サイト
関連ページ
Windows10にVagrantをを入れてCentOS7をインストールしよう
1. VagrantインストールからVagrantfileを設置まで
2. 仮想マシンの操作
3. WinSCP、Tera Termに秘密鍵でログイン
4. WinSCP、Tera Termにrootユーザーでパスワードログイン
5. zip/unzipをインストール
6. Vagrantにて仮想環境を配布ローカルでLAMP環境を構築しよう
0. 事前準備
1. Apacheをインストール
2. MySQLをインストール
3. PHPをインストール
4. ファイアウォールとか停止するComposerをインストール
PHP Slim3フレームワークのサンプルアプリを作ろう
1. skeleton project
2-1. First Application Walkthrough Getting Set Upまで
- 投稿日:2020-06-01T11:14:46+09:00
PHP Slim3フレームワークのサンプルアプリを作ろう(2-1. First Application Walkthrough)
はじめに
PHP Slim3フレームワークの勉強のため、
Slim公式のユーザーガイドにあるFirst Application Walkthroughのサンプルアプリを作成します。
まずは、ビルトインWebサーバーで表示させるところまで。今回は、skeleton projectを使用しないバージョンです。
前提
下記記事で構築した環境を前提とします。
- Windows10にVagrantをを入れてCentOS7をインストールしよう(1、2、3、4、5、6)
- ローカルでLAMP環境を構築しよう(0、1、2、3、4)
- CentOS7にComposerをインストールしよう
使用ツール
- Tera Term
手順
1. 専用のユーザーを作成
2. プロジェクトディレクトリを作成
3. Slim Frameworkをインストール
4. アプリケーションを作成
5. アプリケーションを動作させてみるやってみよう
1. 専用のユーザーを作成
前回の記事と同じ環境で行う方はこの工程はスルーでOKです。
Composerを
root
ユーザーで操作することは推奨されていないので、
以下のコマンドでslimuser
というユーザーを作成します。useradd slimuser作成した
slimuser
に以下のコマンドで切り替えます。su slimuser2. プロジェクトディレクトリを作成
slimuserユーザーが作成されると
/home/
ディレクトリ内にslimuser
ディレクトリが作成されます。
その中に以下のプロジェクトディレクトリを作成します。/home/slimuser/projects/slim/FirstApplicationFirstApplicationの下に以下のようにディレクトリを作成します。
必ずしもこのディレクトリ構造ではなくてもいいようですが、
今回はユーザーガイドに倣います。. ├── FirstApplication │ └── src │ └── public3. Slim Frameworkをインストール
上記で作成した
src
ディレクトリまで移動します。cd /home/slimuser/projects/slim/FirstApplication/src以下のコマンドを実行します。
composer require slim/slim:3.*
src
ディレクトリ内には以下のようなファイル群が生成されます。[slimuser@localhost src]$ ls -la total 16 drwxrwxr-x 4 slimuser slimuser 76 Jun 1 00:30 . drwxrwxr-x 3 slimuser slimuser 17 Jun 1 00:28 .. -rw-rw-r-- 1 slimuser slimuser 54 Jun 1 00:30 composer.json -rw-rw-r-- 1 slimuser slimuser 9700 Jun 1 00:30 composer.lock drwxrwxr-x 2 slimuser slimuser 23 Jun 1 01:10 public drwxrwxr-x 7 slimuser slimuser 92 Jun 1 00:30 vendor4. アプリケーションを作成
src
ディレクトリの下のpublic
ディレクトリに、index.php
ファイルを作成します。vi public/index.php以下のように記述してください。
First Application Walkthroughでは、
5行目をrequire '../vendor/autoload.php';
と記述していましたが、
同じようにするとなぜかエラーが出てしまったので、
require '/home/slimuser/projects/slim/FirstApplication/src/vendor/autoload.php';
のようにフルパスで記述しました。index.php<?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; require '/home/slimuser/projects/slim/FirstApplication/src/vendor/autoload.php'; $app = new \Slim\App; $app->get('/hello/{name}', function (Request $request, Response $response, array $args) { $name = $args['name']; $response->getBody()->write("Hello, $name"); return $response; }); $app->run();5. アプリケーションを動作させてみる
今回は、PHPのビルトインWebサーバーを使用して表示します。
以下のコマンドを実行します。
192.168.33.60
部分は各自の環境に置き換えてください。php -S 192.168.33.60:8080 -t public public/index.php下記が表示されたらブラウザで
http://192.168.33.60:8080/hello/world
を開いてみてください。[slimuser@localhost src]$ php -S 192.168.33.60:8080 -t public public/index.php PHP 7.1.33 Development Server started at Mon Jun 1 02:01:37 2020 Listening on http://192.168.33.60:8080 Document root is /home/slimuser/projects/slim/FirstApplication/src/public Press Ctrl-C to quit."Hello, world"と表示されました。
"world"を"cat"に書き換えて表示すると、表示が変わります。
http://192.168.33.60:8080/hello/cat
参考サイト
関連ページ
Windows10にVagrantをを入れてCentOS7をインストールしよう
1. VagrantインストールからVagrantfileを設置まで
2. 仮想マシンの操作
3. WinSCP、Tera Termに秘密鍵でログイン
4. WinSCP、Tera Termにrootユーザーでパスワードログイン
5. zip/unzipをインストール
6. Vagrantにて仮想環境を配布ローカルでLAMP環境を構築しよう
0. 事前準備
1. Apacheをインストール
2. MySQLをインストール
3. PHPをインストール
4. ファイアウォールとか停止するComposerをインストール
PHP Slim3フレームワークのサンプルアプリを作ろう
1. skeleton project
2-1. First Application Walkthrough
- 投稿日:2020-06-01T11:14:46+09:00
PHP Slim3フレームワークのサンプルアプリを作ろう(2-1. アプリケーションの作成・表示)
はじめに
PHP Slim3フレームワークの勉強のため、
Slim公式のユーザーガイドにあるFirst Application Walkthroughのサンプルアプリを作成します。
まずは、ビルトインWebサーバーで表示させるところまで。今回は、skeleton projectを使用しないバージョンです。
前提
下記記事で構築した環境を前提とします。
- Windows10にVagrantをを入れてCentOS7をインストールしよう(1、2、3、4、5、6)
- ローカルでLAMP環境を構築しよう(0、1、2、3、4)
- CentOS7にComposerをインストールしよう
使用ツール
- Tera Term
手順
1. 専用のユーザーを作成
2. プロジェクトディレクトリを作成
3. Slim Frameworkをインストール
4. アプリケーションを作成
5. アプリケーションを動作させてみるやってみよう
1. 専用のユーザーを作成
前回の記事と同じ環境で行う方はこの工程はスルーでOKです。
Composerを
root
ユーザーで操作することは推奨されていないので、
以下のコマンドでslimuser
というユーザーを作成します。useradd slimuser作成した
slimuser
に以下のコマンドで切り替えます。su slimuser2. プロジェクトディレクトリを作成
slimuserユーザーが作成されると
/home/
ディレクトリ内にslimuser
ディレクトリが作成されます。
その中に以下のプロジェクトディレクトリを作成します。/home/slimuser/projects/slim/FirstApplicationFirstApplicationの下に以下のようにディレクトリを作成します。
必ずしもこのディレクトリ構造ではなくてもいいようですが、
今回はユーザーガイドに倣います。. ├── FirstApplication │ └── src │ └── public3. Slim Frameworkをインストール
上記で作成した
src
ディレクトリまで移動します。cd /home/slimuser/projects/slim/FirstApplication/src以下のコマンドを実行します。
composer require slim/slim:3.*
src
ディレクトリ内には以下のようなファイル群が生成されます。[slimuser@localhost src]$ ls -la total 16 drwxrwxr-x 4 slimuser slimuser 76 Jun 1 00:30 . drwxrwxr-x 3 slimuser slimuser 17 Jun 1 00:28 .. -rw-rw-r-- 1 slimuser slimuser 54 Jun 1 00:30 composer.json -rw-rw-r-- 1 slimuser slimuser 9700 Jun 1 00:30 composer.lock drwxrwxr-x 2 slimuser slimuser 23 Jun 1 01:10 public drwxrwxr-x 7 slimuser slimuser 92 Jun 1 00:30 vendor4. アプリケーションを作成
src
ディレクトリの下のpublic
ディレクトリに、index.php
ファイルを作成します。vi public/index.php以下のように記述してください。
First Application Walkthroughでは、
5行目をrequire '../vendor/autoload.php';
と記述していましたが、
同じようにするとなぜかエラーが出てしまったので、
require '/home/slimuser/projects/slim/FirstApplication/src/vendor/autoload.php';
のようにフルパスで記述しました。index.php<?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; require '/home/slimuser/projects/slim/FirstApplication/src/vendor/autoload.php'; $app = new \Slim\App; $app->get('/hello/{name}', function (Request $request, Response $response, array $args) { $name = $args['name']; $response->getBody()->write("Hello, $name"); return $response; }); $app->run();5. アプリケーションを動作させてみる
今回は、PHPのビルトインWebサーバーを使用して表示します。
以下のコマンドを実行します。
192.168.33.60
部分は各自の環境に置き換えてください。php -S 192.168.33.60:8080 -t public public/index.php下記が表示されたらブラウザで
http://192.168.33.60:8080/hello/world
を開いてみてください。[slimuser@localhost src]$ php -S 192.168.33.60:8080 -t public public/index.php PHP 7.1.33 Development Server started at Mon Jun 1 02:01:37 2020 Listening on http://192.168.33.60:8080 Document root is /home/slimuser/projects/slim/FirstApplication/src/public Press Ctrl-C to quit."Hello, world"と表示されました。
"world"を"cat"に書き換えて表示すると、表示が変わります。
http://192.168.33.60:8080/hello/cat
参考サイト
関連ページ
Windows10にVagrantをを入れてCentOS7をインストールしよう
1. VagrantインストールからVagrantfileを設置まで
2. 仮想マシンの操作
3. WinSCP、Tera Termに秘密鍵でログイン
4. WinSCP、Tera Termにrootユーザーでパスワードログイン
5. zip/unzipをインストール
6. Vagrantにて仮想環境を配布ローカルでLAMP環境を構築しよう
0. 事前準備
1. Apacheをインストール
2. MySQLをインストール
3. PHPをインストール
4. ファイアウォールとか停止するComposerをインストール
PHP Slim3フレームワークのサンプルアプリを作ろう
1. skeleton project
2-1. アプリケーションの作成・表示
- 投稿日:2020-06-01T11:14:46+09:00
PHP Slim3フレームワークのサンプルアプリを作ろう(2-1. Getting Set Up)
はじめに
PHP Slim3フレームワークの勉強のため、
Slim公式のユーザーガイドにあるFirst Application Walkthroughのサンプルアプリを作成します。
まずは、ビルトインWebサーバーで表示させるところまで。今回は、skeleton projectを使用しないバージョンです。
前提
下記記事で構築した環境を前提とします。
- Windows10にVagrantをを入れてCentOS7をインストールしよう(1、2、3、4、5、6)
- ローカルでLAMP環境を構築しよう(0、1、2、3、4)
- CentOS7にComposerをインストールしよう
使用ツール
- Tera Term
手順
1. 専用のユーザーを作成
2. プロジェクトディレクトリを作成
3. Slim Frameworkをインストール
4. アプリケーションを作成
5. アプリケーションを動作させてみるやってみよう
1. 専用のユーザーを作成
前回の記事と同じ環境で行う方はこの工程はスルーでOKです。
Composerを
root
ユーザーで操作することは推奨されていないので、
以下のコマンドでslimuser
というユーザーを作成します。useradd slimuser作成した
slimuser
に以下のコマンドで切り替えます。su slimuser2. プロジェクトディレクトリを作成
slimuserユーザーが作成されると
/home/
ディレクトリ内にslimuser
ディレクトリが作成されます。
その中に以下のプロジェクトディレクトリを作成します。/home/slimuser/projects/slim/FirstApplicationFirstApplicationの下に以下のようにディレクトリを作成します。
必ずしもこのディレクトリ構造ではなくてもいいようですが、
今回はユーザーガイドに倣います。. ├── FirstApplication │ └── src │ └── public3. Slim Frameworkをインストール
上記で作成した
src
ディレクトリまで移動します。cd /home/slimuser/projects/slim/FirstApplication/src以下のコマンドを実行します。
composer require slim/slim:3.*
src
ディレクトリ内には以下のようなファイル群が生成されます。[slimuser@localhost src]$ ls -la total 16 drwxrwxr-x 4 slimuser slimuser 76 Jun 1 00:30 . drwxrwxr-x 3 slimuser slimuser 17 Jun 1 00:28 .. -rw-rw-r-- 1 slimuser slimuser 54 Jun 1 00:30 composer.json -rw-rw-r-- 1 slimuser slimuser 9700 Jun 1 00:30 composer.lock drwxrwxr-x 2 slimuser slimuser 23 Jun 1 01:10 public drwxrwxr-x 7 slimuser slimuser 92 Jun 1 00:30 vendor4. アプリケーションを作成
src
ディレクトリの下のpublic
ディレクトリに、index.php
ファイルを作成します。vi public/index.php以下のように記述してください。
First Application Walkthroughでは、
5行目をrequire '../vendor/autoload.php';
と記述していましたが、
同じようにするとなぜかエラーが出てしまったので、
require '/home/slimuser/projects/slim/FirstApplication/src/vendor/autoload.php';
のようにフルパスで記述しました。index.php<?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; require '/home/slimuser/projects/slim/FirstApplication/src/vendor/autoload.php'; $app = new \Slim\App; $app->get('/hello/{name}', function (Request $request, Response $response, array $args) { $name = $args['name']; $response->getBody()->write("Hello, $name"); return $response; }); $app->run();5. アプリケーションを動作させてみる
今回は、PHPのビルトインWebサーバーを使用して表示します。
以下のコマンドを実行します。
192.168.33.60
部分は各自の環境に置き換えてください。php -S 192.168.33.60:8080 -t public public/index.php下記が表示されたらブラウザで
http://192.168.33.60:8080/hello/world
を開いてみてください。[slimuser@localhost src]$ php -S 192.168.33.60:8080 -t public public/index.php PHP 7.1.33 Development Server started at Mon Jun 1 02:01:37 2020 Listening on http://192.168.33.60:8080 Document root is /home/slimuser/projects/slim/FirstApplication/src/public Press Ctrl-C to quit."Hello, world"と表示されました。
"world"を"cat"に書き換えて表示すると、表示が変わります。
http://192.168.33.60:8080/hello/cat
参考サイト
関連ページ
Windows10にVagrantをを入れてCentOS7をインストールしよう
1. VagrantインストールからVagrantfileを設置まで
2. 仮想マシンの操作
3. WinSCP、Tera Termに秘密鍵でログイン
4. WinSCP、Tera Termにrootユーザーでパスワードログイン
5. zip/unzipをインストール
6. Vagrantにて仮想環境を配布ローカルでLAMP環境を構築しよう
0. 事前準備
1. Apacheをインストール
2. MySQLをインストール
3. PHPをインストール
4. ファイアウォールとか停止するComposerをインストール
PHP Slim3フレームワークのサンプルアプリを作ろう
1. skeleton project
2-1. Getting Set Up
- 投稿日:2020-06-01T03:50:17+09:00
Laravelでコマンドライン処理を行う
前提条件
eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っていますCommandクラス作成
コマンドラインで
cd sample
php artisan make:command SampleCommand
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
eclipseプロジェクトを右クリック→リフレッシュ
/sample/app/Console/Commands/SampleCommand.phpが現れますCommandクラス修正
さきほど作成したSampleCommand.phpを下記に修正します
SampleCommand.php<?php namespace App\Console\Commands; use Illuminate\Console\Command; class SampleCommand extends Command { protected $signature = 'sample:name1 {arg1} {arg2=val2} {arg3?} {--option1} {--option2=} {--option3=opVal3} {--option4=*}'; protected $description = 'このプログラムの説明を書く'; public function __construct() { parent::__construct(); } public function handle() { $arg1 = $this->argument('arg1'); $arg2 = $this->argument('arg2'); $arg3 = $this->argument('arg3'); $option1 = $this->option('option1'); $option2 = $this->option('option2'); $option3 = $this->option('option3'); $option4 = $this->option('option4'); $this->line('下記入力を受け付けました'); $this->line('arg1:' . $arg1); $this->line('arg2:' . $arg2); $this->line('arg3:' . $arg3); $this->line('option1:' . $option1); $this->line('option2:' . $option2); $this->line('option3:' . $option3); $this->line('option4:' . var_export($option4, true)); $in = $this->ask('何か入力してください'); $this->line('下記入力を受け付けました'); $this->line($in); $in = $this->secret('何か入力してください。この入力中はユーザーがタイプした値を表示しません'); $this->line('下記入力を受け付けました'); $this->line($in); if ($this->confirm('yかyesを入力するとtrueとして扱います')) { $this->line('true'); } else { $this->line('false'); } $in = $this->choice( '選択入力。カンマ区切りで複数入力できます', ['php', 'Laravel', 'apache', 'eclipse'], //選択肢 $defaultIndex = 1, // 何も選ばれなかった場合に返ってくる選択肢の要素 $maxAttempts = 2, // 最大選択可能個数 $allowMultipleSelections = true // 複数選択可ならtrueにする ); $this->line('下記入力を受け付けました'); $this->line(var_export($in, true)); $this->line('テーブル出力'); $head = ['col1', 'col2', 'col3', 'col4']; $body = [ ['1-col1', '1-col2', '1-col3', '1-col4'], ['2-col1', '2-col2', '2-col3', '2-col4'], ['3-col1', '3-col2', '3-col3', '3-col4'], ]; $this->table($head, $body); $this->line('プログレスバー出力'); $bar = $this->output->createProgressBar(10); // プログレスバーを10の区切りで出力 $bar->start(); for ($i = 0; $i < 10; $i++) { sleep(1); $bar->advance(); } $bar->finish(); $this->line(''); $this->error('エラー出力'); } }
$signature
変数はこのコマンドの引数、オプションを定義します
ユーザーから入力してもらう引数とオプションはすべて波括弧で囲んで定義します
{arg1} 第1引数必須。プログラム内で$this->argument('arg1')
で値を取得できる
{arg2=val2} 第2引数任意。入力されなかった場合、$this->argument('arg2')
でval2が返る
{arg3?} 第3引数任意。デフォルト値無し
{--option1} オプション。option1オプションが指定されると$this->option('option1')
でtrueが返る
{--option2=} オプション。コマンド実行時何か値を与えるオプション
{--option3=opVal3} オプション。入力されなかった場合、$this->option('option3')
でopVal3が返る
{--option4=*} オプション。コマンド実行時何か値を与えるオプション。複数の値を渡すことができる。$this->option('option4')
の結果は配列となる
$description
変数はこのコマンドの説明ですhandleメソッドは実際の処理です
コマンドライン処理が実行されるとhandleメソッドが実行されます
$this->argument
は引数を取得できます
$this->option
はオプションを取得できます
$this->line
は文字列を出力できます
$this->ask
はユーザーからの入力を待ちます
$this->secret
はユーザーからの入力を待ちます。ただし、ユーザーのタイピングした値が表示されません。パスワード入力などに使います
$this->confirm
はユーザーの入力によりbool値を返します
$this->choice
はユーザーに選択肢から値を選択させます
$this->table
はテーブル形式で表示されます
$this->output->createProgressBar
の戻り値を使うことによってプログレスバーを表示できます
$this->error
でエラー時の文字列出力ができますCommandクラス登録
作成したCommandクラスを呼び出せるようにLaravelに登録しましょう
/sample/app/Console/Kernel.php修正Kernel.php‥‥ protected $commands = [ Commands\SampleCommand::class ]; ‥‥Kernel.phpに定義されている$commands配列に先ほど作成したSampleCommandクラス名を追記します
これでコマンドラインで実行できるようになりました動作確認
コマンドラインで
cd sample
php artisan sample:name1 aaa bbb ccc --option1 --option2=eee --option3=fff --option4=ggg --option4=hhh
先ほど作成したSampleCommandクラスの$signature
変数に定義した通りにコマンドラインで実行します
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します実行結果
下記入力を受け付けました arg1:aaa arg2:bbb arg3:ccc option1:1 option2:eee option3:fff option4:array ( 0 => 'ggg', 1 => 'hhh', ) 何か入力してください: > aaa 下記入力を受け付けました aaa 何か入力してください。この入力中はユーザーがタイプした値を表示しません: > 下記入力を受け付けました bbb yかyesを入力するとtrueとして扱います (yes/no) [no]: > y true 選択入力。カンマ区切りで複数入力できます [Laravel]: [0] php [1] Laravel [2] apache [3] eclipse > 2,3 下記入力を受け付けました array ( 0 => 'apache', 1 => 'eclipse', ) テーブル出力 +--------+--------+--------+--------+ | col1 | col2 | col3 | col4 | +--------+--------+--------+--------+ | 1-col1 | 1-col2 | 1-col3 | 1-col4 | | 2-col1 | 2-col2 | 2-col3 | 2-col4 | | 3-col1 | 3-col2 | 3-col3 | 3-col4 | +--------+--------+--------+--------+ プログレスバー出力 10/10 [============================] 100% エラー出力実行できました
- 投稿日:2020-06-01T00:06:11+09:00
PHPのフレームワーク作ってみました。
■PHPフレームワーク(Webシステム用)の概要
久しぶりに?PHPを触る機会があったので、フレームワークを作ってみました。
コンセプトは、
- スモールスタートできる。
- あんまり準備が必要ない。
- 直感的にわかる。
です。
なぜフレームワークを作ったかと言うとLaravelとかCakePHPとか使いづらい、と言うか作りづらいと思うからです。
車輪の再発明だと言われるかもしれませんが、世の中にたくさんのフレームワークが存在しているわけですし、今更1つくらい増えても問題はないでしょと思います。ちなみに名前は、安易ですが「WSI: Web System Infrastructure(Webシステム基盤)」としています。
★Controllerを作るのは面倒
LaravelもCakePHPも、その他のフレームワークもほとんどの場合、Controllerを作成します。
Controllerで処理をした後Viewに制御を渡して、画面を表示します。プロトタイプなど作成すると、ほとんどの場合、HTMLと紙芝居用のJavaScript、見た目のデザイン用のCSSしか作りません。
でも、そのHTMLとかJavaScriptとか、CSSとかをいざアプリケーションとして開発するために利用すると、結構、多大な変更が必要になります。変更が必要な原因は、HTML/JavaScript/CSSがアプリケーション様に部品化されていないのがほとんどですが、それは動作イメージを捉えるために、システマチックな”部品化”などと言う考えを排除しているためです。
それでもデザインツールなどで作成すれば、部品化(と言うか、パターン化)されるわけですけど。それよりも、例えばURLの書き換え(プロトタイプでは相対URLなどで書いていることが多い)をするのが意外に面倒です。
また、最近はRESTfullと言うのが流行りらしく、URLの構造がAPI的な構造だったりします。
でも、プロトタイプの時は、HTML/JavaScript/CSSを「.html」「.js」「*.css」としています。
それを「/xxx/yyy/zzzz」みたいなURLに修正する必要があります。あと、LaravelとかはControllerをartisanコマンドとかで作成しなければならないです。
「手動で作れるよ」とか言われそうですが、結局Controllerはルールがあるので、開発者が多いほどartisanコマンドで、生成するべきです。
Controllerくらいは大したことないですけど…。
そして、実際のHTMLファイルなどはView専用のディレクトリに配置して、Controllerから制御を渡すコードを書く必要があります。例えば、プロトタイプで作成した「index.html」があったとします。
それを、そのままブラウザで見られるようにすると良いと思います。
※これだけでは処理(Controllerなどに実装する処理)が書けないので、それは後程説明します。[root]
+ index.htmlこのような場合に、ブラウザで「
http://xxxx.yyy.zzz/index.html
」を表示したら、上記のindex.htmlが表示されるようにしています。
だから、基本的にルーティングもいらない。★Viewに対する処理は簡単に追加したい
上記で書いたContollerの話にも関連しますが、Viewに対する処理は簡単に追加できるようにしたいです。
具体的には、上記のファイル構成の場合に以下のようにします。[root]
+ index.html
+ index.html.phpこのように、ファイル名+.phpとすることで、そのファイルを表示する際に実行する処理を追加できます。
index.html.phpの中身(必要最低限)は下記のとおりです。use WSI\Request; use WSI\Status; return function(Request $request) { return Status::ok(); };例えば、以下のようにするとindex.html(View)に変数を渡せます。
// index.html.phpファイルのコード use WSI\Request; use WSI\Status; return function(Request $request) { return Status::ok()->set_param('message', 'Hello world !!'); };<!-- index.htmlファイルのコード --> <!DOCTYPE html> <html> <body> <?=$status->get_param('message') ?> </body> </html>このように簡単に処理コードを追加できます。
ボタンをクリックした場合の処理はどうやって追加するのか?
それは後で説明します。見てわかる通り、基本的にHTMLファイル(それ以外のファイルもですが…)は、phpソースとしてincludeしています。
テンプレートエンジンは使いません(php自体がテンプレートエンジンのようなものなので…)。
これにより、新しいテンプレートの構文を覚える必要はないですし、Viewのコーディングの自由度が上がります。★データベースはSQLビルダーとか使いたくない
LaravelもCakePHPも、その他のPHPフレームワークも、SQLビルダー(もしくはそれに類するもの)を使えるようになっています。
確かにSQLビルダーを使うことでSQL(で実現したいこと)を抽象化できますし、他のDBに移行するのも簡単になります。
しかし、SQLビルダーのルールは覚えなければならないですし、SQLビルダーによってどのようなSQLを実行しているのか分かりづらくなります。
ほとんどの場合、DB開発用のツールを使ってSQLを考えていると思います。
そうなった場合、いちいちSQL文をSQLビルダー構文に変換しなければなりません。
また、その逆もしかりで、何か障害が発生しSQLの解析をするにしても、ログから取得したSQLかSQLビルダーから変換したSQLを元に調査していると思います。
調査した結果でSQLを変更した場合、またSQLビルダー構文に変換しなければなりません。
データベースの移行なんてシチュエーションも、ほとんどありません(その場合は、絶対にSQLを再実装するはずです)。面倒ですし、無駄が多いですよね?
だったら、SQLのまま使った方が良くないですか?
SQLビルダーはSQLに比べて学習コストが低かったりする(売り文句)様ですが、結局どんなSQL(DBに対する命令)を実行しているのか
イメージしながらSQLビルダーでコーディングしますよね?本当に面倒です!
DBのアクセスはSQLで実装しましょう。
このフレームワークでは、SQL(と言うかDB)に対してよく使う機能だけをメソッドとして提供します。
とあるテーブルに問い合わせるSQLは、以下のようなコードになります。-- usersテーブルをユーザーIDで問い合わせるSELECT文です。 -- rootディレクトリ配下に、users\select\all.sqlとして配置されているものとします。 select * from users where id = :user_id and del_flg = 0// index.htmlにユーザー情報を表示するために実装した、index.html.phpのコード use WSI\Request; use WSI\Status; use WSI\Database; return function(Request $request) { $db = Database::connect(); //データベースへの接続(接続先の定義は別にありますが、ここでは割愛します) $sql = Resource::from('/users/select/all.sql'); //SQL文を取得する。 $user = $db->row($sql, ['user_id'='xxxxxx']); //rowメソッドで結果セットの先頭1行目を取得する。 //バインド変数は変数名で参照されます(「?」は使わない)。同じ変数名なら同じ値がバインドされます。 return Status::ok()->set_param('user', $user); };<!-- index.htmlファイルのコード --> <?php $user = $status->get_param('user'); //取得したusersテーブルのレコードをあえて変数に格納 ?> <!DOCTYPE html> <html> <body> <!--レコードはPDO::FETCH_BOTHで取得したものとなっている--> ID: <?=$user['id'] ?><br> 名前: <?=$user['name'] ?><br> : : </body> </html>煩わしいSQLビルダーは使いません。
素直にSQLを使いましょう。
なお、実はSQLファイルもphpファイルとしてincludeしています。
つまり、SQL文を動的に変更することも可能です。
ただし、SQLファイルをキャッシュしていないので、ループの中でResource::fromを実行しない様にしてください。★機能別に分けて開発したい
PHPは、COMPOSERによるパッケージ化ができるのですが、開発しているシステム内を簡単にパッケージ化するのには向いていません。
一つのシステムでは、機能A/機能B/機能C…のように分かれていることが多いですが、LaravelやCakePHPはそういった機能ごとに
分割(分担)して開発するのは、あまり得意でない様に思います。
(自分が知らないだけなのかもしれませんが…)
PHPでも(Javaで言うところのjarファイルのように)pharファイル(PHp ARchiveでしたっけ?)が作れますが、実際のシステム開発であまり使われていないような気がします。
機能ごとに開発してpharファイル化したものを本番環境に配置することでリリースができたら、管理も楽になると思います。もちろん、1システムが1セットで開発することのメリットはあります。
最近では開発に構成管理ツール(Subversionやgitなど)を導入することは多いですので、チェックアウト(gitだとクローン)すればフルセットで開発に必要な資材が手元のPCに簡単に用意できるわけです。
それ以外にも、他の機能のコードも含んでいるので調査したり解析することもできます。しかし、やはり弊害の方が多い気がします。
- 見えるソースが多いため、思考にノイズが入りやすい。
- IDEなどによっては、ソース量が多いため多くのメモリが必要になる。
- 無意識に関係ない機能のソースを修正してしまって、気が付かない場合がある(コミットする際に気が付くとは思いますが…)。
- 依存関係が見えづらい。
など。
機能別にパッケージ化されてソースが隠蔽されることで、設計(特にインターフェースになる部分)が重要になるわけですが、本質的には設計が重要なのは当たり前です。
具体的にどうなるかと言うと、
- 公開ディレクトリ ←下記の共通機能、機能A、機能B、機能Cをpharファイルにして配置
- 共通機能 ←パッケージ
- 機能A ←パッケージ(共通機能に依存)
- 機能B ←パッケージ(共通機能と機能Aに依存)
- 機能C ←パッケージ(共通機能と機能Aに依存)
と言った具合に、機能ごとにパッケージを分けられるようにすれば、部分的な機能別のリリースも可能になります。
なお、当フレームワークでは、最初は1セットとして開発してたものを、機能別に分割(ディレクトリを分けるだけですけど)し、さらに機能別に分割したものをpharファイル化すれば、そのpharファイルを参照して動作する様になっています。
例えば、以下のようなディレクトリ構成でシステムに資材を配置していたとします。[root] + index.html + [view] + login.html + topmenu.html + userlist.html + page1.html + page2.html + [js] + jquery.js + common.js + login.js + topmenu.js + userlist.js + page1.js + page2.js + [css] + bootstrap.js + common.css + method_a_common.cssこれを以下のような構成に分割します。
↓root(公開ディレクトリ) [root] + index.html↓common(共通機能) [common] + index.html [view] + login.html + topmenu.html + userlist.html + [js] + jquery.js + common.js + [css] + bootstrap.js + common.css↓機能A(ディレクトリ) [method_a] + [view] + page1.html + page2.html + [js] + page1.js + page2.js + [css] + common.css ←元は、method_a_common.cssこのように分割すると、それぞれの資源は以下のようにアクセスできます。
- 公開ディレクトリにある資材… /index.hml など
- 共通機能の資材… /common/view/login.html など
- 機能Aの資材… /method_a/view/page1.html など
この状態で、例えば「機能A」をパッケージ化して「method_a.phar」とした場合でも、やはり「/method_a/view/page1.html」と言うように、資材にアクセスするURLは変えません。
もちろん、フレームワークがその様になるように処理しているわけです。資材の配置について、当フレームワークでは、以下のようなURLでアクセスする様にできます。
- 設定前)/method_a/view/page1.html
- 設定後)/method_a/page1.html
これは、「.htmlファイルは、viewディレクトリ配下に配置している」と設定しているためです。
同様に.jsはjsディレクトリ、.cssはcssディレクトリに配置すると設定できます。
このように、アクセスする資材の拡張子によって、ディレクトリの構造を決めることが可能です。もう一つ、公開ディレクトリの特殊な処理についてです。
例えば、rootディレクトリ(公開ディレクトリ)内に、view/common/topmenu.htmlとなるように資材を配置したとします。
この場合、本来のcommonディレクトリ(共通機能)に格納されている資材よりも優先的に参照するようになっています。
つまり「修正してみてどう変わるか」を簡単に検証できます。
topmenu.htmlをコピーしてroot配下に修正したものを配置すると、修正内容を試すことが可能です。また、method_a全体に波及するような修正を施し、method_a1として配置します。
そのmethod_a配下の資材を参照するURLを書き換えた資材(例えば、aタグのhrefを変更したtopmenu.html)を、root配下に配置します。
そうすると、元のmethod_aディレクトリ及びtopmenu.htmlを変更せずに、修正を試すことが可能になります。
想定通りに動作することを確認した後は、method_aをmethod_a1に挿げ替え、topmenu.htmlをcommonディレクトリ配下に配置すれば、不具合の発生を抑えることが可能になります。★やっぱりAjaxですよね
リクエストに対してViewの全てをレスポンスする場合、実装は難しくなります。
ここで言う「難しい」の意味は、単にコード量が多いという意味ではありません。例えば、処理結果で「はい/いいえ」の確認ダイアログを表示して、「はい(または、いいえ)」を選択して処理を継続するような場合に、従来の画面リフレッシュ方式のWeb実装(こういうのをMVCと言えばよいのかな?)では、ちょっとしたテクニックが必要だったりします。
それは、アクションに対する一連の処理(トランザクション)とHTTPによるリクエスト/レスポンスのサイクルは、必ずしも一致しないと言うことに起因します。
また、HTTP通信の役割は画面の再描画であるため、確認ダイアログを表示するために一旦はブラウザにレスポンスしないとなりません。
ブラウザにレスポンスと言う事は、この時点でHTTP通信(リクエスト~レスポンス)の1サイクルは完了しています。
しかし、トランザクションとしては継続中です。
その為、「はい・いいえ」を選択した後は2サイクル目のHTTP通信に対して、1サイクル目のHTTP通信の状態を引き継ぐ必要があります。
このようなシチュエーションは、割とよく発生します。Ajaxの場合は、HTTP通信の役割がデータのやり取りでしかない(画面の再描画ではない)ため、アクションのトランザクション中の「データ・アクセス」と「画面の描画」が切り離せると言う事です。
(データベースのトランザクションの意味ではないです)
これにより、実装はシンプルになり意味的に理解しやすくなるため、結果として実装が難しくなくなります。LaravelやCakePHPは、基本的にMVCフレームワークであるため、こういった問題が発生しやすくなります。
(LaravelだとRESTFull APIを実装できるので、設計次第ではAjaxも選択できるわけですが…)当フレームワークは、一般に言うMVCのフレームワークではなく、Ajax方式のフレームワークになっています。
その為、クライアントで動作する基本JavaScriptとして、以下のような機能を提供しています。
- Ajax
- DOM操作
また、上記のほかに、JavaScriptを実装するためのいくつかの機能も提供しています。
- ネームスペース化
- クラス化
- 画面項目のコントロール
- 画面管理クラス化
- IFrameをまたいだメッセージ通信
結構長くなったので続きは別の機会に
とりあえず、他にもフレームワークとしての機能は実装していますが、さすがに長くなってきたので一旦区切りとします。
また、まだまだ未完成でもありますので、今後も改善・機能追加は発生します。以下に、公開しているGitHubのURLを掲載しますので、興味のある方はご参照ください。
今後は、実際のアプリケーションを作成するための実装方法を掲載するようにします。
それでは、また。