A super organized (with a sane folder structure) and very customizable skeleton for Slim Framework v4 with Twig Views. Use it to start working quickly on a new project.
NOTE: If you want this skeleton without Twig, go to: (https://github.com/ricardoper/slim4-skeleton).
This project use PHP Composer for a fast installation without any trouble.
- PHP >= 7.2
- Customizable with an easy configuration:
- Views
- Logger
- Routes
- Handlers
- Middlewares
- Configurations
- Service Providers
- Response Emitters
- Error Handler
- Shutdown Handler
- Twig Views
- Sessions
- Controllers
- Global Helpers
- PSR-3 for logging
- Environment variables with PHP dotenv
- Pimple as Dependency Injection Container
- ddumper for development (based on Symfony VarDumper)
- Better Error Handlers (HTML, JSON, XML, PlainText - Accept / Content Type)
- Medoo Database Framework (MySQL, PostgreSQL, SQLite, MS SQL Server, ...)
NOTES:
- Medoo is optional and is not enabled by default. You can use another library as a Service Provider. If you want to use this library, don't forget to install the required PDO extensions.
- How to Install
- Most Relevant Folders
- Global Helpers
- Configurations
- Routes
- Controllers
- Views
- Middlewares
- Response Emitters
- Models
- Services Providers
- Handlers
- Database Support
- Exceptions
- Logging
- Debugging
- Demo
- Benchmarks
Run this command from the directory in which you want to install your new Slim Framework v4 Skeleton with Twig Views.
composer create-project ricardoper/slim4-twig-skeleton [my-app-name]
NOTE:
- Replace
[my-app-name]
with the desired directory name for your new application. - Ensure
storage/
is web writeable. - Point your virtual host document root to your new application's
public/
directory.
- /app : Application code (App Namespace - PSR-4)
- ./Controllers : Accepts input and converts it to commands for the model. Add your Controllers here.
- ./Emitters : Emits a response, including status line, headers, and the message body, according to the environment. Add your Response Emitters here.
- ./Handlers : Handles specified behaviors of the application. Add your Handlers here.
- ./Middlewares : Provide a convenient mechanism for filtering HTTP requests entering your application. Add your Middlewares here.
- ./Models : Manages the data, logic and rules of the application. Add your Models here.
- ./Routes : Maps an HTTP request to a request handler (controller). Add your Routes here.
- ./Services : Define bindings and inject dependencies. Add your Service Providers here.
- ./Views : Any representation of information such as a chart, diagram or table. Add your Twig Views here
- /configs : Add/modify your Configurations here
- /public : Add your Assets files here
env(string $variable, string $default)
- Returns environment variables (using DotEnv)app()
- Returns App instancecontainer(string $name)
- Returns bindings and inject dependencies from Containerconfigs(string $variable, string $default)
- Returns Configs database_path(string $path)
- Returns base path locationapp_path(string $path)
- Returns app path locationconfigs_path(string $path)
- Returns configs path locationpublic_path(string $path)
- Returns public path locationstorage_path(string $path)
- Returns storage path location
d($var1, $var2, ...)
- Dump vars in collapsed mode by defaultddd($var1, $var2, ...)
- Dump & die vars in collapsed mode by defaultde($var1, $var2, ...)
- Dump vars in expanded mode by defaultdde($var1, $var2, ...)
- Dump & die vars in expanded mode by default
You can add as many configurations files as you want (/configs
). These files will be automatically preload and merged in the container based on selected environment.
If you have an environment called "sandbox" and you want to overwrite some configuration only for this environment, you need to create a subfolder "sandbox" in /configs
. Something like that /configs/sandbox
. Then create the file that includes the configuration that you need to replace and the respective keys and values inside it.
/configs/logger.php
return [
'name' => 'app',
'maxFiles' => 7,
];
/configs/local/logger.php
return [
'name' => 'app-local',
];
Results of name
for the environment:
- prod : 'app'
- sandbox : 'app'
- local : 'app-local'
- testing : 'app'
NOTE: You can see the example in this framework for the local environment.
You can use dot notation to get values from configurations.
/configs/example.php
return [
'types' => [
'mysql' => [
'host' => 'localhost',
'port' => '3306',
],
'postgre' => [
'host' => 'localhost',
'port' => '3306',
],
],
];
If you want the host
value for MySQL type:
$this->getConfigs('example.types.mysql.host') => 'localhost'
configs('example.types.mysql.host') => 'localhost'
container('configs')->get('example.types.mysql.host') => 'localhost'
Maps an HTTP request to a request handler (Closures or Controllers).
You can add as many routes files as you want (/app/Routes
), but you need to enable these files in /apps/Routes/app.php
file.
You can organize this routes as you like. There is a little Demo that you can see how to organize this files.
use App\Controllers\Demo\AddressesController;
use App\Controllers\Demo\HelloController;
use App\Controllers\Demo\HomeController;
use Slim\App;
/**
* @var $app App
*/
$app->get('/', [(new HomeController()), 'index']);
$app->get('/flash', [(new HomeController()), 'flash']);
$app->get('/dump', [(new HomeController()), 'dump']);
$app->get('/hello/{name}', [(new HelloController()), 'index'])->setName('namedRoute');
$app->get('/addresses', [(new AddressesController()), 'list']);
$app->get('/addresses/pdo', [(new AddressesController()), 'pdo']);
Accepts input and converts it to commands for the Models.
You can add as many Controllers as you want in a cleaning way (/app/Controllers
).
After add your Controller, you can enable or disable it in your Routes.
NOTE: To have helpers you must extend the Controllers with ControllerAbstract located in \App\Kernel\Abstracts
.
use App\Kernel\Abstracts\ControllerAbstract;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
class HomeController extends ControllerAbstract
{
/**
* Index Action
*
* @param Request $request
* @param Response $response
* @return Response
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*/
public function index(Request $request, Response $response): Response
{
unset($request, $response);
return $this->render('Demo/Home/index.twig');
}
getApp()
- Returns App objectgetContainer(string $name)
- Returns the App ContainergetConfigs(string $name)
- Returns App ConfigsgetService(string $service)
- Returns Service Provider from container by namegetRequest()
- Returns HTTP RequestgetResponse()
- Returns HTTP ResponsesetEmitter(string $name, string $emitter)
: Set a new Response EmittergetView()
- Returns Twig objectrender(string $template, array $data, bool $sendHeaders)
- Render Twig view
getFlash()
- Return Flash Messages objectsetFlashMessage(string $key, $message)
- Set Flash MessagehasFlashMessage(string $key)
- Verify if key of Flash Messages existsgetFlashMessages(string $key)
- Get one or all Flash MessagesgetFlashFirstFMessage(string $key, $default)
- Get only the first Flash Messages of key
You can add as many Views as you want in a cleaning way (/app/Views
).
You can organize the folder structure as you like, too.
We recommend to use twig
extension in your files to have code highlighting.
url_for()
- returns the URL for a given route. e.g.: /hello/worldfull_url_for()
- returns the URL for a given route. e.g.: http://www.example.com/hello/worldis_current_url()
- returns true is the provided route name and parameters are valid for the current pathcurrent_url()
- returns the current path, with or without the query stringget_uri()
- returns theUriInterface
object from the incomingServerRequestInterface
objectbase_path()
- returns the base path
You can use url_for
to generate complete URLs to any Slim application named route and use is_current_url
to determine if you need to mark a link as active as shown in this example Twig template:
{% extends "layout.html" %}
{% block body %}
<h1>User List</h1>
<ul>
<li><a href="{{ url_for('profile', { 'name': 'josh' }) }}" {% if is_current_url('profile', { 'name': 'josh' }) %}class="active"{% endif %}>Josh</a></li>
<li><a href="{{ url_for('profile', { 'name': 'andrew' }) }}">Andrew</a></li>
</ul>
{% endblock %}
Provide a convenient mechanism for filtering HTTP requests entering your application.
You can add as many Middlewares as you want in a cleaning way (/app/Middlewares
).
After add your Middleware, you can enable or disable it in configs/middlewares.php
configuration file.
NOTE: Middlewares must respect the MiddlewareInterface located in \Psr\Http\Message
.
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class ExampleMiddleware implements Middleware
{
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
*
* @param Request $request
* @param RequestHandler $handler
* @return Response
*/
public function process(Request $request, RequestHandler $handler): Response
{
$response = $handler->handle($request);
$response = $response->withHeader('X-Example', 'Middleware');
return $response;
}
}
Enable it in configs/middlewares.php
:
use App\Middlewares\Demo\ExampleMiddleware;
return [
'example' => ExampleMiddleware::class,
];
Emits a response, including status line, headers, and the message body, according to the environment.
You can add as many Response Emitters as you want in a cleaning way (/app/Emitters
).
After add your Response Emitter, you can enable or disable it globally in configs/emitters.php
configuration or you can add it in the Controller for a specific action.
NOTE: Response Emitters must respect the ResponseEmitterInterface located in \App\Kernel\Interfaces
.
use App\Kernel\Interfaces\ResponseEmitterInterface;
use Psr\Http\Message\ResponseInterface;
class JsonResponseEmitter implements ResponseEmitterInterface
{
/**
* Send the response to the client
*
* @param ResponseInterface $response
* @return ResponseInterface
*/
public function emit(ResponseInterface $response): ResponseInterface
{
$response = $response
->withHeader('Content-Type', 'application/json; charset=UTF-8');
return $response;
}
}
Enable it in configs/emitters.php
:
use App\Emitters\JsonResponseEmitter;
return [
'json' => JsonResponseEmitter::class,
];
Manages the data, logic and rules of the application.
You can add as many Models as you want in a cleaning way (/app/Models
).
After add your Models, you use it for, for example, in a Controller.
NOTE: To have helpers you must extend the Model with ModelAbstract located in \App\Kernel\Abstracts
.
use App\Kernel\Abstracts\ModelAbstract;
use PDO;
class AddressesModel extends ModelAbstract
{
/**
* Get Last Addresses with Pdo
*
* @param int $limit
* @return array
*/
public function getLastWithPdo(int $limit = 25): array
{
/** @var $db PDO */
$db = $this->getDb()->pdo;
$sql = 'SELECT `address`.`address_id`,`address`.`address`,`address`.`address2`,`address`.`district`,`city`.`city`,`address`.`postal_code`,`address`.`phone` FROM `address` ';
$sql .= 'LEFT JOIN `city` ON `address`.`city_id` = `city`.`city_id` ';
$sql .= 'ORDER BY `address_id` DESC LIMIT 10';
return $db->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
getApp()
- Returns App objectgetContainer(string $name)
- Returns the App ContainergetConfigs(string $name)
- Returns App ConfigsgetService(string $service)
- Returns Service Provider from container by namegetRequest()
- Returns HTTP RequestgetResponse()
- Returns HTTP ResponsegetDb()
- Returns Database object
Define bindings and inject dependencies.
You can add as many Services Providers as you want in a cleaning way (/app/Services
).
After add your Services Provider, you can enable or disable it in configs/services.php
configuration file.
NOTE: Service Providers must respect the ServiceProviderInterface located in \App\Kernel\Interfaces
.
use App\Kernel\Interfaces\ServiceProviderInterface;
use Pimple\Container;
class ExampleServiceProvider implements ServiceProviderInterface
{
/**
* Service register name
*/
public function name(): string
{
return 'example';
}
/**
* Register new service on dependency container
*
* @param Container $container
* @return mixed
*/
public function register(Container $container)
{
return function (Container $c) {
unset($c);
return new Example();
};
}
}
Enable it in configs/services.php
:
use App\Services\Demo\ExampleServiceProvider;
return [
'example' => ExampleServiceProvider::class,
];
Handles specified behaviors of the application.
You can override the following Handlers in a cleaning way (/app/Handlers
):
- ErrorHandler (default located in
/app/Handlers/ErrorHandler
) - ShutdownHandler (default located in
/app/Handlers/ShutdownHandler
)
After add your Handler, you can enable or disable it in configs/app.php
configuration file.
use App\Handlers\ShutdownHandler;
use Slim\Handlers\ErrorHandler;
return [
// Handlers //
'errorHandler' => ErrorHandler::class,
'shutdownHandler' => ShutdownHandler::class,
Medoo is implemented out of box as a Service Provider. The use is optional and is not enabled by default.
To enable database support with Medoo you need to add this library/vendor with Composer:
composer require catfan/medoo
Once installed you need to enable the Service Provider in configs/services.php
:
use App\Services\Database\DatabaseServiceProvider;
return [
'database' => DatabaseServiceProvider::class,
];
Now you are ready to use it...
If you need more details, documentation, api reference, please visit Medoo webpage: https://medoo.in/
NOTES:
- Don't forget to load PDO extensions for your database. For example, if you need MySQL, you need to install
pdo_mysql
PHP extensions. - You can use another library as a Service Provider (Native drivers for MySQLi, PostgreSQL, MongoDB, Redis, ...).
You have some Exceptions out the box, located in \App\Kernel\Exceptions
, than you can use it:
ConfigsException - For Configurations Exceptions
ModelException - For Models Exceptions
ViewException - For ViewsExceptions
Logging is enabled by default and you can see all the output in /storage/logs/app-{date}.log
.
You can set this parameters in /.env
or in /configs/app.php
.
LOG_ERRORS | logErrors (bool) - Enable/Disable logging.
LOG_ERRORS_DETAILS | logErrorDetails (bool) - Enable/disable extra details in the logging file.
LOG_TO_OUTPUT | logToOutput (bool) - `true` to output the logs in console, `false` to output logs in file.
Debugging is disabled by default. You can set this parameters in /.env
or in /configs/app.php
.
APP_DEBUG | displayErrorDetails (bool) - Enable/disable debugging.
This skeleton has a little Demo that you can see all this points in action.
Demo URL's:
/ - Hello World Example.
/flash - Redirect with Flash Message.
/hello/{name} - Greet User. Replace {name} with your name.
/addresses - Database example with Medoo
/addresses/pdo - Database example with PDO from Medoo
/dump - See the source code of this action.
Nothing is free, so let's compare the performance loss with Slim Skeleton.
Machine:
Intel® Core™ i5-8400 CPU @ 2.80GHz × 6
16Gb RAM
SSD
Versions:
Ubuntu 20.04 LTS
Docker v19.03.8
nginx 1.17.10
PHP v7.4.3
Zend OPcache enabled
SIEGE 4.0.4
Bench Details:
25 concurrent connections
500 requests per thread
No delays between requests
Command: siege -c25 -b -r500 "URL"
This Skeleton | Slim Skeleton | |
---|---|---|
Transactions | 12500 hits | 12500 hits |
Availability | 100.00 % | 100.00 % |
Elapsed time | 9.16 secs | 8.80 secs |
Data transferred | 0.45 MB | 0.45 MB |
Response time | 0.02 secs | 0.02 secs |
Transaction rate | 1364.63 trans/sec | 1420.45 trans/sec |
Throughput | 0.05 MB/sec | 0.05 MB/sec |
Concurrency | 24.49 | 24.51 |
Successful transactions | 12500 | 12500 |
Failed transactions | 0 | 0 |
Longest transaction | 0.05 | 0.05 |
Shortest transaction | 0.00 | 0.00 |