Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Handlers: For Handling Serious Biz

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

Nginx is setup! But we need to restart or at least reload Nginx for our site to, ya know, actually work. Surprise! There's a module for that... called service. This module is all business: give it the name of the service and the state you want, like started or restarted.

So we should add a new task to restart Nginx, right? Wait, no! Ansible has a different option... a more awesome option.

Hello Handlers

At the bottom of the playbook, add a new section called handlers:

---
- hosts: vb
... lines 3 - 8
tasks:
... lines 10 - 142
handlers:
... lines 144 - 155

A handler is written just like any task: give it a name - "Restart Nginx" - use become: true, and then choose the module we want: service. Set the name to nginx and state to restarted:

---
- hosts: vb
... lines 3 - 8
tasks:
... lines 10 - 142
handlers:
- name: Restart Nginx
become: true
service:
name: nginx
state: restarted
... lines 149 - 155

Tip

We could also just reload Nginx - that's enough for most changes.

Here's the deal with handlers: unlike tasks, they are not automatically called. Nope, instead, you find a task - like the task where we create the symbolic link to sites-enabled and, at the bottom, add notify: Restart Nginx:

---
- hosts: vb
... lines 3 - 8
tasks:
... lines 10 - 54
- name: Enable Symfony config template from Nginx available sites
... lines 56 - 60
notify: Restart Nginx
... lines 62 - 155

Now, when this task runs, it will "notify" the "Restart Nginx" handler so that it will execute. Actually, that's kind of a lie - but just go with it for now.

There are a few reasons why this is better as a handler than a task. First, handlers run after all of the tasks, and if multiple tasks notify the same handler, that handler only runs once. Cool! I'll mention a second advantage to handlers in a minute.

Change over to our local machine's terminal and run the playbook!

ansible-playbook ansible/playbook.yml -i ansible/hosts.ini

We're expecting Ansible to execute all of the tasks... and then call the handler at the end. Wait! There's nothing at the end: it did not call the handler!

To prove it, refresh the browser: yep, we're still staring at the boring Nginx test page.

Handler and Changed State

So... this didn't work... and this is the second reason why handlers are great! A handler only runs if the task that's notifying it changed. If you look at the output, not surprisingly, almost every task says "Ok"... which means that they did not make a change to the server. The only two that did change are related to Composer... and honestly... those aren't really changing the server either. They just aren't smart enough - yet - to correctly report that they have not made a change.

The important one in our case is "Enable Symfony Config". The symbolic link was already there, so it did not change and so it did not notify the handler.

So let's delete that link and see what happens! In the VM, run:

sudo rm /etc/nginx/sites-enabled/mootube.l.conf

Try the playbook now!

ansible-playbook ansible/playbook.yml -i ansible/hosts.ini

Watch for the "changed" state... got it! And... yes! Running Handlers: Restart Nginx. Now try your browser. Woh! Ok, 502 bad gateway. Ya know, I'm going to say that's progress: Nginx did restart... but now something else is busted! Before we put on our fancy debuggin' hat, let's add the "Restart Nginx" notify everywhere else it's needed.

For example, if we decide to change the template, we need Nginx to restart, or reload if you prefer:

---
- hosts: vb
... lines 3 - 8
tasks:
... lines 10 - 47
- name: Add Symfony config template to the Nginx available sites
... lines 49 - 52
notify: Restart Nginx
... lines 54 - 155

Up further, when we first install Nginx, if this changes because of a new Nginx version, we also want to restart:

---
- hosts: vb
... lines 3 - 8
tasks:
... lines 10 - 40
- name: Install Nginx web server
... lines 42 - 45
notify: Restart Nginx
... lines 47 - 155

Restarting PHP-FPM

The other thing we might need to restart is PHP-FPM, like when we update php.ini. At the bottom, copy the handler and make a new one called "Restart PHP-FPM". Then, just replace the name - nginx with php7.1-fpm:

---
- hosts: vb
... lines 3 - 8
tasks:
... lines 10 - 142
handlers:
... lines 144 - 149
- name: Restart PHP-FPM
become: true
service:
name: php7.1-fpm
state: restarted

Copy the name of the handler.

We definitely need to run this if any PHP extensions are installed or updated. And also if we update php.ini:

---
- hosts: vb
... lines 3 - 8
tasks:
... lines 10 - 80
- name: Install PHP packages
... lines 82 - 92
notify: Restart PHP-FPM
... lines 94 - 101
- name: Set date.timezone for FPM
... lines 103 - 107
notify: Restart PHP-FPM
... lines 109 - 155

Beautiful! Since we have not restarted php-fpm yet, I'll go to my VM so we can make one of these tasks change. Open php.ini:

sudo vim /etc/php/7.1/fpm/php.ini

I'll search for timezone and set this back to an empty string:

; ...
[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone =
; ...

Now, re-run the playbook!

ansible-playbook ansible/playbook.yml -i ansible/hosts.ini

Watch for it.... yes! Restart PHP-FPM.

Let's try it! Refresh the browser! Bah! Still a 502 bad gateway!? That's bad news. Debuggin' time! In the VM, tail the log:

tail /var/log/nginx/mootube.l_error.log

Ah ha! It doesn't see our socket file! That's because I messed up! The true socket path is /var/run/php/php7.1-fpm.sock.

Easy fix: in symfony.conf, add /php in both places:

server {
... lines 2 - 11
location ~ ^/(app_dev|config)\.php(/|$) {
fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
... lines 14 - 24
}
# PROD
location ~ ^/app\.php(/|$) {
fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
... lines 29 - 43
}
... lines 45 - 53
}

Start up the playbook again!

ansible-playbook ansible/playbook.yml -i ansible/hosts.ini

This is a perfect example of why handlers are so cool! Since the template task is "changed", Nginx should be restarted. That's exactly how it should work.

Try the browser now! Ah, 500 error! Again, I'm counting that as progress!

Tail the log once more:

tail /var/log/nginx/mootube.l_error.log

Ah, so Symfony is unable to create the cache directory. That's an easy... but interesting fix. Let's do it next.

Leave a comment!

5
Login or Register to join the conversation
Default user avatar
Default user avatar sokphea chea | posted 5 years ago

(-_-!) still no progress this week? I arrive to the office early everyday just to learn this course. Dear, Ryan teacher..your student need to hear your voice. hahahha :P

Reply

Hey Chea,

We're sorry about that! We were really busy with updating short FOSUserBundle 2.0 tutorial this week and didn't have enough time to continue with Ansible. But we're going to upload more Ansible video next week. Thanks for your patience!

Cheers!

Reply

And this chapter is published! More scheduled for next week. And now almost all of the tutorial is in final processing! Woohoo!

Reply
Default user avatar

I can't wait to finish this course..go go knpuniversity..!

Reply
Default user avatar
Default user avatar sokphea chea | Victor | posted 5 years ago

:D Thank you for the response, love knpuniversity, you guys are awesome.

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial is built using an older version of Symfony, but the core concepts of Ansible are still valid. New versions of Ansible may contain some features that we don't use here.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.12
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.0
        "snc/redis-bundle": "^2.0", // 2.0.0
        "predis/predis": "^1.1", // v1.1.1
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.8
        "symfony/phpunit-bridge": "^3.0", // v3.1.4
        "doctrine/data-fixtures": "^1.1", // 1.3.3
        "hautelook/alice-bundle": "^1.3" // v1.4.1
    }
}
userVoice