https://rakus.connpass.com/event/264108/
PHP8.2のリリースがちょっと延期
バグが見つかったんなら仕方がないね
「gmail」ドメインを「gmai」と誤記、10カ月気付かず2000件超の情報漏えいか 埼玉大が「ドッペルゲンガー・ドメイン」の毒牙に
送信側によるドメインチェックは必須
The PHP Foundation: Impact and Transparency Report 2022
PHPの保守・開発は順調に進んでいるようでなによりです。
Webアプリケーションフレームワーク とは(wiki)
そもそもFWってなんで必要?
https://laravel.com/docs/9.x/installation
What could be more useful than an application developed by users for their own needs
https://symfony.com/at-a-glance#a-philosophy
ユーザーが自分たちのニーズに合わせて開発したアプリケーションほど便利なものはない
作成されたコンポーネントを組み合わせて、フルスタックフレームワークを作成することもマイクロサービスを作成することも可能。
開発者の目的に応じて規模を変えることができることが特徴。
コンポーネントが標準化されており、アプリケーションが成熟しても使用したいコンポーネントを自由に導入することができる。
Java の Spring Framework や Ruby の Ruby on Rails の影響を受けているとのこと。
Symfony コンポーネントは Drupal, Prestashop, Laravel で利用されている
Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
https://www.slimframework.com/
Slimはシンプルかつ強力な Web アプリケーションと API をすばやく作成するのに役立つ PHP マイクロ フレームワークです。
At its core, Slim is a dispatcher that receives an HTTP request, invokes an appropriate callback routine, and returns an HTTP response. That’s it.
You don’t always need a kitchen-sink solution like Symfony or Laravel. These are great tools, for sure. But they are often overkill. Instead, Slim provides only a minimal set of tools that do what you need and nothing else.
https://www.slimframework.com/docs/v4/
本質的に、Slim は HTTP リクエストを受け取り、適切なコールバック ルーチンを呼び出し、HTTP レスポンスを返すディスパッチャーです。それだけ。
SymfonyやLaravelのようなキッチン シンク ソリューションが常に必要なわけではありません。これらは確かに優れたツールです。しかし、それらはしばしばやり過ぎです。代わりに、Slim は、必要なことだけを行う最小限のツール セットのみを提供します。
https://laravel.com/docs/9.x/routing
デフォルトでは下記2つファイルにルーティングを定義する
UserControllerにindxメソッドを定義している場合
下記のように定義すると/user
のパスに対して、UserControllerのindexメソッドが対応される
<?php
use App\Http\Controllers\UserController;
Route::get('/user', [UserController::class, 'index']);
<?php
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);
<?php
Route::get('/posts/{post}/comments/{comment}', [CommentController::class, 'show']);
routes.yaml
を以下の通り編集する
app_lucky_number:
path: /lucky/number
controller: App\Controller\LuckyController::number
/lucky/number
にアクセスすることで LuckyController
の number
メソッドにルーティングされる
コントローラを以下の通り変更
<?php
// src/Controller/LuckyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class LuckyController
{
#[Route('/lucky/number')]
public function number(): Response
{
$number = random_int(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
上記の通り記述することで routes.yaml
を作成しなくともルーティングされる
https://book.cakephp.org/3/ja/development/routing.html
routes.php
に記載
例:/
にアクセスするとArticlesController
のindex()
メソッドを実行する
use Cake\Routing\Router;
// スコープ付きルートビルダーを使用。
Router::scope('/', function ($routes) {
$routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);
});
// static メソッドを使用。
Router::connect('/', ['controller' => 'Articles', 'action' => 'index']);
/articles/15
にアクセスするとArticlesController
のview(15)
メソッドを実行する
$routes->connect(
'/articles/:id',
['controller' => 'Articles', 'action' => 'view']
)
->setPatterns(['id' => '\d+'])
->setPass(['id']);
HTTPメソッドによって分けたいときは以下の記述
// GET リクエストへのみ応答するルートの作成
$routes->get(
'/cooks/:id',
['controller' => 'Users', 'action' => 'view'],
'users:view'
);
// PUT リクエストへのみ応答するルートの作成
$routes->put(
'/cooks/:id',
['controller' => 'Users', 'action' => 'update'],
'users:update'
);
https://www.slimframework.com/docs/v4/objects/routing.html
$app->get('/books/{id}', function ($request, $response, array $args) {
// Show book identified by $args['id']
});
https://laravel.com/docs/9.x/session
config/session.php
グローバルセッションヘルパーとRequestインスタンス経由の2つの方法
<?php
$value = session('key');
<?php
public function show(Request $request, $id)
{
$value = $request->session()->get('key');
//
}
以下の2通り
HttpFoundation component
を追加することで利用可能composer require symfony/http-foundation
config/packages/framework.yaml
に記載
yaml
?handler_id
save_path
に記載したパスにSymfonyのセッションメタデータを保存
use Symfony\Component\HttpFoundation\RequestStack;
class SomeService
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
// Accessing the session in the constructor is *NOT* recommended, since
// it might not be accessible yet or lead to unwanted side-effects
// $this->session = $requestStack->getSession();
}
public function someMethod()
{
$session = $this->requestStack->getSession();
// stores an attribute in the session for later reuse
$session->set('attribute-name', 'attribute-value');
// gets an attribute by name
$foo = $session->get('foo');
// the second argument is the value returned when the attribute doesn't exist
$filters = $session->get('filters', []);
// ...
}
}
SessionInterface
でタイプヒントしてコントローラ引数に渡すだけ。
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
// ...
public function index(SessionInterface $session): Response
{
// stores an attribute for reuse during a later user request
$session->set('foo', 'bar');
// gets the attribute set by another controller in another request
$foobar = $session->get('foobar');
// uses a default value if the attribute doesn't exist
$filters = $session->get('filters', []);
// ...
}
'Session' => [
'defaults' => 'database',
'handler' => [
'engine' => 'DatabaseSession',
'model' => 'CustomSessions'
]
]
model => 'CustomSessions'
)Configure::write('Session', [
'defaults' => 'cache',
'handler' => [
'config' => 'session'
]
]);
CacheSession
クラスをセッション保存先として 委任する$name = $this->getRequest()->getSession()->read('User.name');
// 複数回セッションにアクセスする場合、
// ローカル変数にしたくなるでしょう。
$session = $this->getRequest()->getSession();
$name = $session->read('User.name');
AuthComponent
は自動的にセッション ID を更新実装なし
https://laravel.com/docs/9.x/requests
Illuminate\Http\Requestクラスのインスタンスを生成して取得する
<?php
$name = $request->input('name');
$name = $request->query('name');
https://symfony.com/doc/current/components/http_kernel.html
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
public function index(Request $request): Response
{
$request->isXmlHttpRequest(); // is it an Ajax request?
$request->getPreferredLanguage(['en', 'fr']);
// retrieves GET and POST variables respectively
$request->query->get('page');
$request->request->get('page');
// retrieves SERVER variables
$request->server->get('HTTP_HOST');
// retrieves an instance of UploadedFile identified by foo
$request->files->get('foo');
// retrieves a COOKIE value
$request->cookies->get('PHPSESSID');
// retrieves an HTTP request header, with normalized, lowercase keys
$request->headers->get('host');
$request->headers->get('content-type');
}
$this->request を使用して取得
$controllerName = $this->request->getParam('controller');
// URL は /posts/index?page=1&sort=title の場合に page を取得するとき
$page = $this->request->getQuery('page');
// POSTデータにアクセスするとき
$title = $this->request->getData('MyModel.title');
https://www.slimframework.com/docs/v4/objects/request.html
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
$app->get('/hello', function (Request $request, Response $response) {
$response->getBody()->write('Hello World');
return $response;
});
$app->run();
https://readouble.com/laravel/9.x/ja/errors.html
App\Exceptions\Handler
クラスによって、アプリケーションが投げるすべての例外がログに記録され、ユーザーへレンダーされるHandler
クラスは、カスタム例外レポートとレンダリングコールバックを登録できるregister
メソッドを持っている。
reportable
メソッドで、例外をさまざまな方法で報告できる。(エラー監視ツールに登録するなど。デフォルトではログに記録される。)
renderable
メソッドで、特定の例外に対して、個別にレンダリング方法を指定することができる。(デフォルトでは例外はHTTPレスポンスに変換される)
resources/views/errors
下のHTTPステータスコード名のbladeファイルがレンダリングされる(404.blade.php
など)
use App\Exceptions\InvalidOrderException;
/**
* アプリケーションの例外処理コールバックを登録
*
* @return void
*/
public function register()
{
$this->reportable(function (InvalidOrderException $e) {
// 例外を報告
});
$this->renderable(function (InvalidOrderException $e, $request) {
return response()->view('errors.invalid-order', [], 500);
});
}
Symfony アプリケーションでは、エラーが 404 Not Found エラーであろうと、
コードで何らかの例外をスローすることによってトリガーされた致命的なエラーであろうと、
すべてのエラーを例外として扱う。
組み込みの Twig エラーレンダラーを使用して、デフォルトのエラーテンプレートをオーバーライド可能。
composer require symfony/twig-pack
これらのテンプレートをオーバーライドするには、標準の Symfony メソッドを使用し て、バンドル内にあるテンプレートをオーバーライドし、それらをtemplates/bundles/TwigBundle/Exception/ディレクトリに配置する。
HTML ページを返す典型的なプロジェクトは次のようになります。
Copy
templates/
└─ bundles/
└─ TwigBundle/
└─ Exception/
├─ error404.html.twig
├─ error403.html.twig
└─ error.html.twig # All other HTML errors (including 500)
HTML ページの 404 エラー テンプレートをオーバーライドするには、次の場所にある新しい error404.html.twigテンプレートを作成しtemplates/bundles/TwigBundle/Exception/ます。
{# templates/bundles/TwigBundle/Exception/error404.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Page not found</h1>
<p>
The requested page couldn't be located. Checkout for any URL
misspelling or <a href="{{ path('homepage') }}">return to the homepage</a>.
</p>
{% endblock %}
https://book.cakephp.org/3/ja/development/errors.html
ErrorController にてエラーページを描画
namespace App\Controller\Admin;
use App\Controller\AppController;
use Cake\Event\EventInterface;
class ErrorController extends AppController
{
/**
* Initialization hook method.
*
* @return void
*/
public function initialize(): void
{
$this->loadComponent('RequestHandler');
}
/**
* beforeRender callback.
*
* @param \Cake\Event\EventInterface $event Event.
* @return void
*/
public function beforeRender(EventInterface $event)
{
$this->viewBuilder()->setTemplatePath('Error');
}
}
https://www.slimframework.com/docs/v4/middleware/error-handling.html
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
/**
* The routing middleware should be added earlier than the ErrorMiddleware
* Otherwise exceptions thrown from it will not be handled by the middleware
*/
$app->addRoutingMiddleware();
/**
* Add Error Middleware
*
* @param bool $displayErrorDetails -> Should be set to false in production
* @param bool $logErrors -> Parameter is passed to the default ErrorHandler
* @param bool $logErrorDetails -> Display error details in error log
* @param LoggerInterface|null $logger -> Optional PSR-3 Logger
*
* Note: This middleware should be added last. It will not handle any exceptions/errors
* for middleware added after it.
*/
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
// ...
$app->run();
https://readouble.com/laravel/9.x/ja/database.html
Lavavelでは以下のDBがサポートされる
config/database.phpで設定を行う
データベースにアクセスする方法は以下
$users = DB::select('select * from users where active = ?', [1]);
$users = DB::table('users')->where('active', $isActive)->get();
$users = User::where('active', $isActive)->get();
https://symfony.com/doc/current/doctrine.html
コネクション
composer require doctrine maker
インストールが完了すると .env ファイルにデータベースへの接続設定に関する項目が書き足されます。
DATABASE_URL の箇所を接続するデータベースに合わせて書き換えて下さい。
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
php bin/console doctrine:database:create
エンティティクラスを作成する
symfony console make:entity hoge
SELECT文の実行
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc');
INSERT文の実行
use Cake\Datasource\ConnectionManager;
use DateTime;
$connection = ConnectionManager::get('default');
$connection->insert('articles', [
'title' => 'A New Article',
'created' => new DateTime('now')
], ['created' => 'datetime']);
UPDATE文の実行
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$connection->update('articles', ['title' => 'New title'], ['id' => 10]);
DELETE文の実行
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$connection->delete('articles', ['id' => 10]);
なし
https://readouble.com/laravel/9.x/ja/validation.html
複数のバリデーション方法がある
例を2つ紹介
連想配列形式で各パラメータに適応したいバリデーションルールを指定する
他のバリデーションルール
<?php
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
ファサードを使ってバリデーションをすることも可能
第一引数に対象のデータ、第二引数にバリデーションルールを指定する
<?php
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
config/validator/ ディレクトリ内の .yaml または .xml ファイルとして定義する。
(例)$nameプロパティが空
Attributes
// src/Entity/Author.php
namespace App\Entity;
// ...
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
#[Assert\NotBlank]
private $name;
}
YAML
# config/validator/validation.yaml
App\Entity\Author:
properties:
name:
- NotBlank: ~
XML
<!-- config/validator/validation.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="App\Entity\Author">
<property name="name">
<constraint name="NotBlank"/>
</property>
</class>
</constraint-mapping>
PHP
// src/Entity/Author.php
namespace App\Entity;
// ...
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Mapping\ClassMetadata;
class Author
{
private $name;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('name', new NotBlank());
}
}
https://book.cakephp.org/4/en/core-libraries/validation.html#namespace-Cake\Validation
$validator
->requirePresence('title')
->notEmptyString('title', 'このフィールドに入力してください')
->add('title', [
'length' => [
'rule' => ['minLength', 10],
'message' => 'タイトルは10文字以上必要です。',
]
])
->allowEmptyDateTime('published')
->add('published', 'boolean', [
'rule' => 'boolean'
])
->requirePresence('body')
->add('body', 'length', [
'rule' => ['minLength', 50],
'message' => '記事の内容は意味がなければなりません。'
]);
ドキュメントに記述なし
https://readouble.com/laravel/9.x/ja/migrations.html
マイグレージョン関連のコマンドがartisanコマンドで準備されている
php artisan make:migration create_flights_table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFlightsTable extends Migration
{
/**
* マイグレーションの実行
*
* @return void
*/
public function up()
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('airline');
$table->timestamps();
});
}
/**
* マイグレーションを戻す
*
* @return void
*/
public function down()
{
Schema::drop('flights');
}
}
Doctrine Migrationsを使用。
symfony console make:migration
生成されたファイル名が出力される(migrations/Version20191019083640.php のような名前のファイル):
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version00000000000000 extends AbstractMigration
{
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SEQUENCE comment_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE conference_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE comment (id INT NOT NULL, conference_id INT NOT NULL, author VARCHAR(255) NOT NULL, text TEXT NOT NULL, email VARCHAR(255) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, photo_filename VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_9474526C604B8382 ON comment (conference_id)');
$this->addSql('CREATE TABLE conference (id INT NOT NULL, city VARCHAR(255) NOT NULL, year VARCHAR(4) NOT NULL, is_international BOOLEAN NOT NULL, PRIMARY KEY(id))');
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C604B8382 FOREIGN KEY (conference_id) REFERENCES conference (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
// ...
}
}
https://book.cakephp.org/migrations/3/ja/index.html
composer require cakephp/migrations "@stable"
<?php
use Migrations\AbstractMigration;
class CreateProducts extends AbstractMigration
{
/**
* Change Method.
*
* More information on this method is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
* @return void
*/
public function change()
{
$table = $this->table('products');
$table->addColumn('name', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
]);
$table->addColumn('description', 'text', [
'default' => null,
'null' => false,
]);
$table->addColumn('created', 'datetime', [
'default' => null,
'null' => false,
]);
$table->addColumn('modified', 'datetime', [
'default' => null,
'null' => false,
]);
$table->create();
}
}
* 主キーとなるカラム id は、 勝手に追加される
なし