Phalcon 3.4 チュートリアル Creating a Simple REST API

こちらのチュートリアルを試してみます。
Tutorial: Creating a Simple REST API

簡単なAPIを作りたいだけなのに、通常のphpフレームワークを使用すると
それなりのファイル数をサーバーにアップロードすることになります。

Phalconだと、php拡張なのでいちいちフレームワーク用のファイルを
アップロードする必要がないので、これは便利だと思います。



Defining the API



チュートリアルに従い、ロボット情報を取得・登録するAPIを作成します。
APIの仕様はこちら。

MethodURLAction
GET/api/robots登録されているロボットをすべて取得
GET/api/robots/search/Astro‘Astro’という名前のロボットを検索
GET/api/robots/2Primary Keyを指定してロボットを検索
POST/api/robots新しいロボットを追加
PUT/api/robots/2Primary Keyを指定してロボット情報を更新
DELETE/api/robots/2Primary Keyを指定してロボット情報を削除




Creating the Application



これから作成するアプリケーションのディレクトリ構成です。

974_01.png


以下の2ファイルだけの構成です。


index.php
models/Robots.php




まず、index.phpを編集。
Microというクラスを生成して、urlのrouteを設定します。


  1. <?php
  2. use Phalcon\Mvc\Micro;
  3. $app = new Micro();
  4. // 登録されているロボットをすべて取得
  5. $app->get(
  6.     '/api/robots',
  7.     function () {
  8.         // Operation to fetch all the robots
  9.     }
  10. );
  11. // {name}という名前のロボットを検索
  12. $app->get(
  13.     '/api/robots/search/{name}',
  14.     function ($name) {
  15.         // Operation to fetch robot with name $name
  16.     }
  17. );
  18. // Primary Keyを指定してロボットを検索
  19. $app->get(
  20.     '/api/robots/{id:[0-9]+}',
  21.     function ($id) {
  22.         // Operation to fetch robot with id $id
  23.     }
  24. );
  25. // 新しいロボットを追加
  26. $app->post(
  27.     '/api/robots',
  28.     function () {
  29.         // Operation to create a fresh robot
  30.     }
  31. );
  32. // Primary Keyを指定してロボット情報を更新
  33. $app->put(
  34.     '/api/robots/{id:[0-9]+}',
  35.     function ($id) {
  36.         // Operation to update a robot with id $id
  37.     }
  38. );
  39. // Primary Keyを指定してロボット情報を削除
  40. $app->delete(
  41.     '/api/robots/{id:[0-9]+}',
  42.     function ($id) {
  43.         // Operation to delete the robot with id $id
  44.     }
  45. );
  46. $app->handle();



「/api/robots/{id:[0-9]+}」のように、メソッドが受け取れる値を指定できるのは、
他のフレームワークでもよく見かけますね。




Creating a Model



models/Robots.phpを編集し、入力値の検証(validation)を設定します。

・models/Robots.php


  1. <?php
  2. namespace Store\Toys;
  3. use Phalcon\Mvc\Model;
  4. use Phalcon\Mvc\Model\Message;
  5. use Phalcon\Validation;
  6. use Phalcon\Validation\Validator\Uniqueness
  7. use Phalcon\Validation\Validator\InclusionIn;
  8. class Robots extends Model
  9. {
  10.     public function validation()
  11.     {
  12.         $validator = new Validation();
  13.         
  14.         // Type は「droid」「mechanical」または「virtual」
  15.         $validator->add(
  16.             "type",
  17.             new InclusionIn(
  18.                 [
  19.                     'message' => 'Type は「droid」「mechanical」または「virtual」で指定してください。',
  20.                     'domain' => [
  21.                         'droid',
  22.                         'mechanical',
  23.                         'virtual',
  24.                     ],
  25.                 ]
  26.             )
  27.         );
  28.         // Robot name は重複禁止
  29.         $validator->add(
  30.             'name',
  31.             new Uniqueness(
  32.                 [
  33.                     'field' => 'name',
  34.                     'message' => 'robot nameはユニークな値を指定してください。',
  35.                 ]
  36.             )
  37.         );
  38.         // Year は0以上
  39.         if ($this->year < 0) {
  40.             $this->appendMessage(
  41.                 new Message('yearは0以上の値を指定してください。')
  42.             );
  43.         }
  44.         // 検証チェック
  45.         if ($this->validationHasFailed() === true) {
  46.             return false;
  47.         }
  48.     }
  49. }




作成したモデルのロードと、データベースへの接続設定をindex.phpに追記します。

・index.php

追記した箇所の抜粋
※データベースはPostgreSQLを使用しています。


  1. <?php
  2. use Phalcon\Loader;
  3. use Phalcon\Mvc\Micro;
  4. use Phalcon\Di\FactoryDefault;
  5. use Phalcon\Db\Adapter\Pdo\Postgresql as DbAdapter;
  6. $loader = new Loader();
  7. $loader->registerNamespaces(
  8.     [
  9.         'Store\Toys' => __DIR__ . '/models/',
  10.     ]
  11. );
  12. $loader->register();
  13. $di = new FactoryDefault();
  14. // Set up the database service
  15. $di->set(
  16.     'db',
  17.     function () {
  18.         return new DbAdapter(
  19.             [
  20.                 'host'     => '127.0.0.1',
  21.                 'username' => 'pgadmin',
  22.                 'password' => 'P@ssw0rd',
  23.                 'dbname' => 'sample',
  24.             ]
  25.         );
  26.     }
  27. );
  28. // Create and bind the DI to the application
  29. $app = new Micro($di);






Retrieving Data



データベースを検索する処理を追記します。

・index.php

変更箇所の抜粋


  1. <?php
  2. // 登録されているロボットをすべて取得
  3. $app->get(
  4.     '/api/robots',
  5.     function () use ($app) {
  6.         $phql = 'SELECT * FROM Store\Toys\Robots ORDER BY name';
  7.         $robots = $app->modelsManager->executeQuery($phql);
  8.         $data = [];
  9.         foreach ($robots as $robot) {
  10.             $data[] = [
  11.                 'id' => $robot->id,
  12.                 'name' => $robot->name,
  13.             ];
  14.         }
  15.         echo json_encode($data);
  16.     }
  17. );
  18. // {name}という名前のロボットを検索
  19. $app->get(
  20.     '/api/robots/search/{name}',
  21.     function ($name) use ($app) {
  22.         $phql = 'SELECT * FROM Store\Toys\Robots WHERE name LIKE :name: ORDER BY name';
  23.         $robots = $app->modelsManager->executeQuery(
  24.             $phql,
  25.             [
  26.                 'name' => '%' . $name . '%'
  27.             ]
  28.         );
  29.         $data = [];
  30.         foreach ($robots as $robot) {
  31.             $data[] = [
  32.                 'id' => $robot->id,
  33.                 'name' => $robot->name,
  34.             ];
  35.         }
  36.         echo json_encode($data);
  37.     }
  38. );
  39. // Primary Keyを指定してロボットを検索
  40. $app->get(
  41.     '/api/robots/{id:[0-9]+}',
  42.     function ($id) use ($app) {
  43.         $phql = 'SELECT * FROM Store\Toys\Robots WHERE id = :id:';
  44.         $robot = $app->modelsManager->executeQuery(
  45.             $phql,
  46.             [
  47.                 'id' => $id,
  48.             ]
  49.         )->getFirst();
  50.         
  51.         // Create a response
  52.         $response = new Response();
  53.         if ($robot === false) {
  54.             $response->setJsonContent(
  55.                 [
  56.                     'status' => 'NOT-FOUND'
  57.                 ]
  58.             );
  59.         } else {
  60.             $response->setJsonContent(
  61.                 [
  62.                     'status' => 'FOUND',
  63.                     'data' => [
  64.                         'id' => $robot->id,
  65.                         'name' => $robot->name
  66.                     ]
  67.                 ]
  68.             );
  69.         }
  70.         return $response;
  71.     }
  72. );



PHQLという独自記法が使われています。
FROMにテーブル名ではなく「Store\Toys\Robots」の用にモデル名を指定するのがポイントでしょうか。


Inserting Data



データの登録箇所の抜粋です。

・index.php


  1. <?php
  2. use Phalcon\Http\Response;
  3. // 新しいロボットを追加
  4. $app->post(
  5.     '/api/robots',
  6.     function () use ($app) {
  7.         $robot = $app->request->getJsonRawBody();
  8.         $phql = 'INSERT INTO Store\Toys\Robots (name, type, year) VALUES (:name:, :type:, :year:)';
  9.         $status = $app->modelsManager->executeQuery(
  10.             $phql,
  11.             [
  12.                 'name' => $robot->name,
  13.                 'type' => $robot->type,
  14.                 'year' => $robot->year,
  15.             ]
  16.         );
  17.         // Create a response
  18.         $response = new Response();
  19.         // Check if the insertion was successful
  20.         if ($status->success() === true) {
  21.             // Change the HTTP status
  22.             $response->setStatusCode(201, 'Created');
  23.             $robot->id = $status->getModel()->id;
  24.             $response->setJsonContent(
  25.                 [
  26.                     'status' => 'OK',
  27.                     'data' => $robot,
  28.                 ]
  29.             );
  30.         } else {
  31.             // Change the HTTP status
  32.             $response->setStatusCode(409, 'Conflict');
  33.             // Send errors to the client
  34.             $errors = [];
  35.             foreach ($status->getMessages() as $message) {
  36.                 $errors[] = $message->getMessage();
  37.             }
  38.             $response->setJsonContent(
  39.                 [
  40.                     'status' => 'ERROR',
  41.                     'messages' => $errors,
  42.                 ]
  43.             );
  44.         }
  45.         return $response;
  46.     }
  47. );






Updating Data



データの更新箇所の抜粋です。


  1. <?php
  2. use Phalcon\Http\Response;
  3. // Primary Keyを指定してロボット情報を更新
  4. $app->put(
  5.     '/api/robots/{id:[0-9]+}',
  6.     function ($id) use ($app) {
  7.         $robot = $app->request->getJsonRawBody();
  8.         $phql = 'UPDATE Store\Toys\Robots SET name = :name:, type = :type:, year = :year: WHERE id = :id:';
  9.         $status = $app->modelsManager->executeQuery(
  10.             $phql,
  11.             [
  12.                 'id' => $id,
  13.                 'name' => $robot->name,
  14.                 'type' => $robot->type,
  15.                 'year' => $robot->year,
  16.             ]
  17.         );
  18.         // Create a response
  19.         $response = new Response();
  20.         // Check if the insertion was successful
  21.         if ($status->success() === true) {
  22.             $response->setJsonContent(
  23.                 [
  24.                     'status' => 'OK'
  25.                 ]
  26.             );
  27.         } else {
  28.             // Change the HTTP status
  29.             $response->setStatusCode(409, 'Conflict');
  30.             $errors = [];
  31.             foreach ($status->getMessages() as $message) {
  32.                 $errors[] = $message->getMessage();
  33.             }
  34.             $response->setJsonContent(
  35.                 [
  36.                     'status' => 'ERROR',
  37.                     'messages' => $errors,
  38.                 ]
  39.             );
  40.         }
  41.         return $response;
  42.     }
  43. );





Deleting Data



最後にデータの削除です。


  1. <?php
  2. use Phalcon\Http\Response;
  3. // Primary Keyを指定してロボット情報を削除
  4. $app->delete(
  5.     '/api/robots/{id:[0-9]+}',
  6.     function ($id) use ($app) {
  7.         $phql = 'DELETE FROM Store\Toys\Robots WHERE id = :id:';
  8.         $status = $app->modelsManager->executeQuery(
  9.             $phql,
  10.             [
  11.                 'id' => $id,
  12.             ]
  13.         );
  14.         // Create a response
  15.         $response = new Response();
  16.         if ($status->success() === true) {
  17.             $response->setJsonContent(
  18.                 [
  19.                     'status' => 'OK'
  20.                 ]
  21.             );
  22.         } else {
  23.             // Change the HTTP status
  24.             $response->setStatusCode(409, 'Conflict');
  25.             $errors = [];
  26.             foreach ($status->getMessages() as $message) {
  27.                 $errors[] = $message->getMessage();
  28.             }
  29.             $response->setJsonContent(
  30.                 [
  31.                     'status' => 'ERROR',
  32.                     'messages' => $errors,
  33.                 ]
  34.             );
  35.         }
  36.         return $response;
  37.     }
  38. );





index.php



最終的なindex.phpはこのようになりました。


  1. <?php
  2. use Phalcon\Loader;
  3. use Phalcon\Mvc\Micro;
  4. use Phalcon\Http\Response;
  5. use Phalcon\Di\FactoryDefault;
  6. use Phalcon\Db\Adapter\Pdo\Postgresql as DbAdapter;
  7. $loader = new Loader();
  8. $loader->registerNamespaces(
  9.     [
  10.         'Store\Toys' => __DIR__ . '/models/',
  11.     ]
  12. );
  13. $loader->register();
  14. $di = new FactoryDefault();
  15. // Set up the database service
  16. $di->set(
  17.     'db',
  18.     function () {
  19.         return new DbAdapter(
  20.             [
  21.                 'host'     => '127.0.0.1',
  22.                 'username' => 'pgadmin',
  23.                 'password' => 'P@ssw0rd',
  24.                 'dbname' => 'sample',
  25.             ]
  26.         );
  27.     }
  28. );
  29. // Create and bind the DI to the application
  30. $app = new Micro($di);
  31. // 登録されているロボットをすべて取得
  32. $app->get(
  33.     '/api/robots',
  34.     function () use ($app) {
  35.         $phql = 'SELECT * FROM Store\Toys\Robots ORDER BY name';
  36.         $robots = $app->modelsManager->executeQuery($phql);
  37.         $data = [];
  38.         foreach ($robots as $robot) {
  39.             $data[] = [
  40.                 'id' => $robot->id,
  41.                 'name' => $robot->name,
  42.             ];
  43.         }
  44.         echo json_encode($data);
  45.     }
  46. );
  47. // {name}という名前のロボットを検索
  48. $app->get(
  49.     '/api/robots/search/{name}',
  50.     function ($name) use ($app) {
  51.         $phql = 'SELECT * FROM Store\Toys\Robots WHERE name LIKE :name: ORDER BY name';
  52.         $robots = $app->modelsManager->executeQuery(
  53.             $phql,
  54.             [
  55.                 'name' => '%' . $name . '%'
  56.             ]
  57.         );
  58.         $data = [];
  59.         foreach ($robots as $robot) {
  60.             $data[] = [
  61.                 'id' => $robot->id,
  62.                 'name' => $robot->name,
  63.             ];
  64.         }
  65.         echo json_encode($data);
  66.     }
  67. );
  68. // Primary Keyを指定してロボットを検索
  69. $app->get(
  70.     '/api/robots/{id:[0-9]+}',
  71.     function ($id) use ($app) {
  72.         $phql = 'SELECT * FROM Store\Toys\Robots WHERE id = :id:';
  73.         $robot = $app->modelsManager->executeQuery(
  74.             $phql,
  75.             [
  76.                 'id' => $id,
  77.             ]
  78.         )->getFirst();
  79.         // Create a response
  80.         $response = new Response();
  81.         if ($robot === false) {
  82.             $response->setJsonContent(
  83.                 [
  84.                     'status' => 'NOT-FOUND'
  85.                 ]
  86.             );
  87.         } else {
  88.             $response->setJsonContent(
  89.                 [
  90.                     'status' => 'FOUND',
  91.                     'data' => [
  92.                         'id' => $robot->id,
  93.                         'name' => $robot->name
  94.                     ]
  95.                 ]
  96.             );
  97.         }
  98.         return $response;
  99.     }
  100. );
  101. // 新しいロボットを追加
  102. $app->post(
  103.     '/api/robots',
  104.     function () use ($app) {
  105.         $robot = $app->request->getJsonRawBody();
  106.         $phql = 'INSERT INTO Store\Toys\Robots (name, type, year) VALUES (:name:, :type:, :year:)';
  107.         $status = $app->modelsManager->executeQuery(
  108.             $phql,
  109.             [
  110.                 'name' => $robot->name,
  111.                 'type' => $robot->type,
  112.                 'year' => $robot->year,
  113.             ]
  114.         );
  115.         // Create a response
  116.         $response = new Response();
  117.         // Check if the insertion was successful
  118.         if ($status->success() === true) {
  119.             // Change the HTTP status
  120.             $response->setStatusCode(201, 'Created');
  121.             $robot->id = $status->getModel()->id;
  122.             $response->setJsonContent(
  123.                 [
  124.                     'status' => 'OK',
  125.                     'data' => $robot,
  126.                 ]
  127.             );
  128.         } else {
  129.             // Change the HTTP status
  130.             $response->setStatusCode(409, 'Conflict');
  131.             // Send errors to the client
  132.             $errors = [];
  133.             foreach ($status->getMessages() as $message) {
  134.                 $errors[] = $message->getMessage();
  135.             }
  136.             $response->setJsonContent(
  137.                 [
  138.                     'status' => 'ERROR',
  139.                     'messages' => $errors,
  140.                 ]
  141.             );
  142.         }
  143.         return $response;
  144.     }
  145. );
  146. // Primary Keyを指定してロボット情報を更新
  147. $app->put(
  148.     '/api/robots/{id:[0-9]+}',
  149.     function ($id) use ($app) {
  150.         $robot = $app->request->getJsonRawBody();
  151.         $phql = 'UPDATE Store\Toys\Robots SET name = :name:, type = :type:, year = :year: WHERE id = :id:';
  152.         $status = $app->modelsManager->executeQuery(
  153.             $phql,
  154.             [
  155.                 'id' => $id,
  156.                 'name' => $robot->name,
  157.                 'type' => $robot->type,
  158.                 'year' => $robot->year,
  159.             ]
  160.         );
  161.         // Create a response
  162.         $response = new Response();
  163.         // Check if the insertion was successful
  164.         if ($status->success() === true) {
  165.             $response->setJsonContent(
  166.                 [
  167.                     'status' => 'OK'
  168.                 ]
  169.             );
  170.         } else {
  171.             // Change the HTTP status
  172.             $response->setStatusCode(409, 'Conflict');
  173.             $errors = [];
  174.             foreach ($status->getMessages() as $message) {
  175.                 $errors[] = $message->getMessage();
  176.             }
  177.             $response->setJsonContent(
  178.                 [
  179.                     'status' => 'ERROR',
  180.                     'messages' => $errors,
  181.                 ]
  182.             );
  183.         }
  184.         return $response;
  185.     }
  186. );
  187. // Primary Keyを指定してロボット情報を削除
  188. $app->delete(
  189.     '/api/robots/{id:[0-9]+}',
  190.     function ($id) use ($app) {
  191.         $phql = 'DELETE FROM Store\Toys\Robots WHERE id = :id:';
  192.         $status = $app->modelsManager->executeQuery(
  193.             $phql,
  194.             [
  195.                 'id' => $id,
  196.             ]
  197.         );
  198.         // Create a response
  199.         $response = new Response();
  200.         if ($status->success() === true) {
  201.             $response->setJsonContent(
  202.                 [
  203.                     'status' => 'OK'
  204.                 ]
  205.             );
  206.         } else {
  207.             // Change the HTTP status
  208.             $response->setStatusCode(409, 'Conflict');
  209.             $errors = [];
  210.             foreach ($status->getMessages() as $message) {
  211.                 $errors[] = $message->getMessage();
  212.             }
  213.             $response->setJsonContent(
  214.                 [
  215.                     'status' => 'ERROR',
  216.                     'messages' => $errors,
  217.                 ]
  218.             );
  219.         }
  220.         return $response;
  221.     }
  222. );
  223. $app->handle();






Creating database



データベースにテーブルを作成します。
今回は、既に作成済のsampleデータベースにテーブルを作成しました。
※PostgreSQL用にアレンジしています。


  1. CREATE TABLE robots (
  2. id serial NOT NULL,
  3. name varchar(200) NOT NULL,
  4. type varchar(200) NOT NULL,
  5. year smallint NOT NULL,
  6. PRIMARY KEY (id)
  7. );
  8. -- 初期データ登録
  9. INSERT INTO robots (name, type, year) VALUES ('Robotina', 'virtual', 1980);
  10. INSERT INTO robots (name, type, year) VALUES ('Astro Boy', 'virtual', 1981);
  11. INSERT INTO robots (name, type, year) VALUES ('Terminator', 'virtual', 1982);





Testing our Application



curlコマンドでAPIをテストしてみます。

・登録されているロボットをすべて取得


$ curl -i -X GET http://192.168.1.102/api/robots
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 09 Mar 2019 11:47:58 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

[{"id":2,"name":"Astro Boy"},{"id":1,"name":"Robotina"},{"id":3,"name":"Terminator"}]




・{name}という名前のロボットを検索


$ curl -i -X GET http://192.168.1.102/api/robots/search/Astro
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 09 Mar 2019 11:50:50 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

[{"id":2,"name":"Astro Boy"}]




・Primary Keyを指定してロボットを検索


$ curl -i -X GET http://192.168.1.102/api/robots/3
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 09 Mar 2019 11:51:34 GMT
Content-Type: application/json; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

{"status":"FOUND","data":{"id":3,"name":"Terminator"}}




・新しいロボットを追加


$ curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}' http://192.168.1.102/api/robots
HTTP/1.1 201 Created
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 09 Mar 2019 11:52:31 GMT
Content-Type: application/json; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

{"status":"OK","data":{"name":"C-3PO","type":"droid","year":1977,"id":"4"}}




・同じ名前のロボットを追加


$ curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}' http://192.168.1.102/api/robots
HTTP/1.1 201 Created
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 09 Mar 2019 11:54:42 GMT
Content-Type: application/json; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

{"status":"OK","data":{"name":"C-3PO","type":"droid","year":1977,"id":"6"}}



エラーになるはずが追加できてしまいました。
PostgreSQLに変更した影響かもしれません。


・Primary Keyを指定してロボット情報を更新


$ curl -i -X PUT -d '{"name":"ASIMO","type":"humanoid","year":2000}' http://192.168.1.102/api/robots/2
$ curl -i -X GET http://192.168.1.102/api/robots/2
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 09 Mar 2019 11:56:37 GMT
Content-Type: application/json; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

{"status":"FOUND","data":{"id":2,"name":"ASIMO"}}




・Primary Keyを指定してロボット情報を削除


$ curl -i -X DELETE http://192.168.1.102/api/robots/6
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 09 Mar 2019 11:57:46 GMT
Content-Type: application/json; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

{"status":"OK"}




関連記事

プロフィール

Author:symfo
blog形式だと探しにくいので、まとめサイト作成中です。
Symfoware まとめ

PR




検索フォーム

月別アーカイブ