PHP Slim 環境構築(5) PDOとDBアクセス
Introduction
前回は、Autoloader周りを触りましたが、今回はPDOを使ってDBにアクセスしてみたいと思います。
変更点
ソースツリー
前回からの変更・追加ソースは以下の通りです。
$(PROJECTROOT)
/compose
docker-compose.yml
/src
/hoge
/lib
/Controller
PlayerController.php (NEW!)
PlayerCreatedLogController.php (NEW!)
/Model
Player.php (NEW!)
PlayerCreatedLog.php (NEW!)
/public
index.php
composer.json
composer.lock
docker-compose.yml
web_hogeコンテナから、DBコンテナ(mysqlとpostgresql)にアクセスするために、linksを設定します。
これで、ホスト名"mysql"と"postgresql"でそれぞれのDBを識別することができます。
web_hoge:
build:
context: ./web_hoge
args:
- environment=local
links: (追加)
- mysql (追加)
- postgresql (追加)
composer.json/composer.lock
Slimフレームワークでは、PDOの生成はDIコンテナから行うのが一般的です。
PSR-11に対応しているDIコンテナとして、php-diをcomposerで追加します。
また、PDOおよびJSONのPHP拡張を使用するので、それらもcomposerで追加しておきます。
$ composer require php-di/php-di:6.0.9
$ composer require ext-pdo
$ composer require ext-json
index.php
19~58行目:
DIコンテナにDBの設定とDBのfactoryを追加しています。本当はソースを分けたりいろいろすべきですが、べらっと書いてしまっています。
62~63行目:
レスポンスに自動的にContent-Lengthを付けるためのMiddlewareを追加しています。
PDO周りとは直接関係ないのですが、PDOから取得したデータをそのまま出すだけだと、Content-Lengthが付かないので、一応お行儀よくしました。
68~70行目:
前回のExampleBeforeMiddlewareとExampleAfterMiddlewareを"/"だけに限定して付けています。
こういうやりかたもできるというテストです。
72~76行目:
GET "/player/{id}"、POST "/player"、PUT "/player/{id}"のCRUD (Dはありませんが)の定義です。
PlayerControllerに処理を移譲しており、この中でMySQLへのアクセス処理を行っています。
78~81行目:
GET "/player_created/{id}"、GET "/player_created"の定義です。
PlayerCreatedLogControllerに処理を移譲しており、この中でPostgreSQLへのアクセス処理を行っています。
<?php
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Routing\RouteCollectorProxy;
use DI\ContainerBuilder;
use Hoge\ExampleAfterMiddleware;
use Hoge\ExampleBeforeMiddleware;
use Hoge\Controller\PlayerController;
use Hoge\Controller\PlayerCreatedLogController;
/** @var Composer\Autoload\ClassLoader $loader */
$loader = require __DIR__ . '/../../vendor/autoload.php';
$loader->addPsr4('Hoge\\', __DIR__ . '/../lib');
$containerBuilder = new ContainerBuilder();
$containerBuilder->addDefinitions([
'settings' => [
'userdb' => [
'host' => 'mysql',
'dbname' => 'userdb',
'user' => 'scott',
'password' => 'tiger'
],
'logdb' => [
'host' => 'postgresql',
'dbname' => 'logdb',
'user' => 'root',
'password' => 'hogehoge'
]
],
'userdb' => function (ContainerInterface $container) {
$settings = $container->get('settings')['userdb'];
$dsn = 'mysql:host=' . $settings['host'] . ';dbname=' . $settings['dbname'];
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
];
return new PDO($dsn, $settings['user'], $settings['password'], $options);
},
'logdb' => function (ContainerInterface $container) {
$settings = $container->get('settings')['logdb'];
$dsn = 'pgsql:host=' . $settings['host'] . ';dbname=' . $settings['dbname'];
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
];
return new PDO($dsn, $settings['user'], $settings['password'], $options);
}
]);
$container = $containerBuilder->build();
AppFactory::setContainer($container);
$app = AppFactory::create();
// Middleware
$contentLengthMiddleware = new \Slim\Middleware\ContentLengthMiddleware();
$app->add($contentLengthMiddleware);
$app->get('/', function (Request $request, Response $response, array $args) {
$response->getBody()->write('Hello, World!');
return $response;
})
->add(new ExampleBeforeMiddleware())
->add(new ExampleAfterMiddleware());
$app->group('/player', function (RouteCollectorProxy $group) {
$group->get('/{id}', PlayerController::class . ':get');
$group->post('', PlayerController::class . ':post');
$group->put('/{id}', PlayerController::class . ':put');
});
$app->group('/player_created', function (RouteCollectorProxy $group) {
$group->get('/{id}', PlayerCreatedLogController::class . ':get');
$group->get('', PlayerCreatedLogController::class . ':list');
});
$app->run();
PlayerController
httpリクエストを受けて、MySQLのplayerテーブルにアクセスするためのControllerです。
CRUDのCRUに相当するメソッドを実装しています。
例外処理をしていなかったり、コードの重複があったりといろいろ手を抜いていますが、あくまでサンプルということでそのままにしています。
なお、それぞれのメソッドは以下のようにアクセスすることができます。
$ wget http://hoge.localhost/user // get
$ wget http://hoge.localhost/user --post-data='{"player_name":"名前"}' // post
$ wget http://hoge.localhost/user/1 --method=put --body-data='{"player_name":"新名前"}'
<?php
namespace Hoge\Controller;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Container\ContainerInterface;
use \PDO;
use Hoge\Model\Player;
class PlayerController
{
/**
* @var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function get(Request $request, Response $response, array $args) : Response
{
// parameters (id)
$id = $args['id'];
$pdo = $this->container->get('userdb'); /** @var PDO $pdo */
$stmt = $pdo->prepare('select * from player where player_id=:id');
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, Player::class);
$player = $stmt->fetch(); /** @var Player|false $player */
if (!$player) {
return $response->withStatus(404);
}
$response->getBody()->write(json_encode($player->toArray(), JSON_UNESCAPED_UNICODE));
return $response;
}
public function post(Request $request, Response $response, array $args) : Response
{
$body = $request->getBody()->getContents();
$input = json_decode($body);
$name = $input->player_name;
$pdo = $this->container->get('userdb'); /** @var PDO $pdo */
$stmt = $pdo->prepare('insert into player(player_name) values(:name)');
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
$pdo->beginTransaction();
$stmt->execute();
$pdo->commit();
return $response;
}
public function put(Request $request, Response $response, array $args) : Response
{
$body = $request->getBody()->getContents();
$input = json_decode($body);
$id = $args['id'];
$name = $input->player_name;
$pdo = $this->container->get('userdb'); /** @var PDO $pdo */
$stmt = $pdo->prepare('update player set player_name=:name where player_id=:id');
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$pdo->beginTransaction();
$stmt->execute();
$pdo->commit();
return $response;
}
}
Player
playerテーブルのデータモデルです。
<?php
namespace Hoge\Model;
class Player
{
private $player_id;
private $player_name;
public function toArray()
{
return [
'player_id' => $this->player_id,
'player_name' => $this->player_name
];
}
}
PlayerCreatedLogController
PostgreSQLのplayer_created_logテーブルにアクセスするためのControllerクラスです。
サンプルのため、特定のidのレコード取得と、全レコード取得を実装しています。
PlayerControllerと同様に、かなり手抜きです。
<?php
namespace Hoge\Controller;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Container\ContainerInterface;
use \PDO;
use Hoge\Model\PlayerCreatedLog;
class PlayerCreatedLogController
{
/**
* @var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function get(Request $request, Response $response, array $args) : Response
{
// parameters (id)
$id = $args['id'];
$pdo = $this->container->get('logdb'); /** @var PDO $pdo */
$stmt = $pdo->prepare('select * from player_created_log where player_id=:id');
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, PlayerCreatedLog::class);
$logList = $stmt->fetchAll(); /** @var PlayerCreatedLog[] $logList */
$logArray = array_map(
function ($log) { /** @var PlayerCreatedLog $log */
return $log->toArray();
},
$logList
);
$response->getBody()->write(json_encode($logArray, JSON_UNESCAPED_UNICODE));
return $response;
}
public function list(Request $request, Response $response, array $args) : Response
{
$pdo = $this->container->get('logdb'); /** @var PDO $pdo */
$stmt = $pdo->prepare('select * from player_created_log');
$stmt->execute();
$stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, PlayerCreatedLog::class);
$logList = $stmt->fetchAll(); /** @var PlayerCreatedLog[] $logList */
$logArray = array_map(
function ($log) { /** @var PlayerCreatedLog $log */
return $log->toArray();
},
$logList
);
$response->getBody()->write(json_encode($logArray, JSON_UNESCAPED_UNICODE));
return $response;
}
}
PlayerCreatedLog
player_created_logテーブルのデータモデルです。
<?php
namespace Hoge\Model;
class PlayerCreatedLog
{
private $player_id;
private $created_at;
public function toArray()
{
return [
'player_id' => $this->player_id,
'created_at' => $this->created_at
];
}
}
ここまでのソース
こちらでどうぞ。