# Search

Search

Loading search index…

No recent searches

No results for "Query here"

# Generic Services

This section assumes that you have a basic understanding how Services work in Pionia. If you haven’t, you can check the Services section first.

# Introduction

Pionia Generic Services get their inspiration from Django Rest Framework’s Generic Views. They provide a set of actions that are common in most CRUD applications. This is to help you focus on the complex business logic and not the repetitive CRUD work. But it is not just CRUD, you can query random and paginated data too. You can also add your own actions as you see fit. This feature is available starting from the core version of v1.1.2.

## The Mixins#

There a few mixins that have been provided to help you with the CRUD operations. These mixins are:

### CreateMixin#

This mixin adds the `create` action to whatever generic service you have added. It is used to create a new record in the database. You can specify the columns to create in the table by providing the `$createColumns` array property in the service.

### DeleteMixin#

This mixin adds the `delete` action to whatever generic service you have added. It is used to delete a record from the database by `pk_field` defined in the service.

### ListMixin#

This mixin adds the `list` action to whatever generic service you have added. It also detects if the request is paginated and applies the pagination to the data accordingly. You can customise the columns to return by defining the `$listColumns` array property in the service.

### RetrieveMixin#

This mixins adds two actions which actually perform the same operation. The `retrieve` and the `details` action. They are used to get a single record from the database by the `pk_field` defined in the service. If the `$listColumns` property is defined, it will return only those columns.

### RandomMixin#

This mixin adds the `random` action to whatever generic service you have added. The number of records returned is by default `1`. But this can be overridden by defining the `size` or `limit` parameter in the request. If the size is finally one, it will return an object otherwise an array of objects.

### UpdateMixin#

This mixin adds the `update` action to whatever generic service you have added. It is used to update a record in the database. You can specify the columns to update in the table by providing the `$updateColumns` array property in the service. If these are not defined, then all/only the table columns that are defined in the request will be updated(partial update). Also, the item to update will depend on the `pk_field` defined in the service. By default this is `id`.

The mixins are not to be used outside services that extend the `GenericService` class. They are already included in the generic services. But if you want to use them, make sure you’re extending the `GenericService` class or any of the generic services defined below.

## The GenericService#

This is the core class that provides the basis for all the actions. It is a class that extends the `BaseRestService` class so you no longer need to do that. All available methods can be found here in the api reference.

You don’t need to interact with this class directly but through the following generic services.

1. RetrieveCreateUpdateService
2. RetrieveListCreateService
3. RetrieveListCreateUpdateDeleteService
4. RetrieveListDeleteService
5. RetrieveListRandomService
6. RetrieveListUpdateDeleteService
7. RetrieveListUpdateService
8. UniversalGenericService

Creating you custom generic service can be done as follows:

    use Pionia\Generics\Base\GenericService;

    class CreateRandomService extends GenericService
    {
        use CreateMixin, RandomMixin;
    }

Now you can let you services extend this class and you will have the `create` and `random` actions available.

    use application\services\CreateRandomService;

    class MyService extends CreateRandomService
    {
        public string $table = 'users';

        public array $createColumns = ['name', 'age', 'gender']
    }

Remember you still need to register all these service as usual. In your request you can now call the `create` and `random` actions.

    {
      "SERVICE": "create_random",
      "ACTION": "random",
      "size": 3
    }

The above request will return 3 random records from the `users` table.

`GenericService` inherits from `BaseRestService` which means that all the methods in the `BaseRestService` are available in the `GenericService`.

## RetrieveCreateUpdateService#

This generic service provides the `retrieve`, `create` and `update` actions.

    use Pionia\Generics\Base\RetrieveCreateUpdateService;

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';
        public array $createColumns = [];
    }

## RetrieveListCreateService#

This generic service provides the `retrieve`, `list` and `create` actions.

    use Pionia\Generics\Base\RetrieveListCreateService;

    class StudentService extends RetrieveListCreateService
    {
        public string $table = 'students';
        public array $createColumns = [];
    }

## RetrieveListCreateUpdateDeleteService#

This generic service provides the `retrieve`, `list`, `create`, `update` and `delete` actions.

    use Pionia\Generics\Base\RetrieveListCreateUpdateDeleteService;

    class StudentService extends RetrieveListCreateUpdateDeleteService
    {
        public string $table = 'students';
        public array $createColumns = [];
        public ?array $updateColumns = null;
    }

## RetrieveListDeleteService#

This generic service provides the `retrieve`, `list` and `delete` actions.

    use Pionia\Generics\Base\RetrieveListDeleteService;

    class StudentService extends RetrieveListDeleteService
    {
        public string $table = 'students';
    }

## RetrieveListRandomService#

This generic service provides the `retrieve`, `list` and `random` actions.

    use Pionia\Generics\Base\RetrieveListRandomService;

    class StudentService extends RetrieveListRandomService
    {
        public string $table = 'students';
    }

## RetrieveListUpdateDeleteService#

This generic service provides the `retrieve`, `list`, `update` and `delete` actions.

    use Pionia\Generics\Base\RetrieveListUpdateDeleteService;

    class StudentService extends RetrieveListUpdateDeleteService
    {
        public string $table = 'students';
        public ?array $updateColumns = null;
    }

## RetrieveListUpdateService#

This generic service provides the `retrieve`, `list` and `update` actions.

    use Pionia\Generics\Base\RetrieveListUpdateService;

    class StudentService extends RetrieveListUpdateService
    {
        public string $table = 'students';
        public ?array $updateColumns = null;
    }

## UniversalGenericService#

This generic service provides all the actions. It is the most generic of all the generic services.

    use Pionia\Generics\Base\UniversalGenericService;

    class StudentService extends UniversalGenericService
    {
        public string $table = 'students';
        public array $createColumns = [];
        public ?array $updateColumns = null;
        public ?array $listColumns = ['id', 'name'];
        public string $pk_field = 'id';
    }

All cases of `retrieve` can be replaced with `details` thus setting `ACCTON` in your request as `details` will still work the same as setting it to `retrieve`.

## Customization#

### $table#

This defines for us the table we are going to be interacting with. It is a required option.

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';
    }

All the other services will require you to define this. This is the table that the service will be interacting with.

### $pk_field#

This defines the primary key field of the table. By default it is `id`. But you can override it to be any other field.

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';
        public string $pk_field = 'student_id';
    }

### $createColumns#

This defines the columns that will be created when the `create` action is called. It is an array of strings.

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';
        public ?array $createColumns = ['name', 'age', 'gender'];
    }

Any other columns that are not defined in this array shall be ignored. This is requred for all create services.

### $updateColumns#

This defines the columns that will be updated when the `update` action is called. It is an array of strings.

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';
        public ?array $updateColumns = ['name', 'age', 'gender'];
    }

If this is not defined, then all the columns that are defined in the request will be updated. This param is optional.

### $listColumns#

This defines the columns that will be returned for all actions that return data back to the end user. It is an array of strings.

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';
        public ?array $listColumns = ['id', 'name'];
    }

The above implies that whether we are hitting `list`, `retrieve`, `random` or any other action that returns data, only the `id` and `name` columns will be returned. This is optional and defaults to `*` if not defined.

### $limit#

This defines the size of records to return. It is an integer. It will henceforth be replaced by the `LIMIT` or `limit` on the request if it is defined or in the `PAGINATION` or `pagination` key in the request.

By default, this is set to 10 records.

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';
        public int $limit = 10;
    }

### $offset#

This defines the offset where to start querying from. It is an integer. It will henceforth to be replaced by the `OFFSET` or `offset` on the request if it is defined or in the `PAGINATION` or `pagination` key in the request.

By default, this is set to 0 records.

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';
        public int $offset = 0;
    }

`limit` or `LIMIT` and `offset` or `OFFSET` must both be defined on the request or in the `PAGINATION` or `pagination` or in the `SEARCH` or `search` key in the request for pagination to kick in, otherwise, each will perform its respective duty without pagination.

## Overriding Actions#

You can override any of the actions provided by the generic services. This is done by defining the action in the service.

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';

        public function create(): BaseResponse
        {
            // Your custom logic here
            $data = $this->request->getData();

            // perform your own way of creating the record

            return BaseResponse::JsonResponse(0, "your message", $data);
        }
    }

The fact that Pionia is structured in an OOP way, you can override any of the actions provided by the generic services. This is just normal PHP OOP inheritance.

## Adding Custom Actions#

You can add your own custom actions to the generic services. This is done by defining the action in the service.

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';

        public function customAction(): BaseResponse
        {
            // Your custom logic here
            $data = $this->request->getData();

            // perform your own custom action

            return BaseResponse::JsonResponse(0, "your message", $data);
        }
    }

You can now call this action in your request by setting the `ACTION` key to `custom_action`.

    {
      "SERVICE": "student",
      "ACTION": "custom_action"
    }

The name of your custom action must not conflict with any of the actions provided by the generic services. Otherwise you stand a chance of overriding the generic action.

## Overriding getters#

You can also tell the service `how to query one` item and `how to list multiple` items. This is can be handy where for example you want to add complex where clauses to your queries.

For this cause, you can override the `getItem` and `getItems` methods in service.

    class StudentService extends RetrieveCreateUpdateService
    {
        public string $table = 'students';

        /**
         * Override this in your service to define the basis to return single item details
         * @return null|object
         */
        public function getItem(): ?object
        {
            return null;
        }

        /**
         * Override this in your service to define the basis to return multiple items from the database
         * @return null|object
         */
        public function getItems(): ?array
        {
            return null;
        }
    }

## Generic Services Signals(Hooks)#

Generic Services come baked with hooks that enable you to run custom code before or after the CRUD operations. The following are some of these hooks:

### preCreate#

This hook is called before the `create` action is executed.

        public function preCreate(?array $createItem = null): array|bool|null
        {
            if ($createItem) {
                $createItem['title'] = $createItem['title'] . " - " . date('Y-m-d H:i:s');
            }

            return $createItem;
        }

If this hook returns `null` or a false value, the `create` action will abort.

Also, this hook has access to the `createItem` that is about to be created. You can mutate data as you see fit at this point or add extra data.

On success, hooks must return the `createItem` that will be used to create the record.

### postCreate#

This hook is called after the `create` action is executed.

        public function postCreate(object|array|null $createdItem = null): object|array|null
        {
            $createdItem->created = true;
            return $createdItem;
        }

It gives you access to the currently create record. You can mutate the return data as you see fit, but this won’t affect the object that has already been saved. This is just for the response.

Whatever object or array you return from here is what shall be sent back to the user.

### preUpdate#

This hook is called before the `update` action is executed.

        public function preUpdate(?array $updateItem = null): array|bool|null
        {
            if ($updateItem) {
                $updateItem['title'] = $updateItem['title'] . " - " . date('Y-m-d H:i:s');
            }

            return $updateItem;
        }

If this hook returns `null` or a false value, the `update` action will abort.

Also, this hook has access to the `updateItem` that is about to be updated. You can mutate data as you see fit at this point or add extra data.

The object that is returned from this hook is what shall be used to update the record.

### postUpdate#

This hook is called after the `update` action is executed.

        public function postUpdate(object|array|null $updatedItem = null): object|array|null
        {
            $updatedItem->updated = true;
            return $updatedItem;
        }

It gives you access to the currently updated record. You can mutate the return data as you see fit, but this won’t affect the object that has already been saved. This is just for the response.

Whatever object or array you return from here is what shall be sent back to the user.

### preDelete#

This hook is called before the `delete` action is executed.

        public function preDelete(object|array|null $itemToDelete = null): array|null|object|bool
        {
            if ($itemToDelete->id > 10){
                return false;
            }
            return $itemToDelete;
        }

If this hook returns `null` or a false value, the `delete` action will abort. Otherwise, the `$itemToDelete` will be deleted.

The hook also gives you access to the item to be deleted. You can perform any conditions or any other logic you see fit.

### postDelete#

This hook is called after the `delete` action is executed.

        public function postDelete(PDOStatement $deleteInstance, object|array|null $deletedItem = null): mixed
        {
            return $deletedItem;
        }

However much the record is nolonger in the db, this hook gives you access to the record that was deleted. You can use it as you see fit. The object that is returned from this hook is what shall be sent back to the user.

### Why would you need these hooks?#

1. Hooks can be used to mutate data before it is saved to the database.
2. Hooks can be used to transform the response back to the user to meet your needs.
3. It is in these hooks where you have a chance to log, send emails or do any other action that is not directly related to the CRUD operation.

The hooks are not to be used outside services that extend the `GenericService` class. They are already included in the generic services.

## Securing Generic Services.#

The fact that Generic Services extend `BaseRestService` means that all authentication and authorization checks are still in place.

You can still use `$serviceRequiresAuth` to protect the entire service from unauthenticated access.

    public bool $serviceRequiresAuth = true;

You can also define a few actions that will require authentication like below.

    public ?array $actionsRequiringAuth = ['create', 'update', 'delete'];

You can also protect actions by permissions using the `$actionPermissions` property.

    public ?array $actionPermissions = [
        'create' => ['create_student'],
        'update' => ['update_student'],
    ];

This will ensure that only users with the listed permissions can access the action.

Please note that the permissions are represented as an `array` not a `string`.

## Conclusion#

The generic services are there to help you with the CRUD operations. They are there to help you focus on the complex business logic.

You can use the mixin to come up with your own custom generic services. You can also override the actions provided by the generic services.

This is all to turbo charge your development process which is the main goal of Pionia.