Skip to content

Configuration

Phalcon Kit applications are configured through app-owned config classes. Keep secrets in environment files and keep application structure in code:

  • modules
  • providers
  • router defaults
  • model aliases
  • permissions and roles
  • locale defaults
  • service integrations

Think of configuration in three layers:

Layer Keep here Example
Package defaults Reusable framework behavior default providers and router targets
App config Reviewable application structure and policy modules, aliases, permissions
Environment Machine-specific values and secrets database host, credentials, feature toggles

App config should merge with package defaults intentionally. Environment values should provide data to config, not replace the config structure.

Official Phalcon references:

  • Config: https://docs.phalcon.io/latest/config/
  • Dependency injection: https://docs.phalcon.io/latest/di/
  • Routing: https://docs.phalcon.io/latest/routing/

Environment

Example .env values:

APP_NAME="My App"

DATABASE_HOST=127.0.0.1
DATABASE_DBNAME=app
DATABASE_USERNAME=app
DATABASE_PASSWORD=app

Keep secrets and machine-specific paths in environment files. Keep module registration, providers, aliases, and policy in config classes so they can be reviewed and versioned.

MySQL And MariaDB

The default mysql driver uses MySQL connection settings, including a MySQL collation and block_encryption_mode. MariaDB needs its own compatible options. Add this independent driver inside your App config's defaults (before the parent constructor), using PhalconKit\Support\Env:

'database' => [
    'default' => 'mariadb',
    'drivers' => [
        'mariadb' => [
            'adapter' => \PhalconKit\Db\Adapter\Pdo\Mysql::class,
            'dialectClass' => \PhalconKit\Db\Dialect\Mysql::class,
            'host' => Env::get('DATABASE_HOST', '127.0.0.1'),
            'port' => (int)Env::get('DATABASE_PORT', 3306),
            'dbname' => Env::get('DATABASE_DBNAME', ''),
            'username' => Env::get('DATABASE_USERNAME', ''),
            'password' => Env::get('DATABASE_PASSWORD', ''),
            'charset' => 'utf8mb4',
            'options' => [
                \PDO\Mysql::ATTR_INIT_COMMAND =>
                    "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci, sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'",
                \PDO::ATTR_EMULATE_PREPARES => false,
                \PDO::ATTR_STRINGIFY_FETCHES => false,
            ],
        ],
    ],
],

Use a new driver name so MySQL-only options are not inherited or appended during config merging. devtools.php uses the same selected driver for migrations. For a local Unix socket, provide unix_socket in the driver descriptor instead of host/port. Configure TLS options for remote database connections according to your database deployment.

Common Environment Settings

Setting Use
APP_ENV / APP_DEBUG Environment label and debug output; keep debug false in shared environments
APP_CACHE Application cache toggle; select appropriate backing services separately
APP_TIMEZONE Application timestamps/timezone
DATABASE_* Connection credentials and database name
SECURITY_JWT_PASSPHRASE Private JWT signing key
IDENTITY_AUTHORIZATION_HEADER Bearer header; defaults to X-Authorization
IDENTITY_STATELESS Explicit choice of stateless identity semantics
SESSION_COOKIE_SECURE HTTPS cookie flag; disable only for local HTTP testing
RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN Exact allowed frontend origins
SWOOLE_HOST / SWOOLE_PORT Optional worker listener; loopback behind a reverse proxy

See Authentication for token/cookie examples and Application Security for deployment settings.

App Config

<?php

namespace App;

use PhalconKit\Support\Env;

final class Config extends \PhalconKit\Bootstrap\Config
{
    public function __construct(array $data = [], bool $insensitive = false)
    {
        $data = $this->internalMergeAppend([
            'app' => [
                'name' => Env::get('APP_NAME', 'My App'),
            ],
            'modules' => [
                \PhalconKit\Mvc\Module::NAME_API => [
                    'className' => \App\Modules\Api\Module::class,
                    'path' => APP_PATH . 'Modules/Api/Module.php',
                ],
            ],
        ], $data);

        parent::__construct($data, $insensitive);
    }
}

Use internalMergeAppend() when the app wants to keep Phalcon Kit defaults and append or override only the app-owned parts.

Modules

Modules define runtime boundaries. Common module names are:

  • frontend
  • admin
  • api
  • oauth2
  • cli
  • ws

Register app modules explicitly:

'modules' => [
    \PhalconKit\Mvc\Module::NAME_API => [
        'className' => \App\Modules\Api\Module::class,
        'path' => APP_PATH . 'Modules/Api/Module.php',
    ],
],

HTTP Exception Route

Expected MVC request failures raised as PhalconKit\Exception\HttpException use the dedicated router.httpException target. Its defaults keep the current module context and dispatch to errorAction():

'router' => [
    'httpException' => [
        'namespace' => Env::get('ROUTER_HTTP_EXCEPTION_NAMESPACE'),
        'module' => Env::get('ROUTER_HTTP_EXCEPTION_MODULE'),
        'controller' => Env::get('ROUTER_HTTP_EXCEPTION_CONTROLLER', 'error'),
        'action' => Env::get('ROUTER_HTTP_EXCEPTION_ACTION', 'error'),
    ],
],

Applications may override this route and controller to adapt their response envelope. The dispatcher still owns status validation: only HttpException codes from 400 through 599 are preserved, and invalid codes become HTTP 500. Generic exceptions never derive transport status from their numeric exception code; production failures use router.fatal, while debug mode rethrows them.

Provider Overrides

Provider overrides are config-first. Replace a core provider by keeping the core provider class as the key. Register new app services with the app provider as both key and value.

'providers' => [
    \PhalconKit\Provider\Identity\ServiceProvider::class =>
        \App\Provider\Identity\ServiceProvider::class,

    \App\Provider\Firebase\ServiceProvider::class =>
        \App\Provider\Firebase\ServiceProvider::class,
],

Common provider categories include database, cache, session, identity, ACL, router, request/response, logger/loggers, mailer, Redis, Swoole, OpenAI, OAuth, filesystem, translation, view, Volt, URL, and helpers.

Use app providers when a service needs app configuration, app credentials, or a different implementation. Avoid replacing a core provider just to change one runtime option when config already supports it.

Storage-backed cache adapters can restrict PHP object deserialization with cache.default.allowedClasses. PhalconKit defaults this to true for compatibility with model and application object caches. Set CACHE_ALLOWED_CLASSES=false when the cache stores only scalars and arrays, or set an explicit class-name list in application config when cached objects are required. Clear incompatible cache entries when tightening the policy.

Event Listeners

Use eventsManager.listeners for app-owned listeners that should attach to the shared Phalcon events manager during bootstrap:

'eventsManager' => [
    'listeners' => [
        'dispatch' => [
            [
                'class' => \App\Listeners\SecurityHeaders::class,
                'priority' => 200,
            ],
            [
                'service' => 'auditDispatchListener',
                'priority' => 100,
            ],
        ],
        'db' => [
            \App\Listeners\QueryCorrelation::class,
        ],
    ],
],

Listeners are grouped by Phalcon event type, such as dispatch, db, model, or view. A listener can be a class name, a DI service name, or an array with class or service. Array definitions also support priority, arguments, and enabled => false.

The bootstrap attaches these listeners after providers are registered and before modules/router setup. Core providers keep their existing built-in listener wiring; this config is for application listeners that should participate in the same shared event manager without replacing providers.

Stateless Identity

API-only applications can keep Phalcon Kit's normal session service available while making identity itself stateless:

'identity' => [
    'stateless' => true,
],

or through the environment:

IDENTITY_STATELESS=true

Stateless identity stores the small identity payload, such as userId and asUserId, directly in the JWT claim instead of PHP session storage. The session provider still registers and starts the configured Phalcon session manager, so flash messages, OAuth2 state, locale persistence, and other session consumers keep their normal behavior.

Do not combine stateless identity with identity.sessionFallback; fallback storage is ignored when identity.stateless is enabled. Clients must replace their JWT after login, logout, OAuth2 login, impersonation, and refresh responses that include new token values. Old JWTs are not server-revoked unless the application adds its own revocation strategy.

App Service Provider Example

Use a provider when the service belongs in the DI container and is shared by controllers, tasks, models, or other services.

<?php

namespace App\Provider\Report;

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

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

    #[\Override]
    public function register(DiInterface $di): void
    {
        $di->setShared($this->getName(), function () use ($di) {
            return new ReportExporter(
                $di->getTyped('db', \Phalcon\Contracts\Db\Adapter\Adapter::class),
                $di->getTyped('logger', \Phalcon\Contracts\Logger\Logger::class)
            );
        });
    }
}

Phalcon Kit DI Boundary

Applications that use PhalconKit\Bootstrap normally do not need to change anything: bootstrap creates a Phalcon Kit DI container before registering config, providers, modules, and services.

Custom bootstraps and tests that pass their own container into Bootstrap::setDI() must pass PhalconKit\Di\DiInterface, such as PhalconKit\Di\Di, PhalconKit\Di\FactoryDefault, or PhalconKit\Di\FactoryDefault\Cli. Native Phalcon\Di\Di is no longer the provider/bootstrap boundary because it does not expose Phalcon Kit's typed helpers.

Use the typed helpers when the service contract is known:

$config = $di->getConfig();
$view = $di->getTyped('view', \Phalcon\Mvc\ViewInterface::class);

Native Phalcon DI signatures may still appear where Phalcon Kit extends native Phalcon interfaces or classes. App-owned providers should use PhalconKit\Di\DiInterface.

Register it in app config:

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

Then use it from an injectable class:

$this->reportExporter->exportProject($projectId);

Model Aliases

Applications can map framework model roles to app model classes. This keeps identity, permissions, and scaffolded resources decoupled from a fixed model namespace.

'models' => [
    'user' => \App\Models\User::class,
    'role' => \App\Models\Role::class,
    'workspace' => \App\Models\Workspace::class,
],

Permissions

Permission config maps features to components, component methods, optional query behaviors, and roles.

'permissions' => [
    'features' => [
        'manageLocation' => [
            'components' => [
                \App\Modules\Api\Controllers\LocationController::class => ['*'],
                \App\Models\Location::class => ['*'],
            ],
        ],
    ],
    'roles' => [
        'admin' => [
            'features' => ['manageLocation'],
        ],
    ],
],

The identity/security system can enforce permissions across controllers, actions, models, methods, CLI tasks, and WebSocket tasks.

Controller/action attributes are enabled by default and are merged into the same permission graph at runtime. Disable attribute scanning for config-only applications that want to avoid controller reflection. The default bootstrap config reads ACL_ATTRIBUTES:

'acl' => [
    'attributes' => Env::get('ACL_ATTRIBUTES', true),
],

For row-level controller conditions and role inheritance, read Identity And Permissions.

Validate Configuration Changes

Configuration failures should surface during bootstrap, before a request reaches business code. After changing modules, providers, aliases, or permissions:

composer validate --strict --no-check-publish
composer phpunit
php -S 127.0.0.1:8000 -t public public/index.php

Then exercise one route that resolves each changed service or policy. For a new provider, add a focused test that builds the container and asserts the service implements its intended contract.

Continue with Developer Cookbook for provider and endpoint recipes, or Troubleshooting when bootstrap cannot resolve the expected configuration.