Create a Symfony project
Let's start by creating a fresh new Symfony application using Symfony CLI. Open a terminal and run:
symfony new poc-symfony
The project name poc-symfony
here is not important, you can choose whatever you want as project name.
Iβm using here Symfony CLI to create a new Symfony application.
If you donβt want to use it, you can use the famous Composer tool as an alternative to create the application:
composer create-project symfony/skeleton poc-symfony
Read this doc to know more about using Composer to create Symfony application.
Start the integrated Symfony server:
symfony serve -d
The -d
option is used to run the server as a daemon, in the background. To stop it, you can run:
symfony server:stop
If you didn't installed the Symfony CLI, you can use the built-in PHP web server by running:
php -S localhost:8000 -t public/
By default, the port used is the 8000
. Open now your application in your favorite browser at the given URL (https://localhost:8000 by default).
You now have something like this in your browser:
Our goal here : create our first Symfony Controller and render our first template with Twig to show Hello from Symfony!π
First, install the twig package by running:
composer require twig
Thanks to Symfony Flex, we have also a few files who were added to our project.
Create now our first PHP file which will be our first Symfony Controller. I'm gonna create a new file called HomeController.php
(in src/Controller
directory, you can name it whatever you want) with this content:
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class HomeController extends AbstractController
{
#[Route('/', name: 'home')]
public function home(): Response
{
return $this->render('home/index.html.twig');
}
}
Nothing special here, the HomeController
is extending the Symfony AbstractController
, and use the render
method from it to return a Response
from the HTTPFoundation Component.
We have a route matching the /
pattern, with name home
. The associated method is also named home
, and return a twig template.
The twig template content contains this:
{% extends 'base.html.twig' %}
{% block body %}
<h1>Hello from Symfony!π</h1>
{% endblock %}
We just extends the base.html.twig
file and customize the content in the body block.
Refresh your page and voilΓ π
Congratulations, you made it π
Testing our application
Testing time ποΈ
Itβs time to test our application.
You don't test your application ? You are too good to add tests ?
Your choice, but I prefer to add some to make sure everything works as expected.
Let's add a new pack for testing by running:
composer require --dev symfony/test-pack
Again, thanks to Symfony Flex, we have a few more files installed with a default configuration working.
Create now a PHP file for test. I called this file HomeControllerTest
(in tests/Controller
directory):
<?php
declare(strict_types=1);
namespace App\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class HomeControllerTest extends WebTestCase
{
public function testHomepage(): void
{
$client = static::createClient();
$client->request('GET', '/');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h1', 'Hello from Symfony!π');
}
}
This simple test can help us check everything works in our application.
Run the tests:
./bin/phpunit
Congrats, the test pass, you rock π€
Dockerize our application
Let's now dockerize our application.
For this project, i'm gonna use a famous template as a base for our application: https://github.com/dunglas/symfony-docker
This template has two environments : dev
and prod
. That's perfect for our needs. I'm just gonna remove useless files.
Start by creating a compose.yaml
(the old filename was docker-compose.yaml
):
services:
php:
image: ${IMAGES_PREFIX:-}app-php
restart: unless-stopped
environment:
SERVER_NAME: ${SERVER_NAME:-localhost}, php:80
volumes:
- ./:/app
- caddy_data:/data
- caddy_config:/config
ports:
# HTTP
- target: 80
published: ${HTTP_PORT:-80}
protocol: tcp
# HTTPS
- target: 443
published: ${HTTPS_PORT:-443}
protocol: tcp
# HTTP/3
- target: 443
published: ${HTTP3_PORT:-443}
protocol: udp
volumes:
caddy_data:
caddy_config:
This configuration will be common to our two environments.
Create now a compose.override.yaml
file, which will be our default configuration for our dev
env:
services:
php:
build:
context: .
target: frankenphp_dev
volumes:
- ./frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro
- ./frankenphp/conf.d/app.dev.ini:/usr/local/etc/php/conf.d/app.dev.ini:ro
environment:
XDEBUG_MODE: "${XDEBUG_MODE:-off}"
extra_hosts:
- host.docker.internal:host-gateway
tty: true
There is the compose.prod.yaml
content, for our prod
env:
services:
php:
build:
context: .
target: frankenphp_prod
environment:
APP_SECRET: ${APP_SECRET}
Don't forget to create a Dockerfile, for our custom docker image:
#syntax=docker/dockerfile:1.4
# Versions
FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream
# The different stages of this Dockerfile are meant to be built into separate images
# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage
# https://docs.docker.com/compose/compose-file/#target
# Base FrankenPHP image
FROM frankenphp_upstream AS frankenphp_base
WORKDIR /app
VOLUME /app/var/
# persistent / runtime deps
# hadolint ignore=DL3008
RUN apt-get update && apt-get install -y --no-install-recommends \
acl \
file \
gettext \
git \
&& rm -rf /var/lib/apt/lists/*
RUN set -eux; \
install-php-extensions \
@composer \
apcu \
intl \
opcache \
zip \
;
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
ENV COMPOSER_ALLOW_SUPERUSER=1
###> recipes ###
###< recipes ###
COPY --link frankenphp/conf.d/app.ini $PHP_INI_DIR/conf.d/
COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
COPY --link frankenphp/Caddyfile /etc/caddy/Caddyfile
ENTRYPOINT ["docker-entrypoint"]
HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1
CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile" ]
# Dev FrankenPHP image
FROM frankenphp_base AS frankenphp_dev
ENV APP_ENV=dev XDEBUG_MODE=off
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
RUN set -eux; \
install-php-extensions \
xdebug \
;
COPY --link frankenphp/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/
CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ]
# Prod FrankenPHP image
FROM frankenphp_base AS frankenphp_prod
ENV APP_ENV=prod
ENV FRANKENPHP_CONFIG="import worker.Caddyfile"
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --link frankenphp/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/
COPY --link frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile
# prevent the reinstallation of vendors at every changes in the source code
COPY --link composer.* symfony.* ./
RUN set -eux; \
composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress
# copy sources
COPY --link . ./
RUN rm -Rf frankenphp/
RUN set -eux; \
mkdir -p var/cache var/log; \
composer dump-autoload --classmap-authoritative --no-dev; \
composer dump-env prod; \
composer run-script --no-dev post-install-cmd; \
chmod +x bin/console; sync;
That's almost it for our Docker configuration files.
We just need a few more files for our PHP configuration for our Docker PHP service.
Again, i'm just gonna past here some configuration files for FrankenPHP, but you can easily find them in the associated repository.
There is our custom configuration for Caddy (FrankenPHP is based on the Caddy web server), this file is named frankenphp/Caddyfile
:
{
{$CADDY_GLOBAL_OPTIONS}
frankenphp {
{$FRANKENPHP_CONFIG}
}
}
{$CADDY_EXTRA_CONFIG}
{$SERVER_NAME:localhost} {
log {
# Redact the authorization query parameter that can be set by Mercure
format filter {
request>uri query {
replace authorization REDACTED
}
}
}
root * /app/public
encode zstd br gzip
{$CADDY_SERVER_EXTRA_DIRECTIVES}
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
header ?Permissions-Policy "browsing-topics=()"
php_server
}
Now, add some custom for our PHP configuration, first for frankenphp/conf.d/app.ini
:
expose_php = 0
date.timezone = UTC
apc.enable_cli = 1
session.use_strict_mode = 1
zend.detect_unicode = 0
; https://symfony.com/doc/current/performance.html
realpath_cache_size = 4096K
realpath_cache_ttl = 600
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.memory_consumption = 256
opcache.enable_file_override = 1
This is a common configuration, for our environments.
Now, our dev
configuration, the file is named frankenphp/conf.d/app.dev.ini
:
xdebug.client_host = host.docker.internal
A good xdebug configuration, for debugging in dev
π.
And last, for our prod
configuration (frankenphp/conf.d/app.prod.ini
):
opcache.preload_user = root
opcache.preload = /app/config/preload.php
We have almost finished, let's also add a frankenphp/docker-entrypoint.sh
:
#!/bin/sh
set -e
if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then
composer install --prefer-dist --no-progress --no-interaction
fi
setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var
setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var
fi
exec docker-php-entrypoint "$@"
Last thing, we need to add a library to handle the worker mode for FrankenPHP, install it with composer:
composer require runtime/frankenphp-symfony
Don't forget to add his associated configuration (frankenphp/worker.Caddyfile
):
worker {
file ./public/index.php
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
}
Congratulations, you now have dockerized your Symfony application π
You can now build your images:
docker compose build --no-cache
Start your containers in dev
mode:
docker compose up -d
And in prod
:
docker compose -f compose.yaml -f compose.prod.yaml up -d
If you have some troubles, you can check the docker logs:
docker compose logs -f
You can also run again your tests in your Docker container and make sure they still pass:
docker compose exec php bin/phpunit
I created a repository where you can find all of this code and associated commands here : https://github.com/abdounikarim/poc-symfony
I hope thie article helped you in some way, maybe by learning something π
Happy coding π
Top comments (0)