R4.02 - Continuous Integration of a Symfony project with GitLab
Table of ContentsClose
1 Setting up a Symfony project and its development environment
1.1 Let's start with a new Symfony project
symfony new gitlab-symfony --full
cd gitlab-symfony
1.2 Prepare a Docker environment to deploy on your local machine
mkdir docker cd docker # git clone or copy the content of https://gitlab-ce.iut.u-bordeaux.fr/Pierre/gitlab-symfony/-/tree/master/docker
Let's adapt the environment to our project by modifying the docker-compose.yml
file.
version: "3.8" services: db_gitlab: image: mysql container_name: db_docker_gitlab restart: always volumes: - db-data:/var/lib/mysql environment: MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' networks: - dev www_gitlab: build: php container_name: www_docker_gitlab ports: - "8080:80" volumes: - ./php/vhosts:/etc/apache2/sites-enabled - ../:/var/www restart: always networks: - dev networks: dev: volumes: db-data:
We need the following containers:
- A MySQL container: you will have noticed the use of the
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
option, which allows us to indicate that we allow accounts without passwords. This will simplify the use of the root account when you customize your Symfony.env
files. This container refers thedev
network and use thedb-data
volume for the storage of the database. - A PHP Apache container: This container is
built
from a Dockerfile available inphp
subdirectory. You will also have noticed that, in the volumes, we map a./php/vhosts
directory into the container (at the same for the website directory/var/www
). This allows use to update the content or edit files outside from the container. This container also refer to thedev
network. - If you want, you can also add a PHPMyAdmin container with:
phpmyadmin_gitlab: image: phpmyadmin container_name: phpmyadmin_docker_gitlab restart: always depends_on: - db_gitlab ports: - 8080:80 environment: PMA_HOST: db networks: - dev
Warning: Docker is not available at IUT department, please replace docker with podman in the following, for all the instructions related to containers on your local machine. But, of course, keep docker for instructions related to containers within Gitlab-CI.
Note: Thanks to the volumes that are managed by OverlayFS via Fuse, it is possible to use the
-v
option with podman, in the same way as with docker.
The vhosts.conf
file need to be updated :
<VirtualHost *:80> ServerName localhost DocumentRoot /var/www/public DirectoryIndex /index.php <Directory /var/www/public> AllowOverride None Order Allow,Deny Allow from All FallbackResource /index.php </Directory> <Directory /var/www/public/bundles> FallbackResource disabled </Directory> ErrorLog /var/log/apache2/project_error.log CustomLog /var/log/apache2/project_access.log combined </VirtualHost>
1.3 Let's start the full stack
From the docker/
subdirectory:
docker-compose up -d
And let's check that everything works by going to http://127.0.0.1:8080/
1.4 Prepare the database with Symfony
First you have set the database in .env
file:
DATABASE_URL="mysql://root:@db_gitlab:3306/db_name?serverVersion=5.7"
Let's create the database from the Symfony CLI. To simplify the next commands, we will enter the shell of the www_docker_gitlab
container.
Warning: In the following, all commands beginning with
> /var/www#
mean that they must be executed inside the containerwww_docker_gitlab
.
docker exec -it www_docker_gitlab bash > /var/www# php bin/console doctrine:database:create
Let's set up a Demo entity with a demo field of type string
(and all the default values proposed by Symfony), create the associated migration and then run the migration.
> /var/www# php bin/console make:entity Demo > /var/www# php bin/console make:migration > /var/www# php bin/console doctrine:migrations:migrate
2 Setting up a GitLab repository and first commit
Symfony already initialize a Git repository. After setting up an empty project in GitLab, add the remote origin (replace the address with your own project):
# git init git remote add origin git@gitlab.com:ramet/gitlab-symfony.git git add . git commit -m "Initial commit" git push -u origin master
2.1 Write a first unit test named UnitTest
> /var/www# php bin/console make:unit-test
Let's edit the tests/UnitTest.php
file and add a simple unit test:
<?php namespace App\Tests; use App\Entity\Demo; use PHPUnit\Framework\TestCase; class UnitTest extends TestCase { public function testDemo() { $demo = new Demo(); $demo->setDemo('demo'); $this->assertTrue($demo->getDemo() === 'demo'); } }
2.2 Test to ensure that it works
> /var/www# php bin/phpunit
2.3 Commit this first unit test
git add .
git commit -m "add unit test"
git push
3 Configure the pipeline
Using the docker image jakzal/phpqa:php8.1
from Static Analysis Tools for PHP, we want to set up the following steps:
- Check for security flaws in dependencies with Security Checker
- Check the code
style
with PHP CS - Run a static analysis with PHP Stan
- Run a static analysis of
Twig
files with Twig-lint - Run our unit tests with PHP Unit
3.1 Prepare the pipeline for static analysis
Here is a template for .gitlab-ci.yml
file:
image: jakzal/phpqa:php8.1 before_script: - composer install cache: paths: - vendor/ stages: - SecurityChecker - CodingStandards - UnitTests security-checker: stage: SecurityChecker script: - local-php-security-checker --path=./composer.lock allow_failure: false phpcs: stage: CodingStandards script: - phpcs -v --standard=PSR12 --ignore=./src/Kernel.php ./src allow_failure: false phpstan: stage: CodingStandards script: - phpstan analyse ./src allow_failure: false twig-lint: stage: CodingStandards script: - twig-lint lint ./templates allow_failure: false phpunit: stage: UnitTests script: - php bin/phpunit allow_failure: false
3.2 Commit this first pipeline
git add .
git commit -m "Ajout de la pipeline"
git push
4 Add functional tests with interaction with the database
The first thing we need to do is to set up the functional tests locally in our Symfony project.
We need to define which database is used for testing.
Let's copy the .env.test
file to .env.test.local
(and this one should not be committed!).
cp .env.test .env.test.local
4.1 Edit the .env.test.local
file
# .env.test.local KERNEL_CLASS='App\Kernel' APP_SECRET='$ecretf0rt3st' SYMFONY_DEPRECATIONS_HELPER=999999 PANTHER_APP_ENV=panther DATABASE_URL="mysql://root:@db_gitlab:3306/db_test?serverVersion=5.7"
4.2 Let's create the test
database, and play the migrations
> /var/www# php bin/console doctrine:database:create --env=test > /var/www# php bin/console doctrine:migrations:migrate --env=test
4.3 Let's generate a CRUD (Demo entity) to have something to test
> /var/www# php bin/console make:crud
And let's check that everything works by going to http://127.0.0.1:8080/demo/
4.4 Let's generate a functional test named FunctionalTest
> /var/www# php bin/console make:functional-test
4.5 Edit the tests/FunctionalTest.php
file
Add a first test that checks that the /demo
url is responding:
<?php namespace App\Tests; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class FunctionalTest extends WebTestCase { public function testShouldDisplayDemoIndex() { $client = static::createClient(); $client->followRedirects(); $crawler = $client->request('GET', '/demo'); $this->assertResponseIsSuccessful(); $this->assertSelectorTextContains('h1', 'Demo index'); } }
Let's add a second test that loads the new
form:
public function testShouldDisplayCreateNewDemo() { $client = static::createClient(); $client->followRedirects(); $crawler = $client->request('GET', '/demo/new'); $this->assertResponseIsSuccessful(); $this->assertSelectorTextContains('h1', 'Create new Demo'); }
Finally, let's add a test that will insert data via the form, and then check that it exists:
public function testShouldAddNewDemo() { $client = static::createClient(); $client->followRedirects(); $crawler = $client->request('GET', '/demo/new'); $buttonCrawlerNode = $crawler->selectButton('Save'); $form = $buttonCrawlerNode->form(); $uuid = uniqid(); $form = $buttonCrawlerNode->form([ 'demo[demo]' => 'Add Demo For Test' . $uuid, ]); $client->submit($form); $this->assertResponseIsSuccessful(); $this->assertSelectorTextContains('body', 'Add Demo For Test' . $uuid); }
4.6 Test to ensure that it works
> /var/www# php bin/phpunit
4.7 Update the section phpunit:
in the .gitlab-ci.yml
file
phpunit: image: php:8.1-apache stage: UnitTests services: - name: mysql:5.7 alias: mysql variables: MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' #MYSQL_ROOT_PASSWORD: pass_test MYSQL_DATABASE: myapptest #MYSQL_USER: myapptest #MYSQL_PASSWORD: myapptest #DATABASE_URL: 'mysql://myapptest:myapptest@mysql:3306/myapptest' DATABASE_URL: 'mysql://root:@mysql:3306/myapptest' before_script: - apt-get update && apt-get install -y git libzip-dev - curl -sSk https://getcomposer.org/installer | php -- --disable-tls && mv composer.phar /usr/local/bin/composer - docker-php-ext-install mysqli pdo pdo_mysql zip - php bin/console doctrine:database:drop --force --if-exists --env=test - php bin/console doctrine:database:create --env=test - php bin/console doctrine:migration:migrate --env=test --no-interaction script: - php bin/phpunit allow_failure: false
4.8 Edit the .env.test
file
Let's indicate to Symfony to use the test
database in the context of the tests:
# .env.test KERNEL_CLASS='App\Kernel' APP_SECRET='$ecretf0rt3st' APP_ENV=test SYMFONY_DEPRECATIONS_HELPER=999999 PANTHER_APP_ENV=panther # DATABASE_URL="mysql://myapptest:myapptest@mysql:3306/myapptest" DATABASE_URL="mysql://root:@mysql:3306/myapptest"
4.9 Commit this new pipeline
git add . git commit -m "add functional tests" git push
Warning: For some reason, the access to the
test
database does not work with the myapptest account, so I use the root account instead.Note: You might have a
/src/Controller/DemoController.php
file that doesn't pass the PHP CS PSR-12 check. Make the modification reported (missing spaces around a "." on line 72) and do acommit + push
again.