”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > Pionia 通用服务终极指南。

Pionia 通用服务终极指南。

发布于2024-08-06
浏览:705

The Ultimate Guide to Pionia Generic Services.

Pionia framework is a PHP Rest framework that is changing how we used to develop Rest platforms. Unlike all the existing frameworks, it gives the entire process a whole new look, making the development of APIs much simpler and less boring. This is because it runs on a different and rather 'new' pattern called moonlight.

Per se, moonlight is not a new architecture/pattern, most institutions/companies/developers have been using it but just unnamed. But today, we aren't talking about moonlight, you can read about it in my other article here and even leave your comments.

To bootstrap a new Pionia project, you need to run the following command assuming you already have composer set up.

Let's create a todo_app.

composer create-project pionia/pionia-app todo_app

You can also run the same project using the pionia command like below:

php pionia serve

To watch you logs as they happen in real time, open a second terminal and run the following command:

tail -f server.log

Background on services.

Services in the Pionia framework are the heart, probably the only part you will spend most of your time while developing your APIs. All normal services in Pionia extend Pionia\Request\BaseRestService. A normal service in Pionia may look like this.

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");
    }

}

After building your service, you need to register it in the switch that shall be handling it from now on. If you are not aware of switches in Pionia, you can read about them here in the docs. So, head over to our switches folder, probably in the MainAppSwitch.php if you have not yet created another one, and register the above service as below in the registerServices method

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

This is how you get your service to be auto-discovered by the kernel from now on. In typical settings, you would have added a router, and a controller to map to this service, but Pionia approaches things differently. Remember, you can have the same service registered in multiple switches. This is how we achieve the concept of API versioning since every switch is handled by its API endpoint. By default, MainAppSwitch can be accessed on /api/v1/.

In your requests, you can point to this service by sending the following.

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

If you notice, ACTION is the name on the action/method we created in our SERVICE/service/class which we baptised the name user upon registration.

This is how normal services work in Pionia.

Below is a complete service that performs CRUD in Piona. It is based on the following simple table called todos in a MySQL database called todo_db.

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);
    }
}

And since our TodoService is already registered, that's all we need to do, no adding extra routes, no adding controllers, just start hitting the actions in your requests and you should get uniform responses from all the above actions.

However much this is not alot to do, and the only thing to do in Pionia(building services), all the above actions in our TodoService can be omitted and we still get the same functionality, This is where our Generic Services come in!

Todo Service, The Generic Way.

If your logic is not more than create, delete, paginate, list, update, delete, or retrieve then Generic Services might be all you need.

Pionia provides both generic services and mixins to use. Mixins can be put together to put up your entire new Generic Service.

Provided mixins include ListMixin, CreateMixin, DeleteMixin, UpdateMixin, RandomMixin and RetrieveMixin. Under the hood, even Generic services are just combining these Mixins while extending GenericService.

Provided Generic Services include RetrieveCreateUpdateService, RetrieveListCreateService, RetrieveListCreateUpdateDeleteService, RetrieveListDeleteService, RetrieveListRandomService, RetrieveListUpdateDeleteService, RetrieveListUpdateService and UniversalGenericService.

If the above Generics don't combine the mixins in the way you want, you can extend GenericService and call all the mixins you want to use thus creating your custom generic service.

Remember, to use mixins, you must be extending Pionia\Generics\Base\GenericService not the normal BaseRestService we extended before. Also, remember that mixins are just PHP traits and should be used that way.

To refactor our TodoService, we shall need the last mentioned generic service, UniversalGenericService as it uses all the defined mixins.

Let's start by changing the class we extend. Refactor this as below

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!

版本声明 本文转载于:https://dev.to/jet_ezra/the-ultimate-guide-to-pionia-generic-services-id?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何处理PHP文件系统功能中的UTF-8文件名?
    如何处理PHP文件系统功能中的UTF-8文件名?
    在PHP的Filesystem functions中处理UTF-8 FileNames 在使用PHP的MKDIR函数中含有UTF-8字符的文件很多flusf-8字符时,您可能会在Windows Explorer中遇到comploreer grounder grounder grounder gro...
    编程 发布于2025-03-28
  • 如何使用组在MySQL中旋转数据?
    如何使用组在MySQL中旋转数据?
    在关系数据库中使用mySQL组使用mySQL组进行查询结果,在关系数据库中使用MySQL组,转移数据的数据是指重新排列的行和列的重排以增强数据可视化。在这里,我们面对一个共同的挑战:使用组的组将数据从基于行的基于列的转换为基于列。 Let's consider the following ...
    编程 发布于2025-03-28
  • 为什么我的CSS背景图像出现?
    为什么我的CSS背景图像出现?
    故障排除:CSS背景图像未出现 ,您的背景图像尽管遵循教程说明,但您的背景图像仍未加载。图像和样式表位于相同的目录中,但背景仍然是空白的白色帆布。而不是不弃用的,您已经使用了CSS样式: bockent {背景:封闭图像文件名:背景图:url(nickcage.jpg); 如果您的html,css...
    编程 发布于2025-03-28
  • 如何在全高布局中有效地将Flexbox和垂直滚动结合在一起?
    如何在全高布局中有效地将Flexbox和垂直滚动结合在一起?
    在全高布局中集成flexbox和垂直滚动Traditional Flexbox Approach (Old Properties)Flexbox layouts using the old syntax (display: box) permit full-height apps with ver...
    编程 发布于2025-03-28
  • 如何使用Python理解有效地创建字典?
    如何使用Python理解有效地创建字典?
    在python中,词典综合提供了一种生成新词典的简洁方法。尽管它们与列表综合相似,但存在一些显着差异。与问题所暗示的不同,您无法为钥匙创建字典理解。您必须明确指定键和值。 For example:d = {n: n**2 for n in range(5)}This creates a dicti...
    编程 发布于2025-03-28
  • 为什么PYTZ最初显示出意外的时区偏移?
    为什么PYTZ最初显示出意外的时区偏移?
    与pytz 最初从pytz获得特定的偏移。例如,亚洲/hong_kong最初显示一个七个小时37分钟的偏移: 差异源利用本地化将时区分配给日期,使用了适当的时区名称和偏移量。但是,直接使用DateTime构造器分配时区不允许进行正确的调整。 example pytz.timezone(...
    编程 发布于2025-03-28
  • 如何配置Pytesseract以使用数字输出的单位数字识别?
    如何配置Pytesseract以使用数字输出的单位数字识别?
    Pytesseract OCR具有单位数字识别和仅数字约束 在pytesseract的上下文中,在配置tesseract以识别单位数字和限制单个数字和限制输出对数字可能会提出质疑。 To address this issue, we delve into the specifics of Te...
    编程 发布于2025-03-28
  • 如何检查对象是否具有Python中的特定属性?
    如何检查对象是否具有Python中的特定属性?
    方法来确定对象属性存在寻求一种方法来验证对象中特定属性的存在。考虑以下示例,其中尝试访问不确定属性会引起错误: >>> a = someClass() >>> A.property Trackback(最近的最新电话): 文件“ ”,第1行, AttributeError: SomeClass...
    编程 发布于2025-03-28
  • 如何在Java中执行命令提示命令,包括目录更改,包括目录更改?
    如何在Java中执行命令提示命令,包括目录更改,包括目录更改?
    在java 通过Java通过Java运行命令命令可能很具有挑战性。尽管您可能会找到打开命令提示符的代码段,但他们通常缺乏更改目录并执行其他命令的能力。 solution:使用Java使用Java,使用processBuilder。这种方法允许您:启动一个过程,然后将其标准错误重定向到其标准输出。...
    编程 发布于2025-03-28
  • 为什么尽管有效代码,为什么在PHP中捕获输入?
    为什么尽管有效代码,为什么在PHP中捕获输入?
    在php ;?>" method="post">The intention is to capture the input from the text box and display it when the submit button is clicked.但是,输出...
    编程 发布于2025-03-28
  • 如何在Java的全屏独家模式下处理用户输入?
    如何在Java的全屏独家模式下处理用户输入?
    Handling User Input in Full Screen Exclusive Mode in JavaIntroductionWhen running a Java application in full screen exclusive mode, the usual event ha...
    编程 发布于2025-03-28
  • Python读取CSV文件UnicodeDecodeError终极解决方法
    Python读取CSV文件UnicodeDecodeError终极解决方法
    在试图使用已内置的CSV模块读取Python中时,CSV文件中的Unicode Decode Decode Decode Decode decode Error读取,您可能会遇到错误的错误:无法解码字节 在位置2-3中:截断\ uxxxxxxxx逃脱当CSV文件包含特殊字符或Unicode的路径逃...
    编程 发布于2025-03-28
  • 找到最大计数时,如何解决mySQL中的“组函数\”错误的“无效使用”?
    找到最大计数时,如何解决mySQL中的“组函数\”错误的“无效使用”?
    如何在mySQL中使用mySql 检索最大计数,您可能会遇到一个问题,您可能会在尝试使用以下命令:理解错误正确找到由名称列分组的值的最大计数,请使用以下修改后的查询: 计数(*)为c 来自EMP1 按名称组 c desc订购 限制1 查询说明 select语句提取名称列和每个名称...
    编程 发布于2025-03-28
  • 为什么使用固定定位时,为什么具有100%网格板柱的网格超越身体?
    为什么使用固定定位时,为什么具有100%网格板柱的网格超越身体?
    网格超过身体,用100%grid-template-columns 为什么在grid-template-colms中具有100%的显示器,当位置设置为设置的位置时,grid-template-colly修复了?问题: 考虑以下CSS和html: class =“ snippet-code”> g...
    编程 发布于2025-03-28
  • 如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    使用http request 上传文件上传到http server,同时也提交其他参数,java.net.net.urlconnection and Multipart/form-data Encoding是普遍的。 Here's a breakdown of the process:Mu...
    编程 发布于2025-03-28

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3