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);&lt;/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');&lt;/p>
		$response->prepare($request);
		$response->sendHeaders();
		$response->sendContent();&lt;/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);&lt;/p>
        // Set parameters for all config entries
        $this->setParameters($this->getAlias(), $config, $container);&lt;/p>
        // Load Services
        $this->loadConfig('services.yml', __DIR__ . '/../Resources/config', $container);&lt;/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!