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
- public/index.php — autoload and
bootstrap/app.php. - App::create() — League Container,
ReflectionContainer, early Whoops,PathManager. - registerServiceProviders — array of classes from
bootstrap/providers/app.php. - App::run() — resolves
RouterandServerRequestfrom the container; providers load config, routes, Twig, session, etc. - Router::dispatch() — middleware,
RouteStrategy, controller; response is sent bySapiEmitter.
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
- bin/console.php — autoload,
paths.php,App::create()(like HTTP, but withoutbootstrap/app.php). - providers/console.php — different provider set: no Twig/Validation HTTP stack, with
ConsoleServiceProvider. - get(ConsoleApplication) — lazy boot: config, core and component command registration.
- 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 ofbootstrap/).paths.php— returns array forPathManager(see below).App::create()— container, early error handler,PathManagerregistration.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 key → relative 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 |
|---|---|---|
BOOTSTRAP | bootstrap | Reserved for bootstrap resources |
SRC | src | Application code (PSR-4) |
CONFIG | config | PHP config, config/{env}/ overrides |
MIGRATIONS | database/migrations | DB migrations |
SEEDERS | database/seeders | Seeders |
PUBLIC | public | Document root, static files |
STORAGE | storage | Writable application data |
LOGS | storage/logs | Monolog, debug log |
CACHE | storage/cache | Twig, Valinor cache, etc. |
RESOURCES | resources | Resources, not only views |
LANG | resources/lang | Language files |
VALIDATOR_TRANSLATIONS | resources/lang/validator | Validation rule translations |
VIEWS | resources/views | Twig / Plates templates |
ERRORS_FALLBACK_VIEWS | resources/views/errors/fallback | Fallback 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.json→autoload.psr-4if you changedPathName::SRC; - ensure the web server points to the current
PathName::PUBLIC; - align paths in
config/migrations.phpandview.phpwith 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.phpandApp::create(); - different provider list —
bootstrap/providers/console.php; - instead of
$app->run()— Symfony ConsoleApplicationfrom the container.
New project checklist
- Install the package — Concept Skeleton or core only.
- Create structure with
public/index.php,bootstrap/, and optionallybin/console.php. - Configure
bootstrap/paths.phpfor your directories. - Register providers in
bootstrap/providers/app.phpandconsole.php. - Point the web server at
public/, create.env. - Proceed to Configuration —
config/and environment variables.