TL;DR: Symfony is a PHP framework as well as a set of reusable PHP components and libraries. It uses the Model-View-Controller design pattern and can be scaled to be used in any requirement. It aims to speed up the creation and maintenance of web applications, replacing repetitive code. In this part of the series, you will learn how to deploy the blog engine that you have created in the previous parts on Heroku. The final code can be found at this repository.


Symfony Tutorial: About Part 1 and Part 2

In the first article, you:

  • installed and configured a Symfony installation;
  • created two entities (and their respective database tables): Author and BlogPost;
  • allowed users to authenticate with Auth0;
  • and ensured that the authenticated users have Author instances associated with them before using the system.

In the second article, you installed Bootstrap, a UI framework for web applications, to make the blog engine look nicer visually. You also enhanced your blog engine to allow visitors to:

  • see a list of blog posts;
  • read a specific blog post;
  • and find out more about authors.

Besides that, authenticated authors were able to:

  • create a new blog post;
  • see all of their own blog posts;
  • and delete their own blog posts from the system.

In this third article, you will be learning about deploying to two different environments: staging and production. You will be carrying this out by using two services, Heroku and Travis-CI, which will allow you to:

  • make code changes to the blog;
  • deploy these changes automatically by committing them to GitHub;
  • and almost instantly see their changes in production and/or staging environments;

About Heroku

Heroku is a container-based cloud Platform as a Service (PaaS). Heroku is used to deploy, manage, and scale modern apps for developers. The service is very flexible, simple to use, and quick to start with. This allows developers to focus more on the product rather than spending too much time on the technology. Added to this, Heroku is fully managed, this further reduces the time developers need to spend on maintaining servers and the hardware of the infrastructure.

About Travis CI

Travis CI is a hosted and distributed continuous integration service. Travis CI allows you to build and test your software hosted on GitHub, allowing you to publicise projects comfortable in knowing that project is fully functional for others to use.

With Travis CI, there are two different sites to use. The travis-ci.org site allows you test open-source projects (public repositories). Whereas travis-ci.com allows you to test private projects but requires a fee dependent on the size of your project.

Building the Blog Engine

Before Starting

For this tutorial, you will need a GitHub account. So, if needed, head over to sign up at GitHub and complete the process.

Once signed up, go to the example repository for the Symfony blog part 2 article and click on the "Fork" button that appears in the top right-hand corner of the browser. As shown in the example below:

Forking a repository on GitHub

You will find that you now have a repository of the same name but under your account. Click the "Clone or download" button and copy the URL found in there.

Next, you will need to clone this repository for yourself, so run the following two commands. However, replace the URL with your own one:

git clone https://github.com/auth0-blog/symfony-blog-part-2

After cloning it, install the dependencies with the following command:

cd symfony-blog-part-2
composer install

In the root directory, you will find a file called .env. Update the following values with your Auth0 credentials:

AUTH0_CLIENT_ID={AUTH0_CLIENT_ID}
AUTH0_CLIENT_SECRET={AUTH0_CLIENT_SECRET}
AUTH0_DOMAIN={AUTH0_DOMAIN}

Note that you will have to replace the values above ({AUTH0_CLIENT_ID}, {AUTH0_CLIENT_SECRET}, and {AUTH0_DOMAIN}). Check the first part to understand how to replace them.

Pro Tip! You will need a MySQL database locally to test your application. So, if you do not have one, an easy way to bootstrap it is with Docker:

docker run --name symfony-blog-mysql \
    -p 3306:3306 \
    -e MYSQL_ROOT_PASSWORD=myextremellysecretpassword \
    -e MYSQL_DATABASE=symfony-blog \
    -e MYSQL_USER=symfony-blog-user \
    -e MYSQL_PASSWORD=mysecretpassword \
    -d mysql:5.7

Now that you have a database with some credentials, in your .env file, find:

###> doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Configure your db driver and server_version in config/packages/doctrine.yaml
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
###< doctrine/doctrine-bundle ###

And change the following line: DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name to be correct. If you were to use the example details in the creation of the docker database, it would look like:

DATABASE_URL=mysql://symfony-blog-user:mysecretpassword@127.0.0.1:3306/symfony-blog

Also, add a new line with the contents below (and replace (Your database name) with the name of your database):

DATABASE_NAME=(Your database name)

If you haven't followed the first part of this series, you might need to issue the following commands to create the database tables and to populate them:

php bin/console doctrine:database:create
php bin/console doctrine:schema:update --force
php bin/console doctrine:fixtures:load

If you get an error stating that An exception occurred in driver: SQLSTATE[HY000] [2002] No such file or directory, check your .env file and replace DATABASE_HOST=localhost by DATABASE_HOST=127.0.0.1.

NOTE: If you do not have Yarn installed (a JavaScript package manager), you will need to install and configure this. So, go to their installation page and follow the instructions for installing and configuring Yarn first.

Lastly, you will need to install third-party libraries (used to make the blog look nicer visually) with the following command:

yarn install
yarn run encore dev

Installing Dependencies

Installing Heroku CLI

In order to use Heroku, you need to have an account. So, go to the sign up page and follow all instructions provided there.

Once your account has been created, you're going to need Heroku's Command Line Interface (CLI). Each operating system has a different method of installing the Heroku CLI. So, below is a list of the commands used to install this:

  • on MacOS: brew install heroku/brew/heroku
  • on Ubuntu Snap: sudo snap install heroku --classic
  • on Debian / Ubuntu: wget -qO- https://cli-assets.heroku.com/install-ubuntu.sh | sh
  • on Windows 64-bit: Installer
  • on Windows 32-bit: Installer

Now that you have an account and have the CLI installed on your system, you're going to need to create a space on Heroku. Running the following command will do this:

NOTE: Please change the space-name-here to be the space name you wish. Otherwise, you will find that the space name space-name-here is already in use by someone else.

heroku apps:create space-name-here

Creating a Heroku space

Great, you can now access your website. If you take the URL that's given to you as a result of your command above (similar to the previous image) and put it into your browser, you should see the example page as shown in the image below:

Heroku space created

Now that you have your space, you need to create your database. There are several different add-ons for Heroku in order to use a database in your package. However, although there are free plans, you still need to provide your card details. So, please, head over to Heroku Verify to add your card details.

Once verified, you're going to add the ClearDb add-on to your space. So, in the terminal, run the following command to add a database to support your blog engine:

heroku addons:add cleardb:ignite

When the command has finished running, you should see something similar to the image below:

Creating a ClearDB database

Heroku stores the environment variables themselves. So, in order for your blog to use sensitive keys (such as the database credentials and Auth0 properties), you need to update these variables. Previously, you stored them in a .env file. Now, you will need to execute the following commands to populate Heroku with these variables:

heroku config:set APP_ENV=prod
heroku config:set SYMFONY_ENV=prod
heroku config:set AUTH0_CLIENT_ID=(Your Auth0 Client ID)
heroku config:set AUTH0_CLIENT_SECRET=(Your Auth0 client secret)
heroku config:set AUTH0_DOMAIN=(Your Auth0 Domain)
heroku config:set DATABASE_NAME=(Your database name, as shown by the heroku addons:add command)

Note: You will need to replace the values after AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, and AUTH0_DOMAIN with the properties of your Auth0 application and DATABASE_NAME with the CLEARDB_DATABASE_URL provided by the Heroku CLI (something like cleardb-regular-32216).

Installing Travis CI

Travis CI can be installed via Ruby. If you have Ruby installed, please, make sure it is at least version 1.9.3 (2.0.0 recommended). You can check this by typing the following command in a terminal:

ruby -v

If Ruby isn't installed, it can be installed by one of the following methods, depending on your operating system:

  • Mac OS X: brew install ruby
  • Debian / Ubuntu: sudo apt-get install ruby-full
  • Free bsd: pkg install ruby
  • CentOS, Fedora, or RHEL: sudo yum install ruby

Now that you have Ruby, you will be able to install Travis CI CLI with the following command:

gem install travis -v 1.8.8 --no-rdoc --no-ri

Once complete, the installation can be verified by checking the version with the following command:

travis version

Making use of Travis CI requires a GitHub account. As you already created your account previously, you don't need to create another. So, head over to Travis CI to sign up.

Once your account is created, you should see your profile page. This page contains the list of your GitHub repositories (you might need to click on the Sync account button). All you need to do is to turn on your specific repository.

Turning Travis CI integration with GitHub on

In order to run through the Travis CI build, you need a .env file specially created to this service. So, in the root directory of your project, create a .env.travis.dist file and paste the following in:

DATABASE_URL=mysql://travis@127.0.0.1:3306/travisbuild
DATABASE_NAME=travisbuild
CLEARDB_DATABASE_URL=
AUTH0_CLIENT_ID=
AUTH0_CLIENT_SECRET=
AUTH0_DOMAIN=

You have enabled your GitHub repository on Travis CI, but you still need to create a .travis.yml file so that Travis CI knows what to do with your repository. So, create the file and put the following in:

language: php
sudo: false
services:
- mysql
cache:
  directories:
  - "$HOME/.composer/cache/files"
matrix:
  fast_finish: true
  include:
  - php: 7.1
env:
- SYMFONY_VERSION="4.0.*" DB=mysql
before-install:
- composer self-update
install:
- cp .env.travis.dist .env
- composer install
- php bin/console doctrine:database:create --env=test
- php bin/console doctrine:schema:create --env=test
notifications:
email:
deploy:
  provider: heroku
  api_key:
    secure:
  app:

There are 3 parts of this file where you need to input unique details:

  • First, your e-mail address. Please, type in your own e-mail next to the line that starts with email:.
  • Second, your secure api_key for Heroku. If you run the following command, it'll put the encrypted Heroku API key for you:
travis encrypt $(heroku auth:token) --add deploy.api_key
  • And finally, the app your Heroku refers to, this is your Heroku space id. So, please, type it in next to app: which is at the bottom of the file.

Configuring Symfony for Production

Now that you have set up and configured Heroku and Travis CI, you need to make some changes to your Symfony project to make it functional when hosted on Heroku.

First, you will need to change your composer.json file. On this file, you need to add a compile command that will tell Heroku to update the schema of the database. Where it says:

"auto-scripts": {
    "cache:clear": "symfony-cmd",
    "assets:install --symlink --relative %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
    "@auto-scripts"
],
"post-update-cmd": [
    "@auto-scripts"
],

Add the compile entry at the bottom as shown below:

"auto-scripts": {
    "cache:clear": "symfony-cmd",
    "assets:install --symlink --relative %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
    "@auto-scripts"
],
"post-update-cmd": [
    "@auto-scripts"
],
"compile": [
    "php bin/console cache:warmup",
    "php bin/console doctrine:schema:update --force",
    "yarn install",
    "yarn run encore production"
]

After that, you need to exclude your DataFixtures from the services auto-wiring. So, open the ./config/services.yaml file and find:

App\:
    resource: '../src/*'
    exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'

In exclude, where it shows {Entity,Repository,Tests}, add DataFixtures. This will look like:

App\:
    resource: '../src/*'
    exclude: '../src/{Entity,Migrations,Tests,Kernel.php,DataFixtures}'

Your Doctrine configuration will be different in production than it is in development. So, in the config/packages/doctrine.yaml file, you should find something similar to:

doctrine:
    dbal:
        # configure these for your database server
        driver: 'pdo_mysql'
        server_version: '5.7'
        charset: utf8mb4

        # With Symfony 3.3, remove the `resolve:` prefix
        url: '%env(resolve:DATABASE_URL)%'
    orm:
        auto_generate_proxy_classes: '%kernel.debug%'
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                type: annotation
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App

Replace this line:

url: '%env(resolve:DATABASE_URL)%'

With:

dbname: '%env(DATABASE_NAME)%'

Then, create a new file called doctrine.yaml in the config/packages/dev directory and paste the following configuration into it:

doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'

After that, open the doctrine.yaml file (it resides on config/packages/prod) and, under Doctrine:, add:

dbal:
    url: '%env(resolve:CLEARDB_DATABASE_URL)%'

After update Doctrine configurations, you will need to install the Monolog Bundle to see Heroku's logs. This can be done by running the following command:

composer require logger

Now, alter the monolog configuration found in config/packages/prod/monolog.yaml. In this file, there will be something similar to:

monolog:
    handlers:
        main:
            type: fingers_crossed
            action_level: error
            handler: nested
            excluded_404s:
                # regex: exclude all 404 errors from the logs
                - ^/
        nested:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            level: debug
        console:
            type:   console
            process_psr_3_messages: false
            channels: ["!event", "!doctrine"]

Replace the line containing path: "%kernel.logs_dir%/%kernel.environment%.log" with:

path: 'php://stderr'

In order for travis-ci to run, it needs to be able to run tests. Although you are not using PHPUnit tests, you will need to install the package. So, run the following command:

composer require phpunit-bridge

With the new version of Symfony, Heroku doesn't know where the public directory is (it used to be ./web/ but moved to ./public/). Therefore, to make Heroku aware of the public directory, you need to create a new file called Procfile in the root of your project directory. In this file add the following line:

web: vendor/bin/heroku-php-apache2 public/

Also, you will need a .htaccess file. So, run the following command:

composer require apache-pack

In order for Heroku to know what NPM packages to include in your new space, you need to specify which build packs to use. Note that Node.js is required to run yarn to build the styling of the blog.

heroku buildpacks:add heroku/nodejs
heroku buildpacks:add heroku/php

Heroku's spaces are hosted behind reverse proxies and load balancers which cause problems with your application. These reverse proxies may mask whether you're accessing the browser via HTTP or HTTPS for example. As a result of this, you need to add the following code in your ./public/index.php file below the $request = Request::createFromGlobals(); line:

// tell Symfony about your reverse proxy
Request::setTrustedProxies(
    array($request->server->get('REMOTE_ADDR')),
    Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST
);

It's time to commit all of your changes to your forked repository. This can be done by the following commands:

git add .
git commit -m "Added file changes to prepare for pushing to production"
git push

In order to see your blog via the URL of your space hosted on Heroku, you will need to update your Auth0 Application to accept callbacks from this URL.

So, Head over to the Auth0 Dashboard, choose your production application and carry out the following instructions:

  • In the production Auth0 application, go to the settings tab.
  • Find the text box labeled Allowed Callback URLs.
  • The url you need to put in the text box is your space url, for example: https://space-name-here.herokuapp.com/ followed by: auth0/callback. So it will look like: https://space-name-here.herokuapp.com/auth0/callback.
  • Click the Save Changes button at the bottom of the page

You will now be able to read articles on your blog and authenticate as a writer.

Instructions for Setting Up the Staging Environment

If you wish to make use of a staging environment for testing on, you are going to need to repeat some of the steps above. A staging environment allows you to make use of a direct replica of the production server, providing an environment for those with access to test new features before they're ready to be published for the public.

First, create your staging space on Heroku with the following command:

heroku apps:create space-name-here-staging

If previously you called your space space-name-here, then just append -staging to the end. This will facilitate differentiating what is staging and what is production.

Next, you are going to need to create a new database entry for this space. You can do this by running the following command:

NOTE: You may need to define your space name in this command, so change the space-name-here-staging to your space name below

heroku addons:add cleardb:ignite -a space-name-here-staging

Now, you need to change the travis.yml file. Where you previously had:

  app: space-name-here

You will need to replace this with branch specific configurations:

  app:
      master: space-name-here
      staging: space-name-here-staging

After that, you are going to need to create a staging area on Auth0, log into your account here, then click the "Create Application" button. You can call it whatever you want. I would suggest calling it the same as your current application but append "-staging" to the end of it.

Once created, you need to make use of the Auth0 client Id, secret, and domain. You also need to make use of the database name returned to you when you ran the cleardb:ignite command. Replace the contents of the brackets in the examples below with these details. Then, run each of these commands:

heroku buildpacks:add heroku/nodejs -a space-name-here-staging
heroku buildpacks:add heroku/php -a space-name-here-staging
heroku config:set APP_ENV=staging -a space-name-here-staging
heroku config:set SYMFONY_ENV=staging -a space-name-here-staging
heroku config:set AUTH0_CLIENT_ID=(Your Auth0 Client ID) -a space-name-here-staging
heroku config:set AUTH0_CLIENT_SECRET=(Your Auth0 client secret) -a space-name-here-staging
heroku config:set AUTH0_DOMAIN=(Your Auth0 Domain) -a space-name-here-staging
heroku config:set DATABASE_NAME=(Your database name shown in the image above) -a space-name-here-staging

In the above commands, you've set the SYMFONY_ENV to staging. Now, you need some files in config/packages/staging to store your staging configurations. Create the staging directory under ./config/packages/ and, within that directory, create the doctrine.yaml file and paste the following in:

doctrine:
    dbal:
        url: '%env(resolve:CLEARDB_DATABASE_URL)%'
    orm:
        metadata_cache_driver:
            type: service
            id: doctrine.system_cache_provider
        query_cache_driver:
            type: service
            id: doctrine.system_cache_provider
        result_cache_driver:
            type: service
            id: doctrine.result_cache_provider

services:
    doctrine.result_cache_provider:
        class: Symfony\Component\Cache\DoctrineProvider
        public: false
        arguments:
            - '@doctrine.result_cache_pool'
    doctrine.system_cache_provider:
        class: Symfony\Component\Cache\DoctrineProvider
        public: false
        arguments:
            - '@doctrine.system_cache_pool'

framework:
    cache:
        pools:
            doctrine.result_cache_pool:
                adapter: cache.app
            doctrine.system_cache_pool:
                adapter: cache.system

Then, in the same directory, create the file monolog.yaml and paste the following in:

monolog:
    handlers:
        main:
            type: fingers_crossed
            action_level: error
            handler: nested
            excluded_404s:
                # regex: exclude all 404 errors from the logs
                - ^/
        nested:
            type: stream
            path: 'php://stderr'
            level: debug
        console:
            type:   console
            process_psr_3_messages: false
            channels: ["!event", "!doctrine"]

Now that you have your environment set up, you need to run the following commands to create and deploy the staging branch:

git add .
git commit -m "Preparing the staging environment"
git checkout -b staging
git push --set-upstream origin staging

Time to add the staging URL to the allowed callback URLs for your staging application. Head over to the Auth0 Dashboard, choose your staging application and carry out the following instructions:

  • In the staging Auth0 application, go to the settings tab.
  • Find the text box labelled Allowed Callback URLs.
  • The URL you need to put in the text box is your space URL, for example: https://space-name-here-staging.herokuapp.com/ followed by: auth0/callback. So it will look like: https://space-name-here-staging.herokuapp.com/auth0/callback.
  • Click the Save Changes button at the bottom of the page

You will now be able to test your application on a staging environment. So, if you head over to your Heroku dashboard, you should see the build in progress or complete. After this build is completed, you can browse to the URL for your staging environment and you will see an exact duplicate of the production environment.

Conclusion

The deployment process is a very broad subject. It can also be very daunting for someone who hasn't had any exposure to this part of development. Hopefully, this article will have given you a footstep into the world of deploying PHP applications.

With this article, you should have the foundation for creating, designing, and deploying the PHP application you always wanted!