Skip to content

Application Cookbook

Use these recipes alongside the complete Project tutorial. Each example states the application code or configuration it requires.

A JSON Health Endpoint

Create src/Modules/Api/Controllers/HealthController.php. A non-model endpoint can extend Rest directly:

<?php
namespace App\Modules\Api\Controllers;

class HealthController extends \PhalconKit\Mvc\Controller\Rest
{
    public function indexAction(): \Phalcon\Http\ResponseInterface
    {
        $this->setRestViewVar('healthy', true);
        return $this->setRestResponse(true);
    }
}

Grant HealthController::class => ['index'] under the intended role, then call:

curl http://127.0.0.1:8080/api/health

Expected HTTP 200 with response: true and view.healthy: true. This proves application dispatch. Add separate private readiness checks for dependencies; do not expose database details, secrets, or full runtime configuration publicly.

A Shared Application Service

For a service you implement at src/Service/ReportExporter.php, register its constructor dependencies in src/Provider/Report/ServiceProvider.php:

<?php
namespace App\Provider\Report;

use App\Service\ReportExporter;
use PhalconKit\Di\DiInterface;
use PhalconKit\Provider\AbstractServiceProvider;

class ServiceProvider extends AbstractServiceProvider
{
    protected string $serviceName = 'reportExporter';

    public function register(DiInterface $di): void
    {
        $di->setShared($this->getName(), static fn () => new ReportExporter(
            $di->getTyped('db', \Phalcon\Contracts\Db\Adapter\Adapter::class)
        ));
    }
}

The example assumes your ReportExporter constructor accepts that database contract; it is an application class, not shipped by Core. Register the provider in src/Config.php:

'providers' => [
    \App\Provider\Report\ServiceProvider::class =>
        \App\Provider\Report\ServiceProvider::class,
],

Resolve it with $this->di->getShared('reportExporter') in a controller/task. Use ordinary constructor injection inside services.

A Project State Transition

Add this business method to the tutorial's concrete App\Models\Project:

/** Activate a draft project once its budget has been assigned. */
public function activate(): void
{
    if ($this->getStatus() !== 'draft' || (int)$this->getBudget() <= 0) {
        throw new \DomainException('Only a funded draft project can be activated.');
    }
    $this->setStatus('active');
}

Call it from a controller action after resolving the record through the controller's authorized query:

public function activateAction(): \Phalcon\Http\ResponseInterface
{
    $project = $this->findFirst();
    if (!$project instanceof \App\Models\Project) {
        return $this->setRestErrorResponse(404);
    }
    try {
        $project->activate();
    } catch (\DomainException $exception) {
        $this->setRestViewVar('messages', [$exception->getMessage()]);
        return $this->setRestErrorResponse(422, response: false);
    }
    if (!$project->save()) {
        $this->setRestViewVar('messages', $project->getMessages());
        return $this->setRestErrorResponse(422, response: false);
    }
    $this->setRestViewVar('data', $this->expose($project));
    return $this->setRestResponse(true);
}

Grant activate, allow POST in the controller's method map, and grant model find/update for the write role. Request:

curl http://127.0.0.1:8080/api/project/activate \
  -b cookies.txt -c cookies.txt \
  -H "X-Authorization: Bearer $API_TOKEN" \
  -H 'Content-Type: application/json' --data '{"id":2}'

On the original fixture, project 2 changes from draft to active and returns HTTP 200 with its exposed data. Repeating the transition returns 422. A missing or hidden record returns 404. For concurrent transitions, add locking or an application version check inside a service transaction.

A Stable Custom Representation

For an application-defined transformer, call it explicitly in the action:

$data = [
    'id' => $project->getId(),
    'label' => $project->getLabel(),
    'status' => $project->getStatus(),
    'links' => ['self' => '/api/project/find-first?id=' . $project->getId()],
];
$this->setRestViewVar('data', $data);
return $this->setRestResponse(true);

A transformer class is not automatically discovered just because it exists. Use controller exposure for simple field selection; use an explicit transformer when a client contract needs derived fields or a different structure.

Common Application Workflows

Build Recipe
Paginated search screen Filters, search, stable order, totals
Detail screen with child rows Controlled eager graph
Parent/child edit form Nested writes and ownership
Facet menu and dashboard totals Distinct and aggregates
CSV download Export format, columns, and limits
Account provisioning Existing CLI user commands
Scheduled notification job CLI task calling a service
Live project notifications WebSocket application protocol

Test the final application behavior with Application Testing.