A flexible application bootstrap for Phalcon-based projects
This library provides flexible application bootstrapping, encapsulating module registration (or handler registration for micro applications), event management and middleware logic assignment for mvc, micro and cli-based applications. A simple factory instantiates the dependency injection container, encapsulating the registration of service dependency definitions defined in the configuration setttings.
- PHP >=8.0
- Phalcon >=5.0.0
See composer.json for more details
Open a terminal window and run:
composer require headio/phalcon-bootstrap
First create a config definition file inside your Phalcon project. This file should include the configuration settings, service & middleware definitions and a path to your handlers.
To get started, let's assume the following project structure:
├── public
│ ├── index.php
├── src
│ ├── Config
│ │ │── Config.php
│ │ │── Handlers.php
│ │── Controller
│ │── Domain
│ │── Middleware
│ │── Service
│ │── Var
│ │ │── Log
├── tests
├── vendor
├── Boot.php
├── codeception.yml
├── composer.json
├── .gitignore
├── README.md
└── .travis.yml
and your PSR-4 autoload declaration is:
{
"autoload": {
"psr-4": {
"Foo\\": "src/"
}
}
}
Create a config file Config.php inside the Config directory and copy-&-paste the following definition:
<?php
namespace Foo\Config;
use Foo\Middleware\NotFoundMiddleware;
return [
'applicationPath' => dirname(__DIR__) . DIRECTORY_SEPARATOR,
'debug' => true,
'locale' => 'en_GB',
'logPath' => dirname(__DIR__) .
DIRECTORY_SEPARATOR . 'Var' .
DIRECTORY_SEPARATOR . 'Log' .
DIRECTORY_SEPARATOR,
'handlerPath' => __DIR__ . DIRECTORY_SEPARATOR . 'Handlers.php',
'middleware' => [
NotFoundMiddleware::class => 'before'
],
'services' => [
'Foo\Service\EventManager',
'Foo\Service\Logger',
],
'timezone' => 'Europe\London'
];
The handlerPath declaration must include your handlers; the best strategy is to utilize Phalcon collections. The contents of this file might look something like this:
<?php
namespace Foo\Config;
use Foo\Controller\Index;
use Phalcon\Mvc\Micro\Collection;
$handler = new Collection();
$handler->setHandler(Index::class, true);
$handler->setPrefix('/');
$handler->get('/', 'indexAction', 'apiIndex');
$app->mount($handler);
Now, create an index file inside the public directory and copy-&-paste the following:
<?php
declare(strict_types=1);
chdir(dirname(__DIR__));
require 'Boot.php';
Finally, paste the following bootstrap code inside the Boot.php file:
<?php
declare(strict_types=1);
use Headio\Phalcon\Bootstrap\Bootstrap;
use Headio\Phalcon\Bootstrap\Di\Factory as DiFactory;
use Phalcon\Config\Config;
require_once __DIR__ . '/vendor/autoload.php';
// Micro example
(function () {
$config = new Config(
require __DIR__ . '/src/Config/Config.php'
);
$di = (new DiFactory($config))->createDefaultMvc();
// Environment
if (extension_loaded('mbstring')) {
mb_internal_encoding('UTF-8');
mb_substitute_character('none');
}
set_error_handler(
function ($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// Unmasked error context
return;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
}
);
set_exception_handler(
function (Throwable $e) use ($di) {
$di->get('logger')->error($e->getMessage(), ['exception' => $e]);
// Verbose exception handling for development
if ($di->get('config')->debug) {
}
exit(1);
}
);
// Run the application
return Bootstrap::handle($di)->run($_SERVER['REQUEST_URI'], Bootstrap::Micro);
})();
Create a config definition file inside your Phalcon project. This file should include your configuration settings and service & middleware definitions.
Let's assume the following mvc project structure:
├── public
│ ├── index.php
├── src
│ ├── Config
│ │ │── Config.php
│ │── Controller
│ │── Domain
│ │── Middleware
│ │── Module
│ │ │── Admin
│ │ │ │── Controller
│ │ │ │── Form
│ │ │ │── Task
│ │ │ │── View
│ │ │ │── Module.php
│ │── Service
│ │── Var
│ │ │── Log
├── tests
├── vendor
├── Boot.php
├── codeception.yml
├── composer.json
├── .gitignore
├── README.md
└── .travis.yml
and your PSR-4 autoload declaration is:
{
"autoload": {
"psr-4": {
"Foo\\": "src/"
}
}
}
Create a config file Config.php inside the Config directory and copy-&-paste the following definition:
<?php
namespace Foo\Config;
return [
'annotations' => [
'adapter' => 'Apcu',
'options' => [
'lifetime' => 3600 * 24 * 30,
'prefix' => 'annotations',
],
],
'applicationPath' => dirname(__DIR__) . DIRECTORY_SEPARATOR,
'baseUri' => '/',
'debug' => true,
'dispatcher' => [
'defaultAction' => 'index',
'defaultController' => 'Admin',
'defaultControllerNamespace' => 'Foo\\Module\\Admin\\Controller',
'defaultModule' => 'admin'
],
'locale' => 'en_GB',
'logPath' => dirname(__DIR__) .
DIRECTORY_SEPARATOR . 'Var' .
DIRECTORY_SEPARATOR . 'Log' .
DIRECTORY_SEPARATOR,
'modules' => [
'admin' => [
'className' => 'Foo\\Module\\Admin\\Module',
'path' => dirname(__DIR__) . '/Module/Admin/Module.php'
],
],
'middleware' => [
'Foo\\Middleware\\Bar'
],
'routes' => [
'admin' => [
'Foo\Module\Admin\Controller\Admin' => '/admin',
],
],
'services' => [
'Foo\Service\EventManager',
'Foo\Service\Logger',
'Foo\Service\Annotation',
'Foo\Service\Router',
'Foo\Service\View'
],
'timezone' => 'Europe\London',
'useI18n' => true,
'view' => [
'defaultPath' => dirname(__DIR__) . '/Module/Admin/View/',
'compiledPath' => dirname(__DIR__) . '/Cache/Volt/',
'compiledSeparator' => '_',
]
];
Now, create an index file inside the public directory and paste the following:
<?php
declare(strict_types=1);
chdir(dirname(__DIR__));
require 'Boot.php';
Finally, paste the following bootstrap code inside the Boot.php file:
<?php
declare(strict_types=1);
use Headio\Phalcon\Bootstrap\Bootstrap;
use Headio\Phalcon\Bootstrap\Di\Factory as DiFactory;
use Phalcon\Config\Config;
require_once __DIR__ . '/vendor/autoload.php';
// Mvc example
(function () {
$config = new Config(
require __DIR__ . '/src/Config/Config.php'
);
$di = (new DiFactory($config))->createDefaultMvc();
// Environment
if (extension_loaded('mbstring')) {
mb_internal_encoding('UTF-8');
mb_substitute_character('none');
}
set_error_handler(
function ($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// Unmasked error context
return;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
}
);
set_exception_handler(
function (Throwable $e) use ($di) {
$di->get('logger')->error($e->getMessage(), ['exception' => $e]);
// Verbose exception handling for development
if ($di->get('config')->debug) {
}
exit(1);
}
);
// Run the application
return Bootstrap::handle($di)->run($_SERVER['REQUEST_URI']);
})();
Create a config definition file inside your Phalcon project. This file should include your configuration settings and service & middleware definitions.
Let's assume the following project structure:
├── src
│ ├── Config
│ │ │── Config.php
│ │── Domain
│ │── Middleware
│ │── Service
│ │── Task
│ │── Var
│ │ │── Log
├── tests
├── vendor
├── Cli.php
├── codeception.yml
├── composer.json
├── .gitignore
├── README.md
└── .travis.yml
and your PSR-4 autoload declaration is:
{
"autoload": {
"psr-4": {
"Foo\\": "src/"
}
}
}
Create a config file Config.php inside the Config directory and copy-&-paste the following definition:
<?php
namespace Foo\Config;
return [
'applicationPath' => dirname(__DIR__) . DIRECTORY_SEPARATOR,
'locale' => 'en_GB',
'logPath' => dirname(__DIR__) .
DIRECTORY_SEPARATOR . 'Var' .
DIRECTORY_SEPARATOR . 'Log' .
DIRECTORY_SEPARATOR,
'debug' => true,
'dispatcher' => [
'defaultTaskNamespace' => 'Foo\\Task',
],
'middleware' => [
],
'services' => [
'Foo\Service\EventManager',
'Foo\Service\Logger',
'Foo\Service\ConsoleOutput',
],
'timezone' => 'Europe\London'
];
Finally, paste the following bootstrap code inside the Cli.php file:
<?php
declare(strict_types=1);
use Headio\Phalcon\Bootstrap\Cli\Bootstrap;
use Headio\Phalcon\Bootstrap\Di\Factory as DiFactory;
use Phalcon\Config\Config;
require_once __DIR__ . '/vendor/autoload.php';
// Cli example
(function () {
$config = new Config(
require __DIR__ . '/src/Config/Config.php'
);
$di = (new DiFactory($config))->createDefaultCli();
// Environment
set_error_handler(
function ($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// Unmasked error context
return;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
}
);
set_exception_handler(
function (Throwable $e) use ($di) {
$di->get('logger')->error($e->getMessage(), ['exception' => $e]);
$output = $di->get('consoleOutput');
$output->writeln('<error>' . $e->getMessage() . '</error>');
// Verbose exception handling for development
if ($di->get('config')->debug) {
$output->writeln(sprintf(
'<error>Exception thrown in: %s at line %d.</error>',
$e->getFile(),
$e->getLine())
);
}
exit(1);
}
);
// Run the application
return Bootstrap::handle($di)->run($_SERVER);
})();
From the examples above you will have noticed that we instantiated Phalcon's factory default mvc or cli container services.
$config = new Config(
require __DIR__ . '/src/Config/Config.php'
);
// Micro/Mvc
$di = (new DiFactory($config))->createDefaultMvc();
// Cli
$di = (new DiFactory($config))->createDefaultCli();
Naturally, you can override the factory default services by simply defining a service definition in your config file, like so:
<?php
namespace Foo\Config
return [
'services' => [
'Foo\Service\Router'
]
]
Then create the respective service provider and modify its behaviour:
<?php
/**
* This source file is subject to the MIT License.
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this package.
*/
declare(strict_types=1);
namespace Foo\Service;
use Foo\Exception\OutOfRangeException;
use Phalcon\Config\Config;
use Phalcon\Di\ServiceProviderInterface;
use Phalcon\Di\DiInterface;
use Phalcon\Cli\Router as CliService;
use Phalcon\Mvc\Router as MvcRouter;
use Phalcon\Mvc\Router\Annotations as MvcService;
class Router implements ServiceProviderInterface
{
/**
* {@inheritDoc}
*/
public function register(DiInterface $di) : void
{
$di->setShared(
'router',
function () use ($di) {
$config = $di->get('config');
if ($config->get('cli')) {
$service = new CliService();
$service->setDefaultModule($config->dispatcher->defaultTaskModule);
return $service;
}
if (!$config->has('modules')) {
throw new OutOfRangeException('Undefined modules');
}
if (!$config->has('routes')) {
throw new OutOfRangeException('Undefined routes');
}
$service = new MvcService(false);
$service->removeExtraSlashes(true);
$service->setDefaultNamespace($config->dispatcher->defaultControllerNamespace);
$service->setDefaultModule($config->dispatcher->defaultModule);
$service->setDefaultController($config->dispatcher->defaultController);
$service->setDefaultAction($config->dispatcher->defaultAction);
foreach ($config->get('modules')->toArray() ?? [] as $module => $settings) {
if (!$config->routes->get($module, false)) {
continue;
}
foreach ($config->get('routes')->{$module}->toArray() ?? [] as $key => $val) {
$service->addModuleResource($module, $key, $val);
}
}
return $service;
}
);
}
}
For complete control over the registration of service dependencies, or more generally, the services available in the container, you have two options: firstly, you can use Phalcon's base DI container, which is an empty container; or you can create your own DI container by implementing Phalcon's Phalcon\Di\DiInterface. See the following for an example:
use Phalcon\Di;
use Foo\Bar\MyDi;
$config = new Config(
require __DIR__ . '/src/Config/Config.php'
);
// Empty DI container
$di = (new DiFactory($config))->create(new Di);
// Custom DI container
$di = (new DiFactory($config))->create(new MyDi);
The DI factory create method expects an instance of Phalcon\Di\DiInterface.
The bootstrap factory will automatically instantiate a Phalcon application and return the response. If you want to bootstrap the application yourself, you can use the application factory directly.
<?php
/**
* This source file is subject to the MIT License.
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this package.
*/
declare(strict_types=1);
use Headio\Phalcon\Bootstrap\Application\Factory as AppFactory;
use Headio\Phalcon\Bootstrap\Di\Factory as DiFactory;
use Phalcon\Config\Config;
chdir(dirname(__DIR__));
require_once 'vendor/autoload.php';
$config = new Config(
require 'src/Config/Config.php'
);
try {
$di = (new DiFactory($config))->createDefaultMvc();
/** @var Phalcon\Mvc\Application */
$app = (new AppFactory($di))->createForMvc();
// Do some stuff
/** @var Phalcon\Mvc\ResponseInterface|bool */
$response = $app->handle($_SERVER['REQUEST_URI']);
if ($response instanceof \Phalcon\Mvc\ResponseInterface) {
return $response->send();
}
return $response;
} catch(\Throwable $e) {
echo $e->getMessage();
}
To see the tests, run:
php vendor/bin/codecept run -f --coverage --coverage-text
Phalcon bootstrap is open-source and licensed under MIT License.