ホームページ >バックエンド開発 >PHPチュートリアル >Pionia ジェネリック サービスの究極ガイド。

Pionia ジェネリック サービスの究極ガイド。

王林
王林オリジナル
2024-07-16 20:46:49656ブラウズ

The Ultimate Guide to Pionia Generic Services.

Pionia フレームワークは、Rest プラットフォームの開発方法を変える PHP Rest フレームワークです。既存のすべてのフレームワークとは異なり、プロセス全体にまったく新しい外観が与えられ、API の開発がはるかにシンプルになり、退屈さが軽減されます。これは、ムーンライトと呼ばれる、異なる、かなり「新しい」パターンで実行されるためです。

Moonlight 自体は新しいアーキテクチャ/パターンではなく、ほとんどの機関/企業/開発者が使用していますが、名前が付けられていないだけです。しかし、今日は月光について話しているのではありません。それについては、ここにある私の他の記事を読んで、コメントを残すこともできます。

新しい Pionia プロジェクトをブートストラップするには、コンポーザーがすでにセットアップされていると仮定して、次のコマンドを実行する必要があります。

todo_app を作成しましょう。

composer create-project pionia/pionia-app todo_app

以下のように pionia コマンドを使用して同じプロジェクトを実行することもできます。

php pionia serve

ログの発生をリアルタイムで確認するには、2 番目のターミナルを開いて次のコマンドを実行します。

tail -f server.log

サービスの背景。

Pionia フレームワークのサービスは心臓部であり、おそらく API の開発中にほとんどの時間を費やす唯一の部分です。 Pionia のすべての通常サービスは PioniaRequestBaseRestService を拡張します。 Pionia の通常のサービスは次のようになります。

namespace application\services;


use Exception;
use Pionia\Request\BaseRestService;
use Pionia\Response\BaseResponse;
use Porm\Porm;

class UserService extends BaseRestService
{
    /**
     * @throws Exception
     */
    protected function login(array $data): BaseResponse
    {
        // password and username are required, without them we won't proceed even
        $this->requires(["username", "password"]);

        $username = $data["username"];
        $password = password_hash($data['password'], PASSWORD_DEFAULT);

        $user = Porm::from('user')->get(['username' => $username, 'password' => $password]);
        //You can do more here, maybe generate a JWT token or add more checks
        // for example if the user is an active or not
        if ($user) {
            return BaseResponse::JsonResponse(0, "Login successful", $user);
        }
        throw new Exception("User with the given username and password not found");
    }

}

サービスを構築した後、それを今後処理するスイッチに登録する必要があります。 Pionia のスイッチについて知らない場合は、ここのドキュメントでスイッチについて読むことができます。したがって、別のスイッチをまだ作成していない場合は、スイッチ フォルダー (おそらく MainAppSwitch.php 内) に移動し、上記のサービスを registerServices メソッドで以下のように登録します

     /**
     * Register your services here.
     *
     * @return array
     */
    public function registerServices(): array
    {
        return [
            'user' => new UserService(),
            'todo' => new TodoService()
        ];
    }

これは、これからカーネルによってサービスが自動検出されるようにする方法です。一般的な設定では、ルーターとコントローラーを追加してこのサービスにマッピングしますが、Pionia では異なるアプローチをとります。同じサービスを複数のスイッチに登録できることに注意してください。すべてのスイッチはその API エンドポイントによって処理されるため、これが API バージョン管理の概念を実現する方法です。デフォルトでは、MainAppSwitch は /api/v1/ でアクセスできます。

リクエストで次の内容を送信することで、このサービスを指定できます。

// POST http://localhost:8000/api/v1/
{
    "SERVICE": "user",
    "ACTION": "login",
    "username": "pionia",
    "password": "pionia1234"
}

お気づきかと思いますが、ACTION は、SERVICE/サービス/クラスで作成したアクション/メソッドの名前であり、登録時にユーザー名にバプテスマを施しました。

これが、Pionia での通常のサービスの仕組みです。

以下は、Piona で CRUD を実行する完全なサービスです。これは、todo_db という MySQL データベース内の todos という次の単純なテーブルに基づいています。

create table todo_db.todos
(
    id          int auto_increment primary key,
    title       varchar(200)                        not null,
    description text                                null,
    created_at  timestamp default CURRENT_TIMESTAMP null
) engine = InnoDB;
use Exception;
use Pionia\Request\BaseRestService;
use Pionia\Request\PaginationCore;
use Pionia\Response\BaseResponse;
use Porm\exceptions\BaseDatabaseException;
use Porm\Porm;

class TodoService extends BaseRestService
{
    /**
     * Returns all todos
     * @throws Exception
     */
    public function list(): BaseResponse
    {
        $result = Porm::table('todos')
            ->using('db')
            ->columns(['id', 'title', 'description', 'created_at'])
            ->all();

        return BaseResponse::JsonResponse(0, null, $result);
    }

    /**
     * Returns a single todo
     * @throws Exception
     */
    public function details(array $data): BaseResponse
    {
        $this->requires(['id']);
        $id = $data['id'];

        $result = Porm::table('todos')
            ->using('db')
            ->columns(['id', 'title', 'description', 'created_at'])
            ->get(['id' => $id]);

        return BaseResponse::JsonResponse(0, null, $result);
    }

    /**
     * Creates a new todo
     * @throws Exception
     */
    public function create(array $data): BaseResponse
    {
        $this->requires(['title', 'description']);
        $title = $data['title'];
        $description = $data['description'];

        $result = Porm::table('todos')
            ->save(['title' => $title, 'description' => $description]);

        return BaseResponse::JsonResponse(0, 'Todo created successfully', $result);
    }

    /**
     * Updates a todo
     * @throws Exception
     */
    public function update(array $data): BaseResponse
    {
        $this->requires(['id']);

        $id = $data['id'];

        $todo = Porm::table('todos')
            ->get($id); // similar to `get(['id' => $id])`

        // if the todo is not found, we throw an exception
        if (!$todo) {
            throw new BaseDatabaseException('Todo not found');
        }

        $description = $data['description'] ?? $todo->description;
        $title = $data['title'] ?? $todo->title;

        // we update in a transaction as below
        $result= null;
        Porm::table('todos')
            ->inTransaction(function () use ($description, $title, $id, &$result) {
                Porm::table('todos')
                    ->update(['description' => $description, 'title' => $title], $id);

                $result = Porm::table('todos')
                    ->get($id);
            });

        return BaseResponse::JsonResponse(0, "Todo $id updated successfully", $result);
    }

    /**
     * Deletes a todo
     * @throws Exception
     */
    public function delete(array $data): BaseResponse
    {
        $this->requires(['id']);
        $id = $data['id'];

        $todo = Porm::table('todos')
            ->get($id);

        if (!$todo) {
            throw new BaseDatabaseException('Todo not found');
        }

        $deleted = false;
        Porm::table('todos')
            ->inTransaction(function () use ($id, &$deleted) {
                Porm::table('todos')
                    ->delete($id);
                $deleted = true;
            });
        if (!$deleted) {
            throw new BaseDatabaseException('Todo not deleted');
        }
        return BaseResponse::JsonResponse(0, "Todo $id deleted successfully");
    }

    /**
     * Returns a random todo object if the size is not defined or 1,
     * else returns an array of random todos
     * @throws Exception
     */
    public function random($data): BaseResponse
    {
        $size = $data['size'] ?? 1;

        $result = Porm::table('todos')
            ->random($size);

        return BaseResponse::JsonResponse(0, null, $result);
    }

    /**
     * Returns a paginated list of todos
     * @throws Exception
     */
    public function paginatedList(array $data): BaseResponse
    {
        $offset = $data['offset'] ?? 0;
        $limit = $data['limit'] ?? 3;

        $paginator = new PaginationCore($data, 'todos', $limit, $offset, 'db');
        $result = $paginator->paginate();

        return BaseResponse::JsonResponse(0, null, $result);
    }
}

TodoService はすでに登録されているため、追加のルートやコントローラーの追加は必要なく、リクエスト内のアクションを開始するだけで、上記のすべてのアクションから均一な応答が得られるはずです。

これはそれほどやるべきことではなく、Pionia (サービスの構築) で行う唯一のことですが、TodoService の上記のアクションはすべて省略でき、同じ機能が得られます。汎用サービスが登場!

Todo サービス、一般的な方法。

ロジックが作成、削除、ページ分割、一覧表示、更新、削除、または取得だけであれば、必要なのは汎用サービスだけかもしれません。

Pionia は、使用する汎用サービスとミックスインの両方を提供します。ミックスインを組み合わせて、新しい汎用サービス全体を構築できます。

提供されるミックスインには、ListMixin、CreateMixin、DeleteMixin、UpdateMixin、RandomMixin、および RetrieveMixin が含まれます。内部では、Generic サービスも GenericService を拡張しながらこれらの Mixin を組み合わせているだけです。

提供される汎用サービスには、RetrieveCreateUpdateService、RetrieveListCreateService、RetrieveListCreateUpdateDeleteService、RetrieveListDeleteService、RetrieveListRandomService、RetrieveListUpdateDeleteService、RetrieveListUpdateService、および UniversalGenericService が含まれます。

上記のジェネリックで希望どおりにミックスインが結合されない場合は、GenericService を拡張して、使用するすべてのミックスインを呼び出し、カスタム ジェネリック サービスを作成できます。

ミックスインを使用するには、前に拡張した通常の BaseRestService ではなく、PioniaGenericsBaseGenericService を拡張する必要があることに注意してください。また、ミックスインは単なる PHP の特性であり、そのように使用する必要があることを覚えておいてください。

TodoService をリファクタリングするには、定義されたすべてのミックスインを使用するため、最後に述べた汎用サービス UniversalGenericService が必要になります。

拡張するクラスを変更することから始めましょう。これを以下のようにリファクタリングします

use Pionia\Generics\UniversalGenericService;
// ... rest of the imports

class TodoService extends UniversalGenericService
{
// ... rest of your actions
}

Before we do anything, let's first define the table we want to target in the database. We use the $table property for this. This is a compulsory feature and must be defined for all generic views.

use Pionia\Generics\UniversalGenericService;
// ... rest of the imports

class TodoService extends UniversalGenericService
{
   public string $table = "todo";
// ... rest of your actions
}

Secondly, from our list action, we are defining columns we want to return, however, we are defining all. If you want to return a certain range of columns only, we define the $listColumns(which defaults to all) and pass the columns we want to return. Let's just still pass all though it is the default behavior of the service.

use Pionia\Generics\UniversalGenericService;
// ... rest of the imports

class TodoService extends UniversalGenericService
{
    public string $table = "todo";
    public ?array $listColumns = ['id', 'title', 'description', 'created_at'];

// ... rest of your actions
}

At this point, we can delete the list action from our service. That's complete!

Our second target action is now details. This one can be replaced by defining the $pk_field which defaults to id. Since our primary key field for our todo table is also id, we don't need to define it, we just need to delete it too! Remember, this one also uses the defined $listColumns for columns to return from the DB.
The RetrieveMixin also defines another sister action to this called retrieve, so in your request, you can use ACTION as details or retrieve, the two will perform the same thing.
Since we already have all we need, we can drop the details action too!

Our third action is create. For this, we must define the $createColumns to define those columns we shall be looking for from the request(required) to create a record. Let's add the property now.

use Pionia\Generics\UniversalGenericService;
// ... rest of the imports

class TodoService extends UniversalGenericService
{
    public string $table = "todo";
    public ?array $listColumns = ['id', 'title', 'description', 'created_at'];
    public ?array $createColumns = ['title', 'description'];

// ... rest of your actions
}

After adding, go ahead and delete it too!

Our fourth action is update. For this, we require the $pk_field and can also optionally define the $updateColumns. If undefined, the responsible mixin checks if any of the properties were defined in the request, and will update only those.
Let's add the $updateColumns and give it the only properties we intend to update.

use Pionia\Generics\UniversalGenericService;
// ... rest of the imports

class TodoService extends UniversalGenericService
{
    public string $table = "todo";
    public ?array $listColumns = ['id', 'title', 'description', 'created_at'];
    public ?array $createColumns = ['title', 'description'];
    public ?array $updateColumns = ['title', 'description'];

// ... rest of your actions
}

We can now drop the update action too!

For our fifth action, delete, we only need the $pk_field which is by default id, so we shall be checking if id was passed in the request, and then we delete the associated record. So, just delete it, we already have all we need!

Now to our sixth action, random, this also uses the $listColumns to determine the columns to fetch from the DB per record. We already have out property defined, so, just drop it too!

For our seventh action, paginatedList, we can drop it, and in any request, we target our list action, but we define any of the following pairs of keys in our request.

  1. limit and offset on the request object level.
{
   "SERVICE": "todo",
   "ACTION": "list",
   "limit": 3,
   "offset": 0
}
  1. PAGINATION or pagination object on the request with limit and offset keys.
{
   "SERVICE": "todo",
   "ACTION": "list",
   "PAGINATION": {
      "limit": 3,
      "offset": 0,
   }
}
  1. SEARCH or search object on the request object with limit and offset keys.
{
   "SERVICE": "todo",
   "ACTION": "list",
   "SEARCH": {
      "limit": 3,
      "offset": 0
   }
}

Note: Both the limit and offset keys must be defined for pagination to kick in.

And just like that, our service now has been reduced to the following.

use Pionia\Generics\UniversalGenericService;

class TodoService extends UniversalGenericService
{
    public string $table = "todo";
    public ?array $listColumns = ['id', 'title', 'description', 'created_at'];
    public ?array $createColumns = ['title', 'description'];
    public ?array $updateColumns = ['title', 'description'];
}

Let's do a little more cleanup. As we had mentioned earlier, if we are listing all columns from our table, then we don't need to define the $listColumns property, let's remove that too.

use Pionia\Generics\UniversalGenericService;

class TodoService extends UniversalGenericService
{
    public string $table = "todo";
    public ?array $createColumns = ['title', 'description'];
    public ?array $updateColumns = ['title', 'description'];
}

Also, since our update can also discover the columns to update from the request data, let's remove the $updateColumns too!

And we are left with the following as our new TodoService but still exposing the actions of list(all and paginated), create, update, delete, retrieve or details and random

use Pionia\Generics\UniversalGenericService;

class TodoService extends UniversalGenericService
{
    public string $table = "todo";
    public ?array $createColumns = ['title', 'description'];
}

You can also override how we get a single record and multiple records. You might not need it, but sometimes you may need to add where clauses and other conditions as you see fit. For that, you can read about it in this section in the docs.

Also, you may want to add your other actions in the same generic service, this is fully supported and will work as if you're in normal services, however, make sure none of those actions share the names with the provided mixin actions or otherwise you stand a chance of overriding the provided actions.

This also drives to the last point, what if you intend to override the default action? that's also okay! You can also look into it under this section of the docs.

Welcome to Pionia Framework, where we believe in both developer and program performance, writing precise and maintainable codebase with simplicity both at the front end and the back end!

Let me hear what you say about the Pionia Framework specifically about generic services. Happy coding!

以上がPionia ジェネリック サービスの究極ガイド。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。