Slim 4 - Tutorial
source link: https://odan.github.io/2019/11/05/slim4-tutorial.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Daniel's Dev Blog
Developer, Trainer, Open Source Contributor
Blog About me DonateSlim 4 - Tutorial
05 Nov 2019
This tutorial shows you how to work with the powerful and lightweight Slim 4 framework.
You can buy all Slim articles as eBook.
Table of contents
Requirements
- PHP 7.2+
- MySQL 5.7+ or MariaDB
- Apache webserver with mod_rewrite and .htaccess
- Composer (only for development)
Introduction
Slim Framework is a great microframework for web applications, RESTful API’s and websites.
Our aim is to create a RESTful API with routing, business logic and database operations.
Standards like PSR and best practices are very important and integrated part of this tutorial.
Installation
Create a new project directory and run this command to install the Slim 4 core components:
composer require slim/slim:"4.*"
In Slim 4 the PSR-7 implementation is decoupled from the App core. This means you can also install other PSR-7 implementations like nyholm/psr7.
In our case we are installing the Slim PSR-7 implementations using this command:
composer require slim/psr7
As next we need a PSR-11 container implementation for dependency injection and autowiring.
Run this command to install PHP-DI:
composer require php-di/php-di
For testing purpose we are installing phpunit as development dependency with the --dev
option:
composer require phpunit/phpunit --dev
Ok nice, now we have installed the most basic dependencies for our project. Later we will add more.
Note: Please don’t commit the vendor/
to your git repository. To set up the git repository correctly, create a file called .gitignore
in the project root folder and add the following lines to this file:
vendor/
.idea/
Directory structure
A good directory structure helps you organize your code, simplifies setup on the webserver and increases the security of the entire application.
Create the following directory structure in the root directory of your project:
.
├── config/ Configuration files
├── public/ Web server files (DocumentRoot)
│ └── .htaccess Apache redirect rules for the front controller
│ └── index.php The front controller
├── templates/ Twig templates
├── src/ PHP source code (The App namespace)
├── tmp/ Temporary files (cache and logfiles)
├── vendor/ Reserved for composer
├── .htaccess Internal redirect to the public/ directory
├── .gitignore Git ignore rules
└── composer.json Project dependencies and autoloader
In a web application, it is important to distinguish between the public and non-public areas.
The public/
directory serves your application and will therefore also be
directly accessible by all browsers, search engines and API clients.
All other folders are not public and must not be accessible online.
This can be done by defining the public
folder in Apache as DocumentRoot
of your website. But more about that later.
PSR-4 autoloading
One of the most fundamental and important thing is to have a
working PSR-4 autoloader.
For the next steps we have to define the src/
directory as root for the \App
namespace.
Add this autoloading settings into composer.json
:
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Test\\": "tests/"
}
}
The complete composer.json
file should look like this:
{
"require": {
"php-di/php-di": "^6.0",
"slim/psr7": "^1",
"slim/slim": "^4.4"
},
"require-dev": {
"phpunit/phpunit": "^8.4"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Test\\": "tests/"
}
},
"config": {
"process-timeout": 0,
"sort-packages": true
}
}
Run composer update
for the changes to take effect.
Apache URL rewriting
To run a Slim app with apache we have to add url rewrite rules to redirect the web traffic to a so called front controller.
The front controller is just a index.php
file and the entry point to the application.
- Create a directory:
public/
- Create a
.htaccess
file in yourpublic/
directory and copy/paste this content:
# Redirect to front controller
RewriteEngine On
# RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
Please don’t change the RewriteRule
directive. It must be exactly as shown above.
- Create a second
.htaccess
file in your project root-directory and copy/paste this content:
RewriteEngine on
RewriteRule ^$ public/ [L]
RewriteRule (.*) public/$1 [L]
Don’t skip this step. This second .htaccess
file is important to run your Slim app in a sub-directory
and within your development environment.
- Create the front-controller file
public/index.php
and copy/paste this content:
<?php
(require __DIR__ . '/../config/bootstrap.php')->run();
The front controller is the entry point to your slim application and handles all requests by channeling requests through a single handler object.
Configuration
The directory for all configuration files is: config/
The file config/settings.php
is the main configuration file and combines
the default settings with environment specific settings.
- Create a directory:
config/
- Create a configuration file
config/settings.php
and copy/paste this content:
<?php
// Error reporting for production
error_reporting(0);
ini_set('display_errors', '0');
// Timezone
date_default_timezone_set('Europe/Berlin');
// Settings
$settings = [];
// Path settings
$settings['root'] = dirname(__DIR__);
$settings['temp'] = $settings['root'] . '/tmp';
$settings['public'] = $settings['root'] . '/public';
// Error Handling Middleware settings
$settings['error'] = [
// Should be set to false in production
'display_error_details' => true,
// Parameter is passed to the default ErrorHandler
// View in rendered output by enabling the "displayErrorDetails" setting.
// For the console and unit tests we also disable it
'log_errors' => true,
// Display error details in error log
'log_error_details' => true,
];
return $settings;
Startup
The app startup process contains the code that is executed when the application (request) is started.
The bootstrap procedure includes the composer autoloader and then continues to build the container, creates the app and registers the routes + middleware entries.
Create the bootstrap file config/bootstrap.php
and copy/paste this content:
<?php
use DI\ContainerBuilder;
use Slim\App;
require_once __DIR__ . '/../vendor/autoload.php';
$containerBuilder = new ContainerBuilder();
// Set up settings
$containerBuilder->addDefinitions(__DIR__ . '/container.php');
// Build PHP-DI Container instance
$container = $containerBuilder->build();
// Create App instance
$app = $container->get(App::class);
// Register routes
(require __DIR__ . '/routes.php')($app);
// Register middleware
(require __DIR__ . '/middleware.php')($app);
return $app;
Routing setup
Create a file for all routes config/routes.php
and copy/paste this content:
<?php
use Slim\App;
return function (App $app) {
// empty
};
Middleware
What is a middleware?
A middleware can be executed before and after your Slim application to manipulate the request and response object according to your requirements.
Routing and error middleware
Create a file to load global middleware handler config/middleware.php
and copy/paste this content:
<?php
use Slim\App;
use Slim\Middleware\ErrorMiddleware;
return function (App $app) {
// Parse json, form data and xml
$app->addBodyParsingMiddleware();
// Add the Slim built-in routing middleware
$app->addRoutingMiddleware();
// Catch exceptions and errors
$app->add(ErrorMiddleware::class);
};
Container
A quick guide to the container
Dependency injection is passing dependency to other objects. Dependency injection makes testing easier. The injection can be done through a constructor.
A dependencies injection container (aka IoC Container) is a tool for injecting dependencies.
A general rule: The core application should not use the container. Injecting the container into a class is an anti-pattern. You should declare all class dependencies in the constructor explicitly.
Why is injecting the container (in most cases) an anti-pattern?
In Slim 3 the Service Locator (anti-pattern) was the default “style” to inject the whole (Pimple) container and fetch the dependencies from it. However, there are the following disadvantages:
- The Service Locator (anti-pattern) hides the actual dependencies of your class.
- The Service Locator (anti-pattern) also violates the Inversion of Control (IoC) principle of SOLID.
Q: How can I make it better?
A: Use composition over inheritance and (constructor) dependency injection.
Since Slim 4 you can use modern tools like PHP-DI with the awesome autowire feature. This means: Now you can declare all dependencies explicitly in your constructor and let the DIC inject these dependencies for you.
To be more clear: Composition has nothing to do with the “autowire” feature of the DIC. You can use composition with pure classes and without a container or anything else. The autowire feature just uses the PHP Reflection classes to resolve and inject the dependencies automatically for you.
Container definitions
Slim 4 uses a dependency injection container to prepare, manage and inject application dependencies.
You can add any container library that implements the PSR-11 interface.
Create a new file for the container entries config/container.php
and copy/paste this content:
<?php
use Psr\Container\ContainerInterface;
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Middleware\ErrorMiddleware;
return [
'settings' => function () {
return require __DIR__ . '/settings.php';
},
App::class => function (ContainerInterface $container) {
AppFactory::setContainer($container);
return AppFactory::create();
},
ErrorMiddleware::class => function (ContainerInterface $container) {
$app = $container->get(App::class);
$settings = $container->get('settings')['error'];
return new ErrorMiddleware(
$app->getCallableResolver(),
$app->getResponseFactory(),
(bool)$settings['display_error_details'],
(bool)$settings['log_errors'],
(bool)$settings['log_error_details']
);
},
];
Base path
Most people will get a 404 error (not found), because the basePath is not set corretly.
If you run your Slim app in a sub-directory, resp. not directly within the DocumentRoot of your webserver, you must set the “correct” base path.
Ideally the DoumentRoot
of your production server points directly to the public/
directory.
In all other cases you have to make sure, that your base path is correct. For example,
the DocumentRoot directory is /var/www/domain.com/htdocs/
, but the application
is stored under /var/www/domain.com/htdocs/my-app/
, then you have to set /my-app
as base path.
To be more precise: In this context “sub-directory” means a sub-directory of the project,
and not the public/
directory. For example when you place your app not directly
under the webservers DocumentRoot
.
For security reasons you should always place your front-controller (index.php) into the public/
directory. Don’t place your front controller directly into the project root directory.
You can manually set the base path in Slim using the setBasePath
method:
$app->setBasePath('/slim4-tutorial');
But the problem is, that the basePath can be different for each host (dev, testing, staging, prod etc…).
The BasePathMiddleware detects and sets the base path into the Slim app instance.
To install the BasePathMiddleware, run:
composer require selective/basepath
Add the following container definition into config/container.php
:
use Selective\BasePath\BasePathMiddleware;
// ...
return [
// ...
BasePathMiddleware::class => function (ContainerInterface $container) {
return new BasePathMiddleware($container->get(App::class));
},
];
Then add the BasePathMiddleware::class
to the middleware stack in config/middleware.php
:
<?php
use Selective\BasePath\BasePathMiddleware;
use Slim\App;
use Slim\Middleware\ErrorMiddleware;
return function (App $app) {
// Parse json, form data and xml
$app->addBodyParsingMiddleware();
// Add the Slim built-in routing middleware
$app->addRoutingMiddleware();
$app->add(BasePathMiddleware::class); // <--- here
// Catch exceptions and errors
$app->add(ErrorMiddleware::class);
};
Now that you have installed the BasePathMiddleware
, remove this line (if exists): $app->setBasePath('...');
.
Your first route
Open the file config/routes.php
and insert the code for the first route:
<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;
return function (App $app) {
$app->get('/', function (
ServerRequestInterface $request,
ResponseInterface $response
) {
$response->getBody()->write('Hello, World!');
return $response;
});
};
Now open your website, e.g. http://localhost and you should see the message Hello, World!
.
Good URLs
Be careful: The public/
directory is only the DoumentRoot
of your webserver,
but it’s never part of your base path and the official url.
Good URLs:
http://www.example.com
http://www.example.com/users
http://www.example.com/my-app
http://www.example.com/my-app/users
Bad URLs:
http://www.example.com/public
http://www.example.com/public/users
http://www.example.com/my-app/public
http://www.example.com/my-app/public/users
Actions
Slim provides some methods for adding controller logic directly in a route callback. The PSR-7 request object is injected into your Slim application routes as the first argument to the route callback like this:
<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
// ...
$app->get('/hello', function (ServerRequestInterface $request, ResponseInterface $response) {
$response->getBody()->write('Hello World');
return $response;
});
While such interfaces look intuitive, they are not suitable for complex business logic scenarios. Assuming there are tens or even hundreds of route handlers that need to be registered. Unless your logic is very simple, I don’t recommend using route callbacks. Isn’t it a better practice to implement these handlers in their own classes? Yes. This is the moment where a Single Action Controller come into play.
Each Single Action Controller is represented by its own class.
The Action does only these things:
- Collects input from the HTTP request (if needed)
- Invokes the Domain with those inputs (if required) and retains the result
- Builds an HTTP response (typically with the Domain invocation results).
All other logic, including all forms of input validation, error handling, and so on, are therefore pushed out of the Action and into the Domain (for domain logic concerns) or the response renderer (for presentation logic concerns).
A response could be rendered to HTML (e.g with Twig) for a standard web request; or it might be something like JSON for RESTful API requests.
Note: Closures (functions) as routing handlers are quite “expensive”, because PHP has to create all closures for each request. The use of class names is more lightweight, faster and scales better for larger applications.
More details about the flow of everything that happens when arriving a route and the communication between the different layers can be found here: Action
- Create a sub-directory:
src/Action
- Create this action class in:
src/Action/HomeAction.php
<?php
namespace App\Action;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
final class HomeAction
{
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response
): ResponseInterface {
$response->getBody()->write('Hello, World!');
return $response;
}
}
Then open config/routes.php
and replace the route closure for /
with this line:
$app->get('/', \App\Action\HomeAction::class)->setName('home');;
The complete config/routes.php
should look like this now:
<?php
use Slim\App;
return function (App $app) {
$app->get('/', \App\Action\HomeAction::class)->setName('home');
};
Now open your website, e.g. http://localhost and you should see the message Hello, World!
.
Writing JSON to the response
To create a valid JSON response you can write the json encoded string to the response body
and set the Content-Type
header to application/json
:
<?php
namespace App\Action;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
final class HomeAction
{
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response
): ResponseInterface {
$response->getBody()->write(json_encode(['success' => true]));
return $response->withHeader('Content-Type', 'application/json');
}
}
Open your website, e.g. http://localhost and you should see the JSON response {"success":true}
.
To change to http status code, just use the $response->withStatus(x)
method. Example:
$result = ['error' => ['message' => 'Validation failed']];
$response->getBody()->write(json_encode($result));
return $response
->withHeader('Content-Type', 'application/json')
->withStatus(422);
Domain
Forget CRUD! Your API should reflect the business use cases and not the technical “database operations” aka. CRUD. Don’t put business logic into actions. The action invokes the domain layer, resp. the service. If you want to reuse the same logic in another action, then just invoke that service you need in your action.
Services
The Domain is the place for the complex business logic.
Instead of putting the logic into gigantic (fat) “Models”, we put the logic into smaller, specialized Service classes.
A service provides a specific functionality or a set of functionalities, such as the retrieval of specified information or the execution of a set of operations, with a purpose that different clients can reuse for different purposes.
There can be multiple clients for a service, e.g. the action (request), another service, the CLI (console) and the unit-test environment (phpunit).
A service class is not a “Manager” or “Utility” class.
Each service class should have only one responsibility, e.g. to transfer money from A to B, and not more.
Separate data from behavior by using services for the behavior and Data transfer objects for the data.
The directory for all (domain) modules and sub-modules is: src/Domain
Create the code for the service class src/Domain/User/Service/UserCreator.php
:
<?php
namespace App\Domain\User\Service;
use App\Domain\User\Repository\UserCreatorRepository;
use App\Exception\ValidationException;
/**
* Service.
*/
final class UserCreator
{
/**
* @var UserCreatorRepository
*/
private $repository;
/**
* The constructor.
*
* @param UserCreatorRepository $repository The repository
*/
public function __construct(UserCreatorRepository $repository)
{
$this->repository = $repository;
}
/**
* Create a new user.
*
* @param array $data The form data
*
* @return int The new user ID
*/
public function createUser(array $data): int
{
// Input validation
$this->validateNewUser($data);
// Insert user
$userId = $this->repository->insertUser($data);
// Logging here: User created successfully
//$this->logger->info(sprintf('User created successfully: %s', $userId));
return $userId;
}
/**
* Input validation.
*
* @param array $data The form data
*
* @throws ValidationException
*
* @return void
*/
private function validateNewUser(array $data): void
{
$errors = [];
// Here you can also use your preferred validation library
if (empty($data['username'])) {
$errors['username'] = 'Input required';
}
if (empty($data['email'])) {
$errors['email'] = 'Input required';
} elseif (filter_var($data['email'], FILTER_VALIDATE_EMAIL) === false) {
$errors['email'] = 'Invalid email address';
}
if ($errors) {
throw new ValidationException('Please check your input', $errors);
}
}
}
Take a look at the constructor! You can see that we have declared the UserCreatorRepository
as a
dependency, because the service can only interact with the database through the repository.
Validation
To validate the clients input we have to check the data and collect all errors into a collection (array) of errors. This pattern is called notification pattern.
Create a new file in src/Exception/ValidationException.php
and copy/paste this content:
<?php
namespace App\Exception;
use RuntimeException;
use Throwable;
final class ValidationException extends RuntimeException
{
private $errors;
public function __construct(
string $message,
array $errors = [],
int $code = 422,
Throwable $previous = null
){
parent::__construct($message, $code, $previous);
$this->errors = $errors;
}
public function getErrors(): array
{
return $this->errors;
}
}
If you like this pattern for validation, I recommend to have a look at this library: selective/validation
Repositories
A repository is responsible for the data access logic, communication with database(s).
There are two types of repositories: collection-oriented and persistence-oriented repositories. In this case, we are talking about persistence-oriented repositories, since these are better suited for processing large amounts of data.
A repository is the source of all the data your application needs and mediates between the service and the database. A repository improves code maintainability, testing and readability by separating business logic from data access logic and provides centrally managed and consistent access rules for a data source. Each public repository method represents a query. The return values represent the result set of a query and can be primitive/object or list (array) of them. Database transactions must be handled on a higher level (service) and not within a repository.
Creating a repository
For this tutorial we need a test database with a users
table.
Please execute this SQL statement in your test database.
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`first_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`last_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Add the database settings into: config/settings.php
:
// Database settings
$settings['db'] = [
'driver' => 'mysql',
'host' => 'localhost',
'username' => 'root',
'database' => 'test',
'password' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'flags' => [
// Turn off persistent connections
PDO::ATTR_PERSISTENT => false,
// Enable exceptions
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// Emulate prepared statements
PDO::ATTR_EMULATE_PREPARES => true,
// Set default fetch mode to array
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
// Set character set
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci'
],
];
Insert a PDO::class
container definition to config/container.php
:
PDO::class => function (ContainerInterface $container) {
$settings = $container->get('settings')['db'];
$host = $settings['host'];
$dbname = $settings['database'];
$username = $settings['username'];
$password = $settings['password'];
$charset = $settings['charset'];
$flags = $settings['flags'];
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
return new PDO($dsn, $username, $password, $flags);
},
From now on, PHP-DI will always inject the same PDO instance as soon as we declare PDO in a constructor as dependency.
Create a new directory: src/Domain/User/Repository
Create the file: src/Domain/User/Repository/UserCreatorRepository.php
and insert this content:
<?php
namespace App\Domain\User\Repository;
use PDO;
/**
* Repository.
*/
class UserCreatorRepository
{
/**
* @var PDO The database connection
*/
private $connection;
/**
* Constructor.
*
* @param PDO $connection The database connection
*/
public function __construct(PDO $connection)
{
$this->connection = $connection;
}
/**
* Insert user row.
*
* @param array $user The user
*
* @return int The new ID
*/
public function insertUser(array $user): int
{
$row = [
'username' => $user['username'],
'first_name' => $user['first_name'],
'last_name' => $user['last_name'],
'email' => $user['email'],
];
$sql = "INSERT INTO users SET
username=:username,
first_name=:first_name,
last_name=:last_name,
email=:email;";
$this->connection->prepare($sql)->execute($row);
return (int)$this->connection->lastInsertId();
}
}
Note that we have declared PDO
as a dependency, because the repository requires a database connection.
The last part is to register a new route for POST /users
.
Create a new action class in: src/Action/UserCreateAction.php
:
<?php
namespace App\Action;
use App\Domain\User\Service\UserCreator;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
final class UserCreateAction
{
private $userCreator;
public function __construct(UserCreator $userCreator)
{
$this->userCreator = $userCreator;
}
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response
): ResponseInterface {
// Collect input from the HTTP request
$data = (array)$request->getParsedBody();
// Invoke the Domain with inputs and retain the result
$userId = $this->userCreator->createUser($data);
// Transform the result into the JSON representation
$result = [
'user_id' => $userId
];
// Build the HTTP response
$response->getBody()->write((string)json_encode($result));
return $response
->withHeader('Content-Type', 'application/json')
->withStatus(201);
}
}
In this example, we create a “barrier” between source data and output, so that schema changes do not affect the clients. For the sake of simplicity, this is done using the same method. In reality, you would separate the input data mapping and output JSON conversion into separate parts of your application.
Add the new route in config/routes.php
:
$app->post('/users', \App\Action\UserCreateAction::class);
The complete project structure should look like this now:
Now you can test the POST /users
route with Postman to see if it works.
If successful, the result should look like this:
Deployment
For deployment on a productive server, there are some important settings and security related things to consider.
You can use composer to generate an optimized build of your application. All dev-dependencies are removed and the Composer autoloader is optimized for performance.
Run this command in the same directory as the project’s composer.json file:
composer install --no-dev --optimize-autoloader
You don’t have to run composer on your production server. Instead you should implement a build pipeline that creates an so called “artifact”. An artifact is an ZIP file you can upload and deploy on your production server.
These tools are very useful for creating artifacts:
- Apache Ant to automate your software build processes. Requires Java.
For security reasons, you should switch off the output of all error details in production:
$settings['error']['display_error_details'] = false;
Important: It’s very important to set the Apache DocumentRoot
to the public/
directory.
Otherwise, it may happen that someone else could access internal files from the web. More details
/etc/apache2/sites-enabled/000-default.conf
DocumentRoot /var/www/example.com/htdocs/public
Tip: Never store secret passwords in your git / SVN repository.
Instead you could store them in a file like env.php
and place this file one directory above your application directory. e.g.
/var/www/example.com/env.php
Conclusion
Remember the relationships:
- Slim - To handle routing and dispatching
- Single Action Controllers - Request, response handling. Invoking the domain (service method).
- Domain - The core layer of your application
- Services - To handle business logic
- Repositories - To execute database queries
General questions
Read more: Slim 4 - Cheatsheet and FAQ
Where can I find the code on github?
The source code with more examples (e.g. reading a user) can be found here: https://github.com/odan/slim4-tutorial
A complete skeleton for Slim 4 can be found here: https://github.com/odan/slim4-skeleton
How can I add unit and integration / functional tests?
Read this topic: Slim 4 - Testing
How to add JSON Web Token (JWT) / Bearer authentication?
Read this article: Slim 4 - OAuth 2.0 and JSON Web Token Setup
How to setup CORS?
Read this article: Slim 4 - CORS setup
How to add a logger?
Read this article: Slim 4 - Logging
I get a 404 (not found) error
Follow the instructions and use the BasePathMiddleware.
Error message: Callable (…) does not exist
Run composer update
to fix it.
How to add a database connection?
You can add a query builder as described here:
How to add multiple database connections?
How to build assets with webpack?
How to serve Slim with NGINX and PHP-FPM?
If you run Slim with Nginx you don’t need the .htaccess
files and the BasePathMiddleware
.
Configure Nginx with this configuration.
How to use the PHP built-in server?
Run the following command in terminal to start localhost web server:
cd public/
php -S localhost:8080
Then navigate to: http://localhost:8080/
Support
For technical questions create an issue here:
If you have Slim-Framework specific questions, visit:
© 2020 Daniel Opitz | Twitter
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK