[CakePHP4] Content Management Tutorial

– Make sure to get PHP version.
$ php -v
PHP 7.4.3 (cli) (built: Mar 2 2022 15:36:52) ( NTS )

$ php composer.phar create-project –prefer-dist cakephp/app:4.* cms
$ bin/cake server -H 0.0.0.0

# create database cake_cms;
# \c cake_cms;

CREATE TABLE users(
	id SERIAL PRIMARY KEY,
	email VARCHAR(255) NOT NULL,
	password VARCHAR(255) NOT NULL,
	created TIMESTAMP,
	modified TIMESTAMP
);

CREATE TABLE articles(
	id SERIAL PRIMARY KEY,
	user_id INT NOT NULL,
	title VARCHAR(255) NOT NULL,
	slug VARCHAR(191) NOT NULL,
	body TEXT,
	published BOOLEAN DEFAULT FALSE,
	created TIMESTAMP,
	modified TIMESTAMP,
	UNIQUE (slug),
	FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE tags(
	id SERIAL PRIMARY KEY,
	title VARCHAR(191),
	created TIMESTAMP,
	modified TIMESTAMP,
	UNIQUE (title)
);

CREATE TABLE articles_tags(
	article_id INT NOT NULL,
	tag_id INT NOT NULL,
	PRIMARY KEY (article_id, tag_id),
	FOREIGN KEY (tag_id) REFERENCES tags(id),
	FOREIGN KEY (article_id) REFERENCES articles(id)
);

INSERT INTO users (email, password, created, modified) VALUES 
('cakephp@example.com', 'secret', NOW(), NOW());

INSERT INTO articles(user_id, title, slug, body, published, created, modified)
VALUES
(1, 'first post', 'first-post', 'This is the first post.', TRUE, NOW(), NOW());

cake_cms=# select * from users;
cake_cms=# select * from articles;

Database Configuration
L app_local.php

'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Postgres',
            'persistent' => false,
            'host' => 'localhost',
            //'port' => 'non_standard_port_number',
            'username' => 'hoge',
            'password' => 'hoge',
            'database' => 'cake_cms',
            // 'encoding' => 'utf8mb4',
            'timezone' => 'UTC',
            'cacheMetadata' => true,
            'url' => env('DATABASE_URL', null),
        ],

Creating our first model
src/Model/Table/ArticlesTable.php

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table {

	public function initialize(array $config): void {
		$this->addBehavior('Timestamp');
	}
}

src/Model/Entity/Article.php

namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity {

	protected $_accessible = [
		'*' => true,
		'id' => false,
		'slug' => false
	];
}

Creating the Articles Controller
src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController {

	public function index() {
		$this->loadComponent('Paginator');
		$articles = $this->Paginator->paginate($this->Articles->find());
		$this->set(compact('articles'));
	}
}

Create the Article List Template
templates/Articles/index.php

<h1>Articles</h1>
<table>
	<tr>
		<th>Title</th>
		<th>Created</th>
	</tr>

	<?php foreach ($articles as $article): ?>
	<tr>
		<td>
			<?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
		</td>
		<td>
			<?= $article->created->format(DATE_RFC850) ?>
		</td>
	</tr>
	<?php endforeach; ?>
</table>

Create the View Action
src/Controller/ArticlesController.php

	public function view($slug = null){

		$article = $this->Articles->findBySlug($slug)->firstOrFail();
		$this->set(compact('article'));
	}

Create the view Template
templates/Articles/view.php

<h1><?= h($article->title) ?></h1>
<p><?= h($article->body) ?></p>
<p><small>Created: <?= $article->created->format(DATE_RFC850) ?></small></p>
<p><?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?></p>

Adding Articles

class ArticlesController extends AppController {

	public function initialize(): void {
		parent::initialize();

		$this->loadComponent('Paginator');
		$this->loadComponent('Flash');
	}

	public function index() {
		$articles = $this->Paginator->paginate($this->Articles->find());
		$this->set(compact('articles'));
	}

	public function view($slug = null){

		$article = $this->Articles->findBySlug($slug)->firstOrFail();
		$this->set(compact('article'));
	}

	public function add(){

		$article = $this->Articles->newEmptyEntity();
		if($this->request->is('post')){
			$article = $this->Articles->patchEntity($article, $this->request->getData());

			$article->user_id = 1;

			if ($this->Articles->save($article)){
				$this->Flash->success(__('Your article has been saved.'));
				return $this->redirect(['action' => 'index']);
			}
			$this->Flash->error(__('Unable to add your article.'));
		}
		$this->set('article', $article);
	}
}

Create Add Template
templates/Articles/add.php

<h1>Add Article</h1>
<?php
	echo $this->Form->create($article);
	echo $this->Form->control('user_id', ['type'=> 'hidden', 'value' => 1]);
	echo $this->Form->control('title');
	echo $this->Form->control('body', ['rows' => '3']);
	echo $this->Form->button(__('Save Article'));
	echo $this->Form->end();
?>

templates/Articles/index.php

<?= $this->Html->link('Add Article', ['action' => 'add']) ?>

Adding Simple Slug Generation
src/Model/Table/ArticlesTable.php

namespace App\Model\Table;

use Cake\ORM\Table;
use Cake\Utility\Text;
use Cake\Event\EventInterface;

public function beforeSave(EventInterface $event, $entity, $options){
	if($entity->isNew() && !$entity->slug){
		$sluggedTitle = Text::slug($entity->title);
		$entity->slug = substr($sluggedTitle, 0, 191);
	}
}

Add Edit Action

	public function edit($slug) {
		$article = $this->Articles
			->findBySlug($slug)
			->firstOrFail();

		if($this->request->is(['post', 'put'])){
			$this->Articles->patchEntity($article, $this->request->getData());
			if ($this->Articles->save($article)){
				$this->Flash->success(__('Your article has been updated.'));
				return $this->redirect(['action' => 'index']);
			}
			$this->Flash->error(__('Unable to update your article.'));
		}
		$this->set('article', $article);
	}

Create Edit Template

<h1>Edit Article</h1>
<?php
	echo $this->Form->create($article);
	echo $this->Form->control('user_id', ['type'=> 'hidden']);
	echo $this->Form->control('title');
	echo $this->Form->control('body', ['rows' => '3']);
	echo $this->Form->button(__('Save Article'));
	echo $this->Form->end();
?>

templates/Articles/index.php

<h1>Articles</h1>
<?= $this->Html->link('Add Article', ['action' => 'add']) ?>
<table>
	<tr>
		<th>Title</th>
		<th>Created</th>
		<th>Action</th>
	</tr>

	<?php foreach ($articles as $article): ?>
	<tr>
		<td>
			<?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
		</td>
		<td>
			<?= $article->created->format(DATE_RFC850) ?>
		</td>
		<td>
			<?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
		</td>
	</tr>
	<?php endforeach; ?>
</table>

cakephp4.xに慣れよう

この記事ではCakePHP4.*のチュートリアルを行います。
1. composerのcurl
2. cakephpのインストール
3. build in serverを起動
4. configディレクトリの.envファイルの編集
5. envファイルの読み込み(コメントアウト解除)
6. データベースの設定
7. migrationファイルの作成
8. 管理画面ファイルをbakeで作成
9. バリデーションの作成
10. 管理画面のルーターの設定
11. 一般ユーザ用のファイル作成
12. Viewファイル作成
13. ユーザ管理機能作成(認証機能)
14. ユーザバリデーション機能追加
15. ユーザー認証機能の実装(authenticationプラグイン)
16. 管理画面レイアウトの作成

$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar create-project –prefer-dist cakephp/app:4.* blog
$ cd blog
$ bin/cake server -H 0.0.0.0

4. configディレクトリの.envファイルの編集
/config/.env

export APP_NAME="MyBlog"
export DEBUG="true"
export APP_ENCODING="UTF-8"
export APP_DEFAULT_LOCALE="ja_JP"
export APP_DEFAULT_TIMEZONE="Asia/Tokyo"
export SECURITY_SALT="3mPhHebbrC9cTH7Kjg9MU5d_bXuBXcUUyGRbgJHe"

5. envファイルの読み込み(コメントアウト解除)
/config/.bootstrap.php

if (!env('APP_NAME') && file_exists(CONFIG . '.env')) {
    $dotenv = new \josegonzalez\Dotenv\Loader([CONFIG . '.env']);
    $dotenv->parse()
        ->putenv()
        ->toEnv()
        ->toServer();
}

6. データベースの設定
/config/app_local.php

use Cake\Database\Driver\Postgres; 
// 省略
    'Datasources' => [
        'default' => [
            'driver' => Postgres::class,
            'host' => 'localhost',
            /*
             * CakePHP will use the default DB port based on the driver selected
             * MySQL on MAMP uses port 8889, MAMP users will want to uncomment
             * the following line and set the port accordingly
             */
            //'port' => 'non_standard_port_number',

            'username' => 'hoge',
            'password' => 'fuga',

            'database' => 'test',
            /*
             * If not using the default 'public' schema with the PostgreSQL driver
             * set it here.
             */
            //'schema' => 'myapp',

            /*
             * You can use a DSN string to set the entire configuration
             */
            'url' => env('DATABASE_URL', null),
        ],

7. migrationファイルの作成
$ bin/cake bake migration CreatePosts

/config/Migrations/20230104050535_CreatePosts.php

    public function change(): void
    {
        $table = $this->table('posts');
        $table->addColumn('title', 'string', [
            'limit' => 150,
            'null' => false,
        ])
        ->addColumn('description', 'text', [
            'limit' => 255,
        ])
        ->addColumn('body', 'text')
        ->addColumn('published', 'boolean', [
            'default' => false,
        ])
        ->addColumn('created', 'datetime')
        ->addColumn('modified', 'datetime')
        ->create();
    }

$ bin/cake migrations migrate

8. 管理画面ファイルをbakeで作成
model, controller, viewを作成することができる
$ bin/cake bake model posts
$ bin/cake bake controller posts –prefix admin
$ bin/cake bake template posts –prefix admin
※ –prefix admin はadminディレクトリに作成するという意味合い

9. バリデーションの作成
src/Model/Table/PostsTable.php

    public function validationDefault(Validator $validator): Validator
    {
        $validator
            ->integer('id')
            ->allowEmptyString('id', null, 'create');

        $validator
            ->scalar('title')
            ->maxLength('title', 150, '150文字以内で入力してください')
            ->minLength('title', 5, '5文字以上で入力してください')
            ->requirePresence('title', 'create')
            ->notEmptyString('title', 'タイトルは必ず入力してください');

        $validator
            ->scalar('description')
            ->maxLength('description', 255, '150文字以上で入力してください。')
            ->allowEmptyString('description');

        $validator
            ->scalar('body')
            ->allowEmptyString('body');

        $validator
            ->boolean('published')
            ->allowEmptyString('published');

        return $validator;
    }

10. 管理画面のルーターの設定
/config/routes.php

use Cake\Routing\Router;
//..
        Router::prefix('admin', function ($routes) {
            $routes->fallbacks('DashedRoute');
            $routes->connect('/', ['controller' => 'Posts', 'action' => 'index']);
        });

11. 一般ユーザ用のファイル作成
$ bin/cake bake controller posts
src/Controller/PostsController.php

class PostsController extends AppController
{
    public $paginate = [
        'limit' => 10,
        'order' => [
            'Posts.created' => 'desc'
        ]
    ];

    public function index()
    {
        $posts = $this->paginate($this->Posts->findByPublished(1));

        $this->set(compact('posts'));
    }

    public function view($id = null)
    {
        $post = $this->Posts->get($id, [
            'conditions' => ['published' => 1],
        ]);

        $this->set('post', $post);
    }   
}

12. Viewファイル作成
templates/Posts/index.php

<div class="content">
	<?php foreach ($posts as $post): ?>
		<p>投稿日:<time><?= h($post->created->i18nFormat('YYYY/MM/dd HH:mm:ss')) ?></time></p>
		<h3 style="margin-bottom:0"><?= h($post->title) ?></h3>
		<?= $this->Text->autoParagrahp(h($post->description)); ?>
		<br>
		<?= $this->Html->link('記事を読む', ['action'=> 'view', $post->id], ['class'=> 'button']) ?>
		<hr>
	<?php endforeach; ?>
    <div class="paginator">
        <ul class="pagination">
            <?= $this->Paginator->first('<< 最初') ?>
            <?= $this->Paginator->prev('< 前へ') ?>
            <?= $this->Paginator->numbers() ?>
            <?= $this->Paginator->next('次へ >') ?>
            <?= $this->Paginator->last('最後 >>') ?>
        </ul>
    </div>
</div>

templates/Posts/view.php

<div class="posts view content">
	<?= h($post->created->il8nformat('YYYY/MM/dd HH:mm:ss')) ?>
    <h2><?= h($post->title) ?></h2>
    <?= $this->Text->autoParagraph(h($post->body)); ?>
    <hr>
    <?= $this->Html->link('一覧へ戻る', ['action'=>'index'], ['class'=>'button']) ?>
</div>

/config/routes.php

        // $builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
        $builder->connect('/', ['controller' => 'Posts', 'action' => 'index']);

13. ユーザ管理機能作成(認証機能)
$ bin/cake bake migration CreateUsers
/config/Migrations/20230104084657_CreateUsers.php

    public function change(): void
    {
        $table = $this->table('users');
        $table->addColumn('username', 'string', [
                'default' => null,
                'limit' => 50,
                'null' => false,
            ])
            ->addColumn('password', 'string', [
                'default' => null,
                'limit' => 255,
                'null' => false,
            ])
            ->addColumn('created', 'datetime')
            ->addColumn('modified', 'datetime')
            ->create();
    }

$ bin/cake migrations migrate
$ bin/cake bake model users
$ bin/cake bake controller users –prefix admin
$ bin/cake bake template users –prefix admin

ユーザエンティティにハッシュ化を追加
src/Model/Entity/User.php

use Cake\Auth\DefaultPasswordHasher;
class User extends Entity
{
    protected $_accessible = [
        'username' => true,
        'password' => true,
        'created' => true,
        'modified' => true,
    ];

    protected $_hidden = [
        'password',
    ];

    protected function _setPassword(string $password) : ?string {
        if (strlen($password) > 0) {
            return (new DefaultPasswordHasher())->hash($password);
        }
    }
}

14. ユーザバリデーション機能追加
src/Model/Table/UsersTable.php

    public function validationDefault(Validator $validator): Validator
    {
        $validator
            ->integer('id')
            ->allowEmptyString('id', null, 'create');

        $validator
            ->scalar('username')
            ->maxLength('username', 50)
            ->requirePresence('username', 'create')
            ->notEmptyString('username', 'ユーザ名は必ず入力してください');

        $validator
            ->scalar('password')
            ->maxLength('password', 255)
            ->requirePresence('password', 'create')
            ->notEmptyString('password', 'パスワードは必ず入力してください');

        return $validator;
    }

15. ユーザー認証機能の実装(authenticationプラグイン)
$ sudo cp composer.phar blog/
$ cd blog
$ php composer.phar require cakephp/authentication:^2.0
src/Application.php

use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Psr\Http\Message\ServerRequestInterface;

    public function getAuthenticationService(ServerRequestInterface $request):AuthenticationServiceInterface {

        $authenticationService = new AuthenticationService([
            'unauthenticatedRedirect' => '/admin/users/login',
            'queryParam' => 'redirect',
        ]);

        $authenticationService->loadIdentifier('Authentication.Password',[
            'fields' => [
                'username' => 'username',
                'password' => 'password',
            ]
        ]);

        $authenticationService->loadAuthenticator('Authentication.Session');
        $authenticationService->loadAuthenticator('Authentication.Form', [
            'fields' => [
                'username' => 'username',
                'password' => 'password',
            ],
            'loginUrl' => '/admin/users/login',
        ]);

        return $authenticationService;
    }

src/Controller/Admin/AdminController.php

namespace App\Controller\Admin;

use Cake\Controller\Controller;

class AdminController extends Controller {

	public function initialize(): void {
		parent::initialize();

		$this->loadComponent('RequestHandler');
		$this->loadComponent('Flash');
		$this->loadComponent('Authentication.Authentication');
	}
}

src/Controller/Admin/PostsController.php

use App\Controller\Admin\AdminController;

class PostsController extends AdminController{
//..
}

src/Controller/Admin/UsersController.php

use App\Controller\Admin\AdminController;

    public function login(){
        $this->request->allowMethod(['get', 'post']);
        $result = $this->Authentication->getResult();

        if($result->isValid()){
            return $this->redirect('/admin');
        }
        if($this->request->is('post') && !$request->isValid()){
            $this->Flash->error('ユーザ名かパスワードが正しくありません');
        }
    }

    public function logout(){
        $result = $this->Authentication->getResult();
        if($result->isValid()){
            $this->Authentication->logout();
            return $this->redirect(['controller'=> 'Users', 'action' => 'login']);
        }
    }

16. 管理画面レイアウトの作成
src/Controller/Admin/UsersController.php

<!DOCTYPE html>
<html>
<head>
    <?= $this->Html->charset() ?>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Controle Panel</title>
    <?= $this->Html->meta('icon') ?>
    <link href="https://fonts.googleapis.com/css?family=Raleway:400,700" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css">
    <?= $this->Html->css('milligram.min.css') ?>
    <?= $this->Html->css('cake.css') ?>
    <?= $this->fetch('meta') ?>
    <?= $this->fetch('css') ?>
    <?= $this->fetch('script') ?>
</head>
<body>
    <nav class="top-nav">
        <div class="top-nav-title">
            <a href="/admin/">Control Panel</a>
        </div>
        <div class="top-nav-links">
            <a href="/admin/users/logout">ログアウト</a>
        </div>
    </nav>
    <main class="main">
        <div class="container">
            <?= $this->Flash->render() ?>
            <?= $this->fetch('content') ?>
        </div>
    </main>
    <footer>
    </footer>
</body>

なるほど、なかなかボリュームがありますな。

openapi-generatorを使いたい2

openapi.yaml

openapi: 3.0.3
info:
  description: "GET/POST IPv4 Address"
  version: "1.0.0"
  title: "openapi-rust"
tags:
  - name: "IP"
paths:
  /ip:
    get:
      responses:
        "200":
          description: "Get Global IPv4 address of the system"
          content:
            application/json:
              schema:
                type: object
                properties:
                  IPv4_address:
                    type: string
                    format: ipv4
                  checked_at:
                    type: string
                    format: date-time
        "500":
          description: "Internal Server Error"
    post:
      requestBody:
        description: "IPv4 address to register"
        content:
          application/json:
            schema:
              properties:
                IPv4_address:
                  type: string
                  format: ipv4
      responses:
        "200":
          description: "The new IPv4 address has been registered"
        "500":
          description: "Internal Server Error"

$ sudo npm install @openapitools/openapi-generator-cli -g

Makefile

generate:
  openapi-generator-cli generate \
    -i ./openapi.yaml \
    -g rust-server \
    -o .

なんか上手くいかないです

openapi-generatorを使いたい

openapi.yaml

openapi: 3.0.2
info:
  version: 0.1.0
  title: example
servers:
  - url: 'http://192.168.56.10:8080/example'
paths:
  /hello:
    get:
      description: Hello World
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Hello'
      tags:
        - example
components:
  schemas:
    Hello:
      type: object
      properties:
        message:
          type: string
          example: 'Hello World'
      x-tags:
        - example

application.yaml

inputSpec: 'openapi.yaml'
generatorName: spring
outputDir: modules/application
additionalProperties:
  configPackage: 'com.mamezou_tech.example.controller.configuration'
  modelPackage: 'com.mamezou_tech.example.controller.model'
  apiPackage: 'com.mamezou_tech.example.controller.api'
  invokerPackage: 'com.mamezou_tech.example.controller.api'
  groupId: 'com.mamezou_tech.example-service'
  dateLibrary: java8
  java8: true
  library: spring-boot
  artifactId: 'example-application'
  artifactVersion: '0.1.0'
  snapshotVersion: 'true'
  useTags: true

$ sudo docker run -it –rm -v `pwd`:/build -v example:/root/.m2 maven:3.8-eclipse-temurin-17-focal bash
$ curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.0.0/openapi-generator-cli-6.0.0.jar -o /tmp/openapi-generator-cli.jar
$ cd /build
$ java -DsupportingFiles -jar /tmp/openapi-generator-cli.jar batch application.yaml
[main] INFO o.o.codegen.cmd.GenerateBatch – Batch generation using up to 4 threads.
Includes: /build
Root: /build
[pool-1-thread-1] Generating spring (outputs to /build/modules/application)…
################################################################################
# Thanks for using OpenAPI Generator. #
# Please consider donation to help us maintain this project 🙏 #
# https://opencollective.com/openapi_generator/donate #
################################################################################
[pool-1-thread-1] Finished generating spring…
[SUCCESS] Batch generation finished 1 generators successfully.
$ cd modules/application
$ mvn install

なんだろう、spring bootが多いな

Swaggerの各フィールドの詳細

Info

info:
  title: Sample API
  description: A short description of API.
  termsOfService: http://example.com/terms/
  contact:
    name: API support
    url: htttp://www.example.com/support
    email: support@example.com
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0

servers

servers:
  - url: https://dev.sample-server.com/v1
    description: Development server
  - url: https://stg.sample-server.com/v1
    description: Staging server
  - url: https://api.sample-server.com/v1
    description: Production server

Path

paths:
  /users:
    get:
      tags:
        - users
      summary: Get all users.
      description: Returns an array of User model
      parameters: []
      response:
        '200': # http status
          description: A Json array of User model
          content:
            application/json: # レスポンスの形式指定
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User' # 参照するモデル
                example: 
                  - id: 1
                    name: Jane Doe
                  - id: 2
                    name: Jane Doe
    post:
      tags:
        - users
      summary: Create a new User
      descrpption: Create a new User
      parameters: []
      requestBody:
        description: user to create
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
            example:
              id: 3
              name: Richard Roe
      responses:
        '201':
          description: CREATED
  /users/{userId}:
    get:
      tags:
        - users
      summary: Get user by ID.
      description: Returns a single User model
      parameters: # リクエストパラメター
        - name: userId
          in: path # パラメータをパス内に含める
          description: user id
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: A single User model
          content:
            application/json:
              schema:
                type: object
                items:
                  $ref: '#/components/schemas/User'
                example:
                  id: 1
                  name: John Doe

components
L 定義したモデルは $ref: ‘#/components/schemas/User’ というように参照する
L modelエリアに表示され、構造やサンプル値が参照できる

components:
  schemas:
    User:
      type: object # 型
      required:
        - id
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
    Product:
      type: object
      required:
        - id
        - price
      properties:
        id:
          type: integer
          format: int64
          example: 1
        name:
          type: string
          example: Laptop
        price:
          type: integer
          example: 1200

security
メソッド定義部分に鍵マークが表示される

security:
  - api_key []
  - users_auth:
    - write:users
    - read:users

externalDocs

exeternalDocs:
  description: Find more info here
  url: https://example.com

tags:

tags:
  - name: users
    description: Access to users
  - name: products
    descrption: Access to Products

なるほど、実際に書いてみると全然違うな…

Swaggerの基本構造を学びたい

sample.yaml

openapi: 3.0.0
info:
  ...
servers:
  ...
paths:
  ...
components:
  ...
security:
  ...
tags:
  ...
exeternalDocs:
  ....

openapi: バージョンを記載(必須)
info: APIのメタデータ(必須) title, description, termsOfService, contact, license, version
servers: APIを提供するサーバを記述(配列で複数記述可能, 任意)
paths: APIで利用可能なエンドポイントやメソッドを記述(必須)
L put(tag, summary, description, operationId, requestBody(description, contents(application/json, xml, x-www-form-urlencoded), required), responses)
L post(tags, summary, description, operationId, requestBody(description, contents(application/json, xml, x-www-form-urlencoded), required)), response)
L get (tags, summary, description, operationId, requestBody(description, contents(application/json, xml, x-www-form-urlencoded), required)), response, security)
L get (tags, summary, description, operationId, parameters, response)
components: APIで使用するオブジェクトスキーマを記述(必須)
L schimas: type, properties(type(integer, string, array), format(int64, int32, date-time), example, required)
security: API全体を通して使用可能なセキュリティ仕様を記述する(OAuth)
tags: name, description, externalDocs, APIで使用されるタグのリスト。各種ツールによってパースされる際は、記述された順序で出力される。タグ名はユニークでなければならない。(任意)
externalDocs: 外部ドキュメントを記述する(API仕様書)

なるほど、swaggerの仕様を理解するだけで大分違うな…

OpenAPI, Swagger入門

OpenAPI: RESTful APIの仕様を記述するフォーマット
Swagger: OpenAPIを使用するツール

## Swagger Editor
$ git clone https://github.com/swagger-api/swagger-editor.git
$ cd swagger-editor
$ npm start

http://192.168.56.10:3001/

なんだこれは…

OpenAPIに慣れたい

swaggerのbasic structure
https://swagger.io/docs/specification/basic-structure/

$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require zircote/swagger-php
$ php composer.phar exec openapi -h

openapiファイルの作成
$ vendor/bin/openapi -o ${出力先} –format ${出力フォーマット} ${スキャン対象ディレクトリ}
$ vendor/bin/openapi -o openapi.json –format json app/Http/Controller/Api

<?php

use OpenApi\Annotations as OA;

class OpenApi {}

class MyController {

	public funtion getResource(){
		
	}
}

https://zircote.github.io/swagger-php/guide/
The idea is to add swagger-php annotations or attributes next to the relevant PHP code in your application. These will contain the details about your API and swagger-php will convert those into machine-readable OpenAPI documentation.

require("vendor/autoload.php");

$openapi = \OpenApi\Generator::scan(["project"]);

header('Content-Type: application/x-yaml');
echo $openapi->toYaml();

annotation:あるデータに対して関連する情報(メタデータ)を注釈として付与すること

/**
 * @OA\Get(
 * tags={"Common"},
 * path="/api/user",
 * @OA\Response(
 *		response="200".
 *		description="success",
 *		@OA\JsonContent(ref="#/components/schemas/user_responder")
 * ),
 * @OA\Response(
 *		response="204",
 *		description="there is no authorized user",
 *		@OA\JsonContent(ref="#/components/schemas/204_no_content")
 *		)
 *	)
 */

schema:

/**
 * @OA\Schema(
 *	schema="user_responder",
 *	required={"id", "name", "email","created_at"},
 *	@OA\Property(
 *		property="id",
 *		type="integer",
 *		description="userId",
 *		example-"1"
 *	),
 *	@OA\Property(
 *		property="name",
 *		type="string",
 *		description="username",
 *		example="sample taro"
 *	),
 *
 *)
 */

annotationはパスとレスポンスで、schemaは入力値を定義しとるんかな。
どちっかというと、swaggerがよくわかってない。

openapi-generatorを利用

$ npm init
$ npm install –include=dev openapi-generator-cli
npm ERR! code E404
npm ERR! 404 Not Found – GET https://registry.npmjs.org/openapi-generator-cli – Not found
npm ERR! 404
npm ERR! 404 ‘openapi-generator-cli@*’ is not in this registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.

npm ERR! A complete log of this run can be found in:
あれ? イマイチよくわからない

PostgreSQLでリレーション関係のあるテーブルを作りたい

create table person (
	id int,
	first_name varchar(50),
	last_name varchar(50),
	gender varchar(7),
	birth_day date
);

testdb=# \d person
Table “public.person”
Column | Type | Collation | Nullable | Default
————+———————–+———–+———-+———
id | integer | | |
first_name | character varying(50) | | |
last_name | character varying(50) | | |
gender | character varying(7) | | |
birth_day | date | | |

### テーブルに制約をつける

create table person (
	id BIGSERIAL not null primary key,
	first_name varchar(50) not null,
	last_name varchar(50) not null,
	gender varchar(7) not null,
	birth_day date not null,
	email varchar(150)
);

bigserial 整数を自動決定
primary key キーとして扱う
not null 空白を許可しない

### テーブルにデータを挿入

insert into person (
	first_name,
	last_name,
	gender,
	birth_day
) values ('Anne', 'Smith', 'Femail', DATE '1988-01-09');

insert into person (
	first_name,
	last_name,
	gender,
	birth_day,
	email
) values ('Jake', 'Jone', 'Male', DATE '1990-01-10', 'jake@gmail.com');

testdb=# select * from person;
id | first_name | last_name | gender | birth_day | email
—-+————+———–+——–+————+—————-
1 | Anne | Smith | Femail | 1988-01-09 |
2 | Jake | Jone | Male | 1990-01-10 | jake@gmail.com
(2 rows)

データの自動生成
https://mockaroo.com/

create table person (
	id BIGSERIAL not null primary key,
	first_name varchar(50) not null,
	last_name varchar(50) not null,
	gender varchar(7) not null,
	email varchar(150),
	date_of_birth date not null,
	country varchar(50) not null
);

$ \i /home/vagrant/dev/app/person.sql
$ select * from person;

create table person (
	id BIGSERIAL not null primary key,
	first_name varchar(50) not null,
	last_name varchar(50) not null,
	gender varchar(7) not null,
	email varchar(150),
	date_of_birth date not null,
	country varchar(50) not null,
	car_id BIGINT REFERENCES car (id),
	unique(car_id)
);
create table car (
	id BIGSERIAL not null primary key,
	make varchar(50) not null,
	model varchar(50) not null,
	price varchar(50) not null
);

BIGINT REFERENCESで関連づけるのか。なるほど、勉強になるね。