DEV Community

Abdouni Abdelkarim
Abdouni Abdelkarim

Posted on • Edited on

Hello from Symfony

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
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Read this doc to know more about using Composer to create Symfony application.

Start the integrated Symfony server:

symfony serve -d
Enter fullscreen mode Exit fullscreen mode

The -d option is used to run the server as a daemon, in the background. To stop it, you can run:

symfony server:stop
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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:

Symfony Welcome Page

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
Enter fullscreen mode Exit fullscreen mode

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');
    }
}
Enter fullscreen mode Exit fullscreen mode

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 %}
Enter fullscreen mode Exit fullscreen mode

We just extends the base.html.twig file and customize the content in the body block.
Refresh your page and voilΓ  πŸŽ‰

Hello from Symfony

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
Enter fullscreen mode Exit fullscreen mode

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!πŸ‘‹');
    }
}
Enter fullscreen mode Exit fullscreen mode

This simple test can help us check everything works in our application.

Run the tests:

./bin/phpunit
Enter fullscreen mode Exit fullscreen mode

PHPUnit tests

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:
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

There is the compose.prod.yaml content, for our prod env:

services:
  php:
    build:
      context: .
      target: frankenphp_prod
    environment:
      APP_SECRET: ${APP_SECRET}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 "$@"
Enter fullscreen mode Exit fullscreen mode

Last thing, we need to add a library to handle the worker mode for FrankenPHP, install it with composer:

composer require runtime/frankenphp-symfony
Enter fullscreen mode Exit fullscreen mode

Don't forget to add his associated configuration (frankenphp/worker.Caddyfile):

worker {
    file ./public/index.php
    env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
}
Enter fullscreen mode Exit fullscreen mode

Congratulations, you now have dockerized your Symfony application πŸ‘

You can now build your images:

docker compose build --no-cache
Enter fullscreen mode Exit fullscreen mode

Start your containers in dev mode:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

And in prod:

docker compose -f compose.yaml -f compose.prod.yaml up -d
Enter fullscreen mode Exit fullscreen mode

If you have some troubles, you can check the docker logs:

docker compose logs -f
Enter fullscreen mode Exit fullscreen mode

You can also run again your tests in your Docker container and make sure they still pass:

docker compose exec php bin/phpunit
Enter fullscreen mode Exit fullscreen mode

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)