Setup

This page describes application bootstrap: HTTP and CLI entry points, paths.php, and service providers. If you have not installed the package yet — start with Installation (Concept Skeleton or composer require php-concept/core). Next — Configuration (config/, .env).

Core only: minimal structure

After composer require php-concept/core you assemble this root structure (paths can be changed in paths.php):

project/
├── public/index.php          # HTTP front controller
├── bin/console.php           # CLI (optional)
├── bootstrap/
│   ├── app.php
│   ├── paths.php
│   └── providers/
│       ├── app.php           # providers for web requests
│       └── console.php       # providers for CLI
├── config/                   # PHP config (required for ConfigServiceProvider)
├── routes/                   # if routes are extracted from config
├── storage/                  # logs, cache
└── composer.json

Alternative directory layouts — in Directory structure. In skeleton these files are ready; below is a breakdown of each element for your own project.

Overview

HTTP and CLI share paths.php and App::create(), but use different entry points and different provider lists. In skeleton, HTTP goes through bootstrap/app.php, while CLI builds bootstrap directly in bin/console.php (without app.php).

registerServiceProviders() only attaches provider classes to the container. Full loading of config, routes, and services happens lazy — on the first container access in $app->run() (HTTP) or $container->get(ConsoleApplication) (CLI).

HTTP

flowchart TB
    WS["Web server"]
    IDX["public/index.php"]
    AUTO["vendor/autoload.php"]
    APP_PHP["bootstrap/app.php"]
    PATHS["paths.php"]
    CREATE["App::create()"]
    EARLY["Early Whoops + PathManager"]
    REG["registerServiceProviders()"]
    PROV_HTTP["providers/app.php"]
    RUN["App::run()"]
    LAZY["Lazy provider boot"]
    CFG["Config: .env + config/"]
    HTTP_P["Http: PSR-7 Request, Router, routes/web.php"]
    COMP["Components: module routes, views"]
    DISP["Router::dispatch()"]
    MW["Middleware"]
    STRAT["RouteStrategy: interceptors, autowire"]
    CTRL["Controller → PSR-7 Response"]
    EMIT["SapiEmitter → client"]

    WS --> IDX --> AUTO --> APP_PHP
    APP_PHP --> PATHS --> CREATE --> EARLY
    APP_PHP --> REG --> PROV_HTTP
    IDX --> RUN
    RUN --> LAZY --> CFG --> HTTP_P --> COMP
    RUN --> DISP --> MW --> STRAT --> CTRL --> EMIT
  1. public/index.php — autoload and bootstrap/app.php.
  2. App::create() — League Container, ReflectionContainer, early Whoops, PathManager.
  3. registerServiceProviders — array of classes from bootstrap/providers/app.php.
  4. App::run() — resolves Router and ServerRequest from the container; providers load config, routes, Twig, session, etc.
  5. Router::dispatch() — middleware, RouteStrategy, controller; response is sent by SapiEmitter.

Detailed breakdown of middleware, route interceptors, and autowiring — Request lifecycle.

CLI

flowchart TB
    BIN["bin/console.php"]
    AUTO["vendor/autoload.php"]
    PATHS["paths.php"]
    CREATE["App::create()"]
    EARLY["Early Whoops + PathManager"]
    REG["registerServiceProviders()"]
    PROV_CLI["providers/console.php"]
    GET["container->get(ConsoleApplication)"]
    LAZY["Lazy provider boot"]
    CFG["Config: .env + config/"]
    CON["ConsoleServiceProvider: commands from config"]
    COMP["Components: module CLI commands"]
    RUN["ConsoleApplication::run()"]
    CMD["Symfony Command"]

    BIN --> AUTO --> PATHS --> CREATE --> EARLY
    BIN --> REG --> PROV_CLI
    BIN --> GET --> LAZY --> CFG --> CON --> COMP --> RUN --> CMD
  1. bin/console.php — autoload, paths.php, App::create() (like HTTP, but without bootstrap/app.php).
  2. providers/console.php — different provider set: no Twig/Validation HTTP stack, with ConsoleServiceProvider.
  3. get(ConsoleApplication) — lazy boot: config, core and component command registration.
  4. ConsoleApplication::run() — Symfony Console runs the command (db:migrate, route:list, etc.).

ComponentsServiceProvider in CLI does not register view paths/contexts — only commands, migrations, seeders, and additional module providers.

public/index.php

The single public entry point for the web server (Apache, Nginx, PHP built-in server). The virtual host document root must point to public/.

<?php declare(strict_types=1);

use Concept\Core\App;

require_once __DIR__ . '/../vendor/autoload.php';

/** @var App $app */
$app = require_once __DIR__ . '/../bootstrap/app.php';

$app->run();
  • autoload — Composer PSR-4 (vendor/autoload.php).
  • bootstrap/app.php — creates App, registers providers for HTTP.
  • run() — handles PSR-7 request, routing, response.

Static files also live in public/ (assets/, vendor/bootstrap after publish) — they are served directly by the web server, without PHP.

bootstrap/ directory

Application initialization code. paths.php is shared for HTTP and CLI; app.php is used only by the web entry point. CLI duplicates App::create() + providers directly in bin/console.php.

bootstrap/
├── app.php              # HTTP: App + app.php providers
├── paths.php            # map of logical paths → project directories
└── providers/
    ├── app.php          # providers for web requests
    └── console.php      # providers for console

bootstrap/app.php

<?php declare(strict_types=1);

use Concept\Core\App;

$rootPath = dirname(__DIR__);
/** @var array<string, string> $paths */
$paths = include $rootPath . '/bootstrap/paths.php';
$app = App::create($rootPath, $paths);

$providerFiles = [
    $rootPath . '/bootstrap/providers/app.php',
];
$app->registerServiceProviders($providerFiles);

return $app;
  • $rootPath — absolute path to project root (parent of bootstrap/).
  • paths.php — returns array for PathManager (see below).
  • App::create() — container, early error handler, PathManager registration.
  • registerServiceProviders() — each file in the list must return an array of provider classes.

bootstrap/providers/app.php

The file returns an array of core service provider classes. Each provider registers services in the container (router, config, Twig, DB, etc.). Order in the array matters — dependencies must come before dependent services.

Minimum for HTTP — config and routing (requires config/app.php, config/routes.php, and a routes file):

<?php declare(strict_types=1);

use Concept\Core\Providers\Bootstrap\ConfigServiceProvider;
use Concept\Core\Providers\Http\HttpServiceProvider;

return [
    ConfigServiceProvider::class,
    HttpServiceProvider::class,
];

For a real application, add error, session, log, view, validation, DB, and component providers — as in Concept Skeleton. To add your own provider — add the class to the array or a separate file in $providerFiles in app.php.

bootstrap/providers/console.php

Reduced set for CLI: no Twig/Validation HTTP stack, with ConsoleServiceProvider. Used with bin/console.php. Example from skeleton:

<?php declare(strict_types=1);

use Concept\Core\Providers\Component\ComponentsServiceProvider;
use Concept\Core\Providers\Bootstrap\ConfigServiceProvider;
use Concept\Core\Providers\Console\ConsoleServiceProvider;
use Concept\Core\Providers\Database\DatabaseServiceProvider;
use Concept\Core\Providers\Bootstrap\ErrorHandlerServiceProvider;
use Concept\Core\Providers\Http\HttpServiceProvider;
use Concept\Core\Providers\Logging\LogServiceProvider;
use Concept\Core\Providers\Http\SessionServiceProvider;

return [
    ConfigServiceProvider::class,
    ErrorHandlerServiceProvider::class,
    HttpServiceProvider::class,
    SessionServiceProvider::class,
    LogServiceProvider::class,
    DatabaseServiceProvider::class,
    ConsoleServiceProvider::class,
    ComponentsServiceProvider::class,
];

bootstrap/paths.php

The central place where you define the physical project structure. The file returns an associative array: logical keyrelative path from project root. The core accesses directories only through PathManager::get($key), not hard-coded strings.

You can define folder structure as you prefer: rename directories, split code in a monorepo, or move logs to var/log — just change values in paths.php, as long as keys remain PathName constants.

Example from skeleton (full set of keys):

<?php

use Concept\Core\Foundation\PathName;

return [
    PathName::BOOTSTRAP => 'bootstrap',
    PathName::SRC => 'src',
    PathName::CONFIG => 'config',
    PathName::MIGRATIONS => 'database/migrations',
    PathName::SEEDERS => 'database/seeders',
    PathName::PUBLIC => 'public',
    PathName::STORAGE => 'storage',
    PathName::LOGS => 'storage/logs',
    PathName::CACHE => 'storage/cache',
    PathName::RESOURCES => 'resources',
    PathName::LANG => 'resources/lang',
    PathName::VALIDATOR_TRANSLATIONS => 'resources/lang/validator',
    PathName::VIEWS => 'resources/views',
    PathName::ERRORS_FALLBACK_VIEWS => 'resources/views/errors/fallback',
];

PathName keys

Constant (array key) Path in skeleton Used by
BOOTSTRAPbootstrapReserved for bootstrap resources
SRCsrcApplication code (PSR-4)
CONFIGconfigPHP config, config/{env}/ overrides
MIGRATIONSdatabase/migrationsDB migrations
SEEDERSdatabase/seedersSeeders
PUBLICpublicDocument root, static files
STORAGEstorageWritable application data
LOGSstorage/logsMonolog, debug log
CACHEstorage/cacheTwig, Valinor cache, etc.
RESOURCESresourcesResources, not only views
LANGresources/langLanguage files
VALIDATOR_TRANSLATIONSresources/lang/validatorValidation rule translations
VIEWSresources/viewsTwig / Plates templates
ERRORS_FALLBACK_VIEWSresources/views/errors/fallbackFallback error templates (production)

PathManager API

use Concept\Core\Foundation\PathName;

// Absolute path: /var/www/my-app/config
$pathManager->get(PathName::CONFIG);

// With subpath: /var/www/my-app/storage/cache/views
$pathManager->get(PathName::CACHE, 'views');

// Arbitrary path from project root
$pathManager->root('custom/dir');

The second argument to get() is a subdirectory inside the registered path (for example view.cache_dir in config is appended to PathName::CACHE).

Custom directory structure

Example: logs and cache in var/, code in app/:

use Concept\Core\Foundation\PathName;

return [
    PathName::SRC => 'app',
    PathName::CONFIG => 'etc/config',
    PathName::LOGS => 'var/log',
    PathName::CACHE => 'var/cache',
    PathName::VIEWS => 'app/Resources/views',
    // ... other keys as needed
];

After changing paths:

  • update composer.jsonautoload.psr-4 if you changed PathName::SRC;
  • ensure the web server points to the current PathName::PUBLIC;
  • align paths in config/migrations.php and view.php with the new structure.

Adding new keys to PathName in core is needed only if a new path type will be used by core itself; for your own code, $pathManager->root('my/custom/path') is enough.

bin/console.php

CLI entry point (migrations, seeders, route:list, view:clear).

<?php declare(strict_types=1);

use Concept\Core\App;
use Symfony\Component\Console\Application as ConsoleApplication;

require __DIR__ . '/../vendor/autoload.php';

$rootPath = dirname(__DIR__);
$paths = include $rootPath . '/bootstrap/paths.php';
$app = App::create($rootPath, $paths);

$providerFiles = [
    $rootPath . '/bootstrap/providers/console.php',
];
$app->registerServiceProviders($providerFiles);

$container = $app->getContainer();
$console = $container->get(ConsoleApplication::class);

$console->run();

Run from project root (as in composer.json → script console):

php bin/console.php

Differences from HTTP:

  • same paths.php and App::create();
  • different provider list — bootstrap/providers/console.php;
  • instead of $app->run() — Symfony Console Application from the container.

New project checklist

  1. Install the package — Concept Skeleton or core only.
  2. Create structure with public/index.php, bootstrap/, and optionally bin/console.php.
  3. Configure bootstrap/paths.php for your directories.
  4. Register providers in bootstrap/providers/app.php and console.php.
  5. Point the web server at public/, create .env.
  6. Proceed to Configurationconfig/ and environment variables.