Services


Who this is for

You scaffolded DeskFlow and need to create and register service classes — the PHP home for task, member, and project business logic.

What you will learn

  • How make:service scaffolds TaskService under services/
  • Registering aliases on MainSwitch so JSON "service": "task" resolves
  • Choosing Basic vs Generic services for Northwind tables

Before you start

Before you start

How it works

flowchart LR JSON["service: task"] --> Switch[MainSwitch] Switch --> Class[TaskService] Class --> Actions[listAction / createAction]

What is a service?

Services are plain PHP classes under services/ that extend Pionia\Http\Services\Service. Each action is a public method named somethingAction() — Pionia maps "action": "list" to listAction().

In DeskFlow, Northwind Studio uses three services:

Registered aliasClassRole
taskTaskServiceTasks for client projects
memberMemberServiceLogin and profiles
projectProjectServiceGroup tasks by client

Clients POST lowercase keys:

{ "service": "task", "action": "list", "project_id": 1 }

Create a service

Generate a scaffold from your app root:

php pionia make:service task

Choose Basic for hand-written actions, or Generic for CRUD over a Porm table — see Generic services.

The CLI creates services/TaskService.php:

namespace Application\Services;

use Pionia\Http\Services\Service;
use Pionia\Collections\Arrayable;

class TaskService extends Service
{
    public function listAction(Arrayable $data)
    {
        return response(0, 'OK', ['tasks' => []]);
    }
}

Register the alias on your switch (usually Application\Switches\MainSwitch):

protected function registerServices(): array
{
    return [
        'task' => TaskService::class,
    ];
}
Try it yourself
curl -s -X POST http://127.0.0.1:8000/api/v1/ \
  -H "Content-Type: application/json" \
  -d '{"service":"task","action":"list"}'

Expected: HTTP 200 with "returnCode": 0 and a tasks array in returnData.


When you run make:service, the CLI offers two paths:

Remember generic services target a base table.

Therefore, you shall be asked the database table name 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

Register services in your switch — usually Application\Switches\MainSwitch:

protected function registerServices(): array
{
    return [
        'task' => TaskService::class,
        'member' => MemberService::class,
        'project' => ProjectService::class,
    ];
}

The array keys are the service names in JSON requests. They must be unique within a switch.

Register the same service class in v1 and v2 switches when Northwind ships a breaking API version — see API versioning.

Targeting a service in the request

In the request, target a service with the lowercase service key:

{
  "service": "task",
  "action": "list"
}

For envelopes and HTTP status codes, see Requests and responses.

Actions

You can read more about actions in the actions section.

Service Security

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


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

    // your other actions here
}

If the flag is set to true, all actions in the service will require authentication. This means that only authenticated users will be able to access the service.

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 Service
{
    public bool $actionsRequiringAuth = ['getTodo'];

    // your other actions here
}

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.

Uncaught throwables flow through the exception pipeline. Status codes depend on the exception type:

ExceptionHTTP status
ValidationException422
ResourceNotFoundException404
HttpExceptionAs defined on the exception
Other500 (message hidden in production)

Throw ValidationException for client input errors; use plain Exception only for unexpected server faults. Prefer rules() or #[Validated] on actions — see Validations.

use Pionia\Collections\Arrayable;
use Pionia\Http\Response\ApiResponse;
use Pionia\Validations\Attributes\Validated;

#[Validated(rules: ['id' => 'required|integer'])]
protected function getTodoAction(Arrayable $data): ApiResponse
{
    $this->mustAuthenticate();

    $id = $data->get('id');

    // rest of action logic

    return response(0, 'Todo fetched successfully', $todo);
}

Uncaught throwables flow through the exception pipeline — use clear exception messages for clients.

Common mistakes

  • Wrong service alias — the JSON key must match registerServices() exactly (task, not TaskService).
  • Forgetting to register after make:service — the CLI creates the class but does not edit MainSwitch for you.
  • Using $serviceRequiresAuth without JWT configured — set up member.login first; see Authentication.
  • Expecting every error to be HTTP 200 — validation uses 422, auth failures 401; see Requests & responses.

What’s next

Actions

Request data, responses, and auth helpers.

Validation

422 when Alex omits task title.

Generic services

CRUD for project rows.