April 23rd 2013
This 2 part Symfony2 tutorial series will cover 3 main topics; Dependency Injection, Services and Twig Extensions. We are going to develop a simple flash notification bundle that builds on the Symfony2 session components flashBag and allows developers to create flash (or instant) messages that will be rendered as javascript flash notifications. This part of the tutorial will specifically cover creating and using a service in the Symfony2 framework.
To follow this tutorial you will need a Symfony2 project installed, you can follow the documentation if you are not sure how.
To start we need a new bundle, the simplest way to do this is to use the command line generator. Run the following command and then answer the questions when prompted.
php app/console generate:bundle \--namespace=LRotherfield/Bundle/NotificationBundle \--bundle-name=LRotherfieldNotificationBundle
Target directory [/Users/luke/Sites/tutorial/src]:
Configuration format (yml, xml, php, or annotation): annotation
Do you want to generate the whole directory structure [no]? no
Do you confirm generation [yes]? yes
Confirm automatic update of your Kernel [yes]? yes
Confirm automatic update of the Routing [yes]? yes
Our bundle is called LRotherfieldNotificationBundle
and has the namespace LRotherfieldBundleNotificationBundle
. You can call yours what you want, just remember to substitute the correct namespace in the rest of the tutorial.
Our notification service needs somewhere for the code to exist. We could use the DefaultController.php file but we will save that for testing our bundle and instead create a new file (with a more meaningful name) called NotificationController.php in the Controller directory:
<?php
namespace LRotherfieldBundleNotificationBundleController;
class NotificationController
{
}
All of the logic for adding and modifying flash notifications will happen in this controller.
In order to use our controller from the service container, we need to register it as a service. This is done locally in the bundle in the Resources/config/services.yml
.
You will have a services.xml
file currently, I prefer Yaml notation so we will change this first. Delete services.xml
and make a new file services.yml
in its place. We then need to make sure that our Yaml file is loaded. Open DependencyInjection/LRotherfieldNotificationExtension.php
. We need to replace lines 25 and 26 with a YamlFileLoader
instance and load our Yaml file:
$loader = new LoaderYamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
We can now use our services.yml
file to register our NotificationController
:
parameters:
lrotherfield.notification.class: LRotherfieldBundleNotificationBundleControllerNotificationController
services:
lrotherfield.notify:
class: "%lrotherfield.notification.class%"
Above we first set up a parameter lrotherfield.notification.class
for our controllers namespace. We have then registered a service called lrotherfield.notify
which gets an instance of our controller using the class namespace parameter. The % symbols wrapping the parameter name mean Symfony is looking for a parameter value rather than interpreting the value as a string. We do not need to add the class namespace as a parameter but the Symfony2 documentation makes the following arguments for doing so:
Now we can access our service from the service container anywhere in our Symfony2 project:
$container->get("lrotherfield.notify")->someMethodCall();
//We currently have not set up any methods to call in our controller class
The most important part of our service from the controllers point of view is going to be the add() method. This method needs to allow developers to add new flash or instant notifications to some kind of storage to be rendered in the views. Because Symfony2 already has the flashBag (a paramaterBag in the session that has a lifetime of one page reload) we are going to use it to store flash notifications. We will store instant notifications (no extra lifetime, removed after the class destructs) in a class variable:
//NotificationController.php
private $defaults
= array(
"type" => "flash",
),
$flashes = array();
/**
* Depending on the supplied type argument, add the values
* to the session flashBag or $this->flashes
*
* @param string $name
* @param array $arguments
*/
public function add($name, array $arguments = array())
{
$arguments += $this->defaults;
// If the type is flash then add the values to the session flashBag
if ($arguments["type"] === "flash") {
$this->session->getFlashBag()->add($name, $arguments);
}
// Otherwise if its instant then add them to the class variable $flashes
elseif ($arguments["type"] === "instant") {
// We want to be able to have multiple notifications of the same name i.e "success"
// so we need to add each new set of arguments into an array not overwrite the last
// "success" value set
if (!isset($this->flashes[$name])) {
$this->flashes[$name] = array();
}
$this->flashes[$name][] = $arguments;
}
}
You may have noticed in the above code that we have used $this->session to get the session service. This will throw an error currently as we have not defined a class variable $session and we have not made it an instance on the Symfony2 session component.
What we need to do is inject the session service into our NotificationController class. We can do this very simply by defining the required arguments passed to our controller on instantiation in our services.yml class:
#services.yml
#...
services:
lrotherfield.notify:
class: "%lrotherfield.notification.class%"
arguments:
session: @session
Now when our NotificationController class is instantiated the session service is passed a variable to the construct method. Using the "@" notation means Symfony will search for services named "session" rather than just pass "session" as a string.
We need to also define the magic __construct() method in our controller so that we can set up our session class variable to be the session instance that is being injected. Using type casting we can make sure that the variable passed is an instance of the session component and nothing else:
//NotificationController.php
private $defaults
= array(
"type" => "flash",
),
$flashes = array(),
$session;
/**
* @param SymfonyComponentHttpFoundationSessionSession $session
*/
public function __construct(SymfonyComponentHttpFoundationSessionSession $session)
{
$this->session = $session;
}
For developers to be able to access notifications they have added, we need to write some additional methods in our controller. There are some standard methods that Symfony2 suggests writing for classes such as ours. We are going to implement the following 3;
//NotificationController.php
//...
/**
* Check the flashBag and $this->flashes for existence of $name
*
* @param $name
*
* @return bool
*/
public function has($name)
{
if($this->session->getFlashBag()->has($name)){
return true;
} else {
return isset($this->flashes[$name]);
}
}
/**
* Search for a specific notification and return matches from flashBag and $this->flashes
*
* @param $name
*
* @return array
*/
public function get($name)
{
if($this->session->getFlashBag()->has($name) && isset($this->flashes[$name])){
return array_merge_recursive($this->session->getFlashBag()->get($name), $this->flashes[$name]);
} elseif($this->session->getFlashBag()->has($name)) {
return $this->session->getFlashBag()->get($name);
} else {
return $this->flashes[$name];
}
}
/**
* Merge all flashBag and $this->flashes values and return the array
*
* @return array
*/
public function all()
{
return array_merge_recursive($this->session->getFlashBag()->all(), $this->flashes);
}
Now that our service has the functionality to add, check and get notifications, we are at the end of the first part of this 2 part Symfony2 tutorial. Before moving onto the next section, we should test the current methods. We will run the test using the default controller, indexAction method, and index view that were created with the bundle:
First request an instance of our service:
$notify = $this->get("lrotherfield.notify");
Add a test notification:
$notify->add("test", array("type" => "instant", "message" => "This is awesome"));
Check for the test notification and return it:
if ($notify->has("test")) {
return array("notifications" => $notify->get("test"));
}
With all of the above elements we now have a method that looks like:
/**
* @Route("/testing/notifications")
* @Template()
*/
public function indexAction()
{
$notify = $this->get("lrotherfield.notify");
$notify->add("test", array("type" => "instant", "message" => "This is awesome"));
if ($notify->has("test")) {
return array("notifications" => $notify->get("test"));
}
return array();
}
Please note that the @Route annotation has been changed from the default value to match /testing/notifications instead.
Finally in the index.html.twig view file we need to loop through the notifications array and print the message value:
{% if notifications is defined %}
{% for notification in notifications %}
{{ notification.message }}
{% endfor %}
{% endif %}
If you have your site set up locally you can now visit http://yoursite.dev/app_dev.php/testing/notifications and you should see "This is awesome" echoed on the page.
The next tutorial will cover writing a twig extension to print out notifications, some css being included with assetic, and setting up default bundle configuration with the config class and dependency injection.
Thank you for reading and I hope the tutorial helped get an understanding of the basics of setting up a service. If you have any feedback, please use the comment form below.
Symfony2 Developer in CT USA. Luke is a Symfony2 wizard and has written some sweet libraries of his own. Luke loves Jesus, his gorgeous wife and his two beautiful daughters :)