Pionia 通用服務終極指南。

王林
發布: 2024-07-16 20:46:49
原創
552 人瀏覽過

The Ultimate Guide to Pionia Generic Services.

Pionia 框架是一個 PHP Rest 框架,它正在改變我們過去開發 Rest 平台的方式。與所有現有框架不同,它使整個流程煥然一新,使 API 的開發變得更加簡單,不再那麼枯燥。這是因為它運行在一種不同的、相當「新」的模式上,稱為月光。

月光本身並不是一種新的架構/模式,大多數機構/公司/開發者都在使用它,但只是未命名。不過今天我們不是在談論月光,你可以在我的另一篇文章中閱讀它,甚至留下你的評論。

要引導一個新的 Pionia 項目,假設您已經設定了 Composer,則需要執行以下命令。

讓我們建立一個todo_app。

composer create-project pionia/pionia-app todo_app
登入後複製

您也可以使用 pionia 指令運行相同的項目,如下所示:

php pionia serve
登入後複製

要即時查看日誌,請開啟第二個終端並執行以下命令:

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 中的開關,您可以在文件中閱讀有關它們的資訊。因此,請前往我們的 Switch 資料夾(如果您尚未建立另一個資料夾,可能位於 MainAppSwitch.php 中),然後在 registerServices 方法中註冊上述服務,如下所示

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

從現在開始,這就是讓核心自動發現您的服務的方法。在典型設定中,您將新增一個路由器和一個控制器來對應到此服務,但 Pionia 的處理方式有所不同。請記住,您可以在多個交換器中註冊相同的服務。這就是我們實作 API 版本控制概念的方式,因為每個開關都由其 API 端點處理。預設情況下,可以在 /api/v1/ 上存取 MainAppSwitch。

在您的要求中,您可以透過發送以下內容來指向此服務。

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

如果您注意到,ACTION 是我們在 SERVICE/service/class 中建立的操作/方法的名稱,我們在註冊時為使用者名稱命名。

這就是 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 提供一般服務和 mixin 可供使用。 Mixins 可以組合在一起來建立整個新的通用服務。

提供的 mixins 包括 ListMixin、CreateMixin、DeleteMixin、UpdateMixin、RandomMixin 和 RetrieveMixin。在底層,即使是通用服務也只是在擴展 GenericService 的同時組合這些 Mixin。

提供的通用服務包括RetrieveCreateUpdateService、RetrieveListCreateService、RetrieveListCreateUpdateDeleteService、RetrieveListDeleteService、RetrieveListRandomService、RetrieveListUpdateDeleteService、RetrieveListUpdateService 和UniversalGenericService。

如果上述泛型沒有按照您想要的方式組合 mixin,您可以擴充 GenericService 並呼叫您想要使用的所有 mixin,從而建立自訂通用服務。

請記住,要使用 mixins,您必須擴展 PioniaGenericsBaseGenericService 而不是我們之前擴充的普通 BaseRestService。另外,請記住 mixin 只是 PHP 特徵,應該以這種方式使用。

要重構我們的 TodoService,我們需要最後提到的通用服務 UniversalGenericService,因為它使用所有定義的 mixin。

讓我們從更改我們擴展的類別開始。重構如下

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中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!