Split my Symfony 4 application

Split my Symfony 4 application into multiple libraries

Recently at work I had to split the code of one of our Symfony application for multiple reasons. But because Symfony 4 is Bundle-less, I had to find an other way to split.

A quick tour of my existing project

Initially I had my files organized by following the best practices of Symfony. It looked like this :

.
├── composer.json
├── config
├── src
│   ├── Command
│   │   ├── Part1
│   │   └── Part2
│   ├── Controller
│   │   ├── Part1
│   │   └── Part2
│   ├── Entity
│   │   ├── Part1
│   │   └── Part2
│   ├── Repository
│   │   ├── Part1
│   │   └── Part2
│   └── Service
│       ├── Part1
│       └── Part2
└── templates

As you can see, all the code is already spited but there are many issues :

  • the files are not totally spitted they just leave next to each other
  • there is often a merge conflict on the configuration files
  • I can't remove easily one part of my project
  • the dependencies are all mixed together (unique composer.json, ...)

Moving the files

So I've created a new folder libraries, and I'll put all my libraries in it. It looks like that:

.
├── composer.json
├── config
├── libraries
│   ├── Part1
│   │   ├── config
│   │   ├── src
│   │   │   ├── Command
│   │   │   ├── Entity
│   │   │   ├── Repository
│   │   │   ├── Service
│   │   │   └── composer.json
│   └── Part2
│   │   ├── config
│   │   ├── src
│   │   │   ├── Command
│   │   │   ├── Entity
│   │   │   ├── Repository
│   │   │   ├── Service
│   │   │   └── composer.json
├── src
│   ├── Controller
│   │   ├── Part1
│   │   └── Part2
└── templates

This migration is quite easy. You just have to move the files and update the namespace to match the new tree structure. For example, this old command:

<?php

namespace App\Command\Part1;

public class MyCommand {}

Will became :

<?php

namespace Part1\Command;

public class MyCommand {}

In each composer.json of the libraries, I've put the exact dependencies for the concerned librairy.

Here is the composer.json for my part1 library :

{
  "name": "my-project/part1",
  "description": "A library to do awesome things",
  "type": "library",
  "license": "proprietary",
  "minimum-stability": "dev",
  "prefer-stable": true,
  "autoload": {
    "psr-4": {
      "Part1\\": "src/"
    }
  },
  "require": {
    "php": "^7.1.3",
    ...
  },
  ...
}

Then the composer.json at the root of my project is almost empty, I just have to import my brand new libraries :

{
    "name": "my-project/core",
    "type": "project",
    "license": "proprietary",
    "require": {
        "my-project/part1": "*@dev",
        "my-project/part2": "*@dev",
    },
    "repositories": [
        {
            "type": "path",
            "url": "libraries/*"
        }
    ],
    ...
}

The magic happen in the repositories section. It said that there are new libraries in my library folder.

Then we need to configure our Symfony application !

Update the Symfony configuration files

Like I said in the beginning of this post, I want to separate my configurations, so I can remove the old ones quite easily. In my config/packages/ folder I've add a part1.yaml and a part2.yaml file which define all the configuration for my lib. For example here is my part1.yaml:

services:
    Part1\:
        resource: '%kernel.project_dir%/libraries/part1/src/*'
        exclude: '%kernel.project_dir%/libraries/part1/src/{Command,DataFixtures,DependencyInjection,Entity,Migrations,Repository,Tests,Kernel.php}'
        autowire: true

    # I have to redefined all that things, otherwise our application can't see them
    Part1\Repository\:
        resource: '%kernel.project_dir%/libraries/part1/src/Repository/*'
        autowire: true
        tags:
            - 'doctrine.repository_service'
    Part1\DataFixtures\:
        resource: '%kernel.project_dir%/libraries/part1/src/DataFixtures/*'
        autowire: true
        tags:
            - 'doctrine.fixture.orm'
    Part1\Command\:
        resource: '%kernel.project_dir%/libraries/part1/src/Command/*'
        autowire: true
        tags:
            - 'console.command'

# Then overload the services you need, here it's for doctrine but you can do it for everything
doctrine:
    orm:
        mappings:
            Part1:
                is_bundle: false
                type: annotation
                dir: '%kernel.project_dir%/libraries/part1/src/Entity'
                prefix: 'Part1\Entity'
                alias: Part1

I've left the Controllers in my main src/ folder because for me belong here. And that's it ! Your application is now really spitted.

Tags: symfony, php, library

docker-compose + self hosting

How to self host EASILY many things on you own server

I'm new in the hosting world, but I know that self hosting could be very painful if it's not done properly. So I wanted to build a system that prevent (some) failure and I didn't want to have my configuration files and my applications all over my server system. So I've decided to use Docker with docker-compose to make my life easier.

With that said, let's dig in !

The reverse proxy

Because I wanted to have many applications on my server, I have to set up a reverse proxy. My flatmate said to me: "Just grab an nginx and it'll be easy". After some hours of configuration and an epic battle with the cert-bot in order to get my SSL certificates, I've stopped. It was definitely not the easy way.

After few research I've found a nginx reverse proxy with a cert-bot and the promises of easy deployment. So I've tried. And it was easy. Not easy, but VERY VERY EASY to use. Let's look at my `reverse-proxy/docker-compose.yml':

version: '2'

services:
  nginx-proxy:
    image: jwilder/nginx-proxy:alpine
    ports:
      - "80:80"
      - "443:443"
    networks:
      - proxy
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - /etc/nginx/certs:ro
      - /etc/nginx/vhost.d
      - /usr/share/nginx/html
    restart: always
  ssl-compagnion:
    image: jrcs/letsencrypt-nginx-proxy-companion:v1.9
    networks:
      - proxy
    volumes:
      - /etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
    volumes_from:
      - nginx-proxy
    restart: always

networks:
  proxy:

Well, just basic stuff. I've just declare a new network called proxy so I can call it in other docker-compose.yml. I enter docker-compose up -d in my terminal and my reverse-proxy is running without almost any configuration !

Miniflux

I've just wanted to have my own RSS aggregator. I've found one written in go with a good documentation: miniflux. Here is my docker-compose.yml.

version: '3'

services:
  app:
    image: miniflux/miniflux:2.0.11
    expose:
      - "8080"
    depends_on:
      - db
    networks:
      - proxy
      - miniflux
    environment:
      - DATABASE_URL=postgres://XXXXXXXXXXXXXXXXXX:XXXXXXXXXXXXXXXXXX@db/miniflux?sslmode=disable
      - RUN_MIGRATIONS=1
      - VIRTUAL_HOST=rss.livda.fr
      - VIRTUAL_PORT=8080
      - LETSENCRYPT_HOST=rss.livda.fr
      - LETSENCRYPT_EMAIL=mail@gmail.com
    restart: always
  db:
    image: postgres:10.2
    environment:
      - POSTGRES_USER=XXXXXXXXXXXXXXXXXX
      - POSTGRES_PASSWORD=XXXXXXXXXXXXXXXXXX
    networks:
      - miniflux
    restart: always

networks:
  miniflux:
  proxy:
    external:
      name: reverse-proxy_proxy

The only thing that is not present in the miniflux documentation are those environment variables:

  • VIRTUAL_HOST=rss.livda.fr
  • VIRTUAL_PORT=8080
  • LETSENCRYPT_HOST=rss.livda.fr
  • LETSENCRYPT_EMAIL=mail@gmail.com

They are here for the reverse proxy and it's companion. The VIRTUAL_PORT is optional if your container expose the port 80.

With all of that, I've just run docker-compose up -d in the miniflux folder, I've edited my DNS record to make rss.livda.fr goes to my public IP and ... It worked ! That's all.

All my other applications are exposed like that, so I don't have to manager my SSL certificates, neither my nginx configuration with multiple vhost. I just put 3 or 4 environment variables, and my new application/website is online with a nice HTTPS url.

Tags: docker-compose, reverse-proxy, ssl