Create a Global Bundle
December 2015 ยท 6 minute read
The following example will create a global bundle with symfony2 which will control all bundles in the application. This will house all traits and characteristics applicable to more than one other bundle that I’ll be building later on. Take note of the naming conventions for directories and files.
1. First, in your source folder, create a module for your application (in this example ‘SelenaSmall’), containing a separate directory for bundles, which contains a GlobalBundle directory and a GlobalBundle.php file
Inside that file, you need to declare the name space which refers to the file path from yuor module and declare your GlobalBundle object class as an extension of Bundle.
namespace SelenaSmall\Bundle\GlobalBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class GlobalBundle extends Bundle {
}
2. A GlobalBundle/DependecyInjection/Configuration.php file will then implement the ConfigurationInterface to build a new tree, with the root node as ‘global.’ This will allow futher bundles to be included.
namespace SelenaSmall\Bundle\GlobalBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface {
public function getConfigTreeBuilder() {
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('global');
return $treeBuilder;
}
}
3. To create an extension on php’s default Loader, create a new path in the GlobalBundle directory called Component/Config/Loader/AdvancedLoader.php
This file is specific to the GlobalBundle and is going to run functions to construct the other bundles we will build later on.
namespace SelenaSmall\Bundle\GlobalBundle\Component\Config\Loader;
use Symfony\Component\Config\Loader\Loader as SymfonyLoader;
abstract class AdvancedLoader extends SymfonyLoader {
# region Properties
protected $type;
protected $bundles;
# endregion Properties
# region Construct
public function __construct($bundles) {
$this->bundles = $bundles;
}
# endregion Construct</p>
# region Public</p>
public function supports($resources, $type = null) {
return ($type === $this->type);
}
}
4. Next, is the GlobalBundle/Service/RouteLoader.php which will extend the AdvancedLoader and declare a public function to load all the routes declared in App/Resources/Config/routing.yml
namespace SelenaSmall\Bundle\GlobalBundle\Service;
use SelenaSmall\Bundle\GlobalBundle\Component\Config\Loader\AdvancedLoader;
use Symfony\Component\Routing\RouteCollection;
class RouteLoader extends AdvancedLoader {
protected $type = 'advanced_bundles';
public function load($resource, $type = null) {
$collection = new RouteCollection();
$routingFilePath = 'Resources/config/routing.yml';
foreach ($this->bundles as $key => $val) {
$bundlePath = dirname(str_replace('\\', '/', $val));
$realPath = realpath(__DIR__ . '/../../../../' . $bundlePath . '/' . $routingFilePath);
if ($realPath !== false && file_exists($realPath)) {
$bundleCollection = $this->import('@' . $key . '/' . $routingFilePath, 'yaml');
$collection->addCollection($bundleCollection);
}
}
return $collection;
}
}
5. Every bundle, including this one is going to need a services.yml file. In this case, the path is GlobalBundle/Resources/config/services.yml
In this file, there is a service for the RouteLoader.
services:
global.routing.loader.bundles:
class: SelenaSmall\Bundle\GlobalBundle\Service\RouteLoader
arguments: ['%kernel.bundles%']
tags:
- { name: routing.loader }
6. In the GlobalBundle/Component path, there’s a need for DependencyInjection/Extension.php which will take care of loading additional config files specific to each bundle.
namespace SelenaSmall\Bundle\GlobalBundle\Component\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension as BaseExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
abstract class Extension extends BaseExtension {
/**
* Load extra config files into container
*
* @param array | string $files Config files to load
* @param string $dir Directory to load config files from
* @param ContainerBuilder $container Container
*/
Protected function loadConfig($files, $dir, ContainerBuilder $container) {
if (!is_array($files)) {
//convert values ot array
$files = array($files);
}
// Create YAML loader for specified dir
$loader = new YamlFileLoader(
$container,
new FileLocator($dir)
);
// Load all files into the container
foreach ($files as $file) {
$loader->load($file);
}
}
/**
* set bundle for Assetic
*
* @param string $bundleName Bundle name to add in CamelCase format
* @param ContainerBuilder $container Container
*/
protected function setAsseticBundle($bundleName, ContainerBuilder $container) {
$asseticBudles = $container->getParameter('assetic.bundles');
$asseticBundles[] = $bundleName;
$container->setParameter('assetic.bunles', $asseticBundles);
}
/**
* Recursively set parameters from a config array
*
* @param string $prefix Parameter key prefixes
* @param array $config Array of config items to process
* @param ContainerBuilder $container Container
*/
protected function setParameters($prefix, array $config, COntainerBuilder $container) {
foreach ($config as $key => $val) {
$container->setParameter($prefix . '.' . $key, $val);
if (isset($val[0]) || !is_array($val)) {
//Reached the end of a branch
$container->setParameter($prefix . '.' . $key, $val);
} else {
//keep going
$this->setParameters($prefix . '.' . $key, $val, $container);
}
}
}
}
7. Again, in the same GlobalBundle/Component path, we’re going to create a controller which will extend the SymfonyController and contain an abstract class to run functions which will identify the bundles being parsed to it and allow user access.
namespace SelenaSmall\Bundle\GlobalBundle\Component\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller as SymfonyController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
/**
* Base controller
*/
abstract class Controller extends SymfonyController {
# region Protected
/**
* Throw AccessDeniedException
*
* @throws AccessDeniedException
*/
protected function denyAccess() {
throw new AccessDeniedException(
$this->translate('access denied', array(), 'GlobalBundle')
);
}
/**&
* Check if has specified bundle
*
* @param string $bundleName Name of bundle to check for
* @return boolean
*/
protected function hasBundle($bundleName) {
$bundles = $this->container->getParameter('kernel.bundles');
return isset($bundles[$bundleName]);
}
/**
* Check if current user has access to specified permissions
*
* @param string $permission Permission for role name
* @param object | null $object Object to check permission against - null if checking role permissions
* @return boolean
*/
protected function isGranted($permission, $object = null) {
$securityContext = $this->get('security.context');
return $securityContext->isGranted($permission, $object);
}
/**
* Check if the current user has access to specified permissions, throw AccessDeniedException if denied
*
* @param string $permission Permission or role name
* @param object | null $object Object to check permission against - null if checking role permissions
* @throws AccessDeniedException
*/
protected function checkAccess($permission, $object = null) {
if (!$this->isGranted($permission, $object)) {
$this->denyAccess();
}
}
/**
* Dispatch event
*
* @param string $eventName
* @param string $event
*/
protected function dispatch($eventName, $event) {
$dispatcher = $this->get('event_dispatcher');
$dispatcher->dispatch($eventName, $event);
}
/**
* Log an alert message
*
* @param string $class Class to log alert against
* @param string $message Message to log
*/
protected function alert($class, $message) {
$this
->get('session')
->getFlashBag()
->add($class, $message);
}
/**
* Translate text
*
* @param string $text
* @param array $data
* @param null $domain
*
* @return string
*/
protected function translate($text, array $data, $domain = null) {
return $this
->get('translator')
->trans($text, $data, $domain);
}
/**
* Convert string to template name - use for finding override templates
*
* @param string $template
* @param array $data
* @param string $filename
* @param Request $request
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*/
protected function exportCsv($template, array $data = array(), $filename, Request $request) {
$response = $this->stream($template, $data);</p>
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '";');
$response->headers->set('Content-Transfer-Encoding', 'binary');
$response->headers->set('Pragma', 'no-cache');
$response->headers->set('Expires', '0');</p>
$response->prepare($request);
$response->sendHeaders();
$response->sendContent();</p>
return$response;
}
# endregion Protected
}
8. A GlobalExtension.php file in the same path will extend the GlobalBundle/Component/DependencyInjection/Extension.php file and compile a list of configs for all bundles.
namespace SelenaSmall\Bundle\GlobalBundle\DependencyInjection;
use SelenaSmall\Bundle\GlobalBundle\Component\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class GlobalExtension extends Extension {
public function load(array $configs, ContainerBuilder $container) {
// Compile full bundle config array
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);</p>
// Set parameters for all config entries
$this->setParameters($this->getAlias(), $config, $container);</p>
// Load Services
$this->loadConfig('services.yml', __DIR__ . '/../Resources/config', $container);</p>
// Add bundle to assetic
$this->setAsseticBundle('GlobalBundle', $container);
}
}
9. Lastly, include your new global bundle into the AppKernel.php file so your app can talk to it.
new SelenaSmall\Bundle\PageBundle\PageBundle(),
Now we’re ready to build a page and display something in the browser!