# Search

Search

Loading search index…

No recent searches

No results for "Query here"

# Services

This section assumes that you have a basic understanding of the Pionia framework. If you are new to Pionia, you can start with the tutorial.

# Introduction

Services in Pionia Framework are central holders of business logic. This is where most of the work happens. Pionia has tried to reduce your work from other areas so that you mainly focus on this essential area. Services are in actual PHP code, just php classes that extend the `BaseRestService`. As you might already know, a class can have multiple methods. In Pionia we call these `Actions`. Therefore, henceforth, the terms `service` and `actions` will be used for the same meaning throughout the same guide.

## Creating a service#

You can create a service using our pionia console or manually. All services, as a convention, must be located in the `services` folder.

We recommend to name your services after your database tables. Example, if your table is called ‘users’, you can name your service ‘UserService’.

If you are using our ‘pionia console’, then you can just name your service ‘user’. These are just conventions!

Remember generic services target a base table.

Therefore, you shall be asked the table you want to target. This is required.

However, starting from version 1.1.7, you can target relationships too!

You can read more about this in the Generic Services Section.

## Service Registration#

Creating a service is not enough in Pionia. You also need to register it in our switcher to make it discoverable by the kernel. Service registration happens in the associated switch.

In the switches folder, find the switch you want to use for this service. You can add your service as below.

     public function registerServices(): array
        {
            return [
                'user' => new UserService(),
                "todo" => TodoService::class, // this is okay
                'auth' => new AuthService(), // and this too
            ];
        }

The `key` of this method is the name you shall use in your proceeding requests to access this service. Therefore, it must be unique!

A single service can be registered in multiple switches. This is useful when you want to use the same service in different api versions.

## Targeting a service in the request#

In the request, you can target a service by determining the `SERVICE` key with your service name as the `key` defined in the `registerServices` method.

    {
        SERVICE: 'user'
        // rest of your request data.
    }

## Targeting a service action#

To target an action in a certain service, you need to define both the service and action as below.

    {
        SERVICE: "user",
        ACTION: "loginAuth"
        // rest of your service data
    }

The action in every request should match the name of your method in your service action. Pionia uses auto-discovery to automatically call the method passing in every required data needed for the request.

The keys `SERVICE` and `ACTION` are reserved and should not be used for any other purpose in the request data.

The same keys are case-sensitive and should be in uppercase.

## Request Data and Response#

### Accessing the Request object in the services#

You can access the request object in your service by calling the `$this->request` method on the service object. This returns the `Pionia\Request\Request` object. You can use this to access anything on the request.

    class TodoService extends BaseRestService
    {
        // your other actions here

        protected functon getTodo($data): BaseResponse
        {
            $request = $this->request;

            $uri = $this->request->getUri();

            // rest of actions logic

        }
    }

### Request Data#

#### JSON and Form Request Data#

An action takes `$data` as the first parameter which is an array of the request data. You can access you post data from this parameter.

This consists of both JSON and form data. Therefore, you can access your data as below.

        $username = $data["username"];
        $email = $data["email"];

You can also access your request data from the `$this->request` object.

        $allData = $this->request->getData();

You can also access the request method, headers, and other request data from the `$this->request` object.

        $method = $this->request->getMethod();
        $headers = $this->request->getHeaders();

#### Multipart Data(Uploads)#

If your action expects multipart upload files, then you can get these from the second action parameter called `$files`. This is an associative array of all uploads.

    protected function profileUpdates(array $data, ?array $files)
    {
            $profilePic = $files['profile_pik'];
    }

> NOTE: This does not consist of base64 encoded uploads, for those, they’ll be part of the `$data`.

### Action Response.#

All actions must return a `Pionia\Response\BaseResponse` object. This is the object that is sent back to the client. You can use the `BaseResponse` object to send back a response with a status code, message, and data.

A helper method `BaseResponse::JsonResponse` is provided to help you create a response object that is ready to be serialized to JSON.

    use Pionia\Response\BaseResponse;

    class TodoService extends BaseRestService
    {
        // your other actions here

        protected functon getTodo($data): BaseResponse
        {
            $this->mustAuthenticate();

            // rest of actions logic

            return BaseResponse::JsonResponse(200, 'Todo fetched successfully', $todo);
        }
    }

For details about request and responses, you can check the request and response section.

## Action protection#

You can protect your actions by determining that they require only authenticated requests(users) to be accessed. You can do this in three ways.

### Globally in the service#

#### Entire service#

You can mark an entire service as requiring authentication by setting the `$serviceRequiresAuth` parameter to `true`.

    class TodoService extends BaseRestService
    {
        public bool $serviceRequiresAuth = true; // all actions in this service require authentication.

        // your other actions here
    }

#### Specific actions#

You can also mark specific actions in a service as requiring authentication. Use the `$actionsRequiringAuth` parameter and add action names of actions that should be reached by authenticated users only.

This, unlike `$serviceRequiresAuth`, will only protect the actions listed in the array not the entire service.

    class TodoService extends BaseRestService
    {
        public bool $actionsRequiringAuth = ['getTodo'];

        // your other actions here
    }

### Internally in the action#

You can also call the `mustAuthenticate` method anywhere in your action, preferably the first line in the action method.

    class TodoService extends BaseRestService
    {
        // your other actions here

        protected functon getTodo($data): BaseResponse
        {
            $this->mustAuthenticate(); // only authenticated will exceed this point.

            // rest of actions logic

        }
    }

Details of how Pionia achieves authentication and authorization can be found in the authentication section.

## Error Handling#

According to Moonlight architecture, all requests should return a 200 Ok status code. This is because the client should be able to know if the request was successful or not by checking the `returnCode` in the response body.

All normal responses set this internally and are always returning a 200 status code. By convention and by default, all requests that are successful return 0 as the `returnCode`. This implies that the server can define multiple other return codes for other scenarios.

In Pionia, we have a global exception handler that catches all exceptions thrown anywhere in the code. This is to ensure that the client always gets the same response format.

All exceptions thrown are caught will raise a 500 status code and the message of the exception will be sent back to the client as the `returnMessage`.

Therefore, in your services and actions, you can throw exceptions as you see fit. And you don’t need to catch them at all!

    protected function getTodo($data): BaseResponse
    {
        $this->mustAuthenticate();

        if($data['id'] == null){
            throw new \Exception('Todo id is required'); // will be caught globally!
        }

        // rest of actions logic

        return BaseResponse::JsonResponse(200, 'Todo fetched successfully', $todo);
    }

Please note that all exceptions are caught globally and sent back to the client. Therefore, you do not need to catch exceptions in your services. Developers need to set clean, descriptive exception messages in their exceptions to help the client understand what went wrong.

## Deactivating actions in a service#

BaseRestService provides a parameter `$deactivatedActions` that can be used to register all deactivated actions in a service. This is useful when you want to deactivate an action in a service without deleting it.

    class TodoService extends BaseRestService
    {
        public array $deactivatedActions = ['getTodo']; // one or more actions to deactivate.

    }

Deactivated actions will not be called by the switcher. Therefore, they will not be accessible by the client.