Create page with Symfony2

February 2016 · 8 minute read

1. In the PageBundle/Admin Controller, create a New Page Function

/**
 * Create new page
 *
 * @Route("/create/", name="PageBundle:Admin:create")
 * @Route("/{id}/create/", name="PageBundle:Admin:create_child")
 * @Template("PageBundle:Admin:create.html.twig")
 */
public function createAction($id = null, Request $request) {
	$page = new Page();

	$em = $this
		->getDoctrine()				  
		->getManager();

	if ($id !== null) {  
		$parent = $em

		->getRepository('PageBundle:Page')		  
		->findOneBy($id);

		if ($parent = null) {		  
			// Parent item doesn't exist		  
			throw $this->createNotFoundException();	  
		}
		$page->setParent($parent);  
	}

	//Check permissions		  
	$this->checkAccess('CREATE', $page);

	//Build form	  
	$form = $this->createForm(page, $page);		  
	$form->handleRequest($request);

	if ($form->isValid()) {
					  
		//Form has been posted and is valid – persist to database		  
		$em->persist($page);		  
		$em->flush();

		//Log alert message			  
		$this->alert(			  
			'success',				  
			$this->translate(					  
				'admin.create.success',					  
				array('%name%' => $page->getName()),					  
				'PageBundle'			  
			)			  
		);

		//Redirect to edit page	  
		return $this->redirect(			  
			$this->generateUrl(				  
				'PageBundle:Admin:index'		  
			)  
		);	  
	}

	//Render defined template with this data		  
	return array(		  
		'form' => $form->createView(),		  
	);  
}

2. Create a new Global file to create a form in the path GlobalBundle/Component/Form/FormType. This will extend Symfony’s AbstractType to create forms.

namespace SelenaSmall\Bundle\GlobalBundle\Component\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Validator\Constraints as Validate;

abstract class FormType extends AbstractType {
	# region Properties

	protected $name;
	protected $vars = array();

	#endregion Properties

	#region Public

	/**
	 * Add additional vars to form field
	 *
	 * @param string $field Field name to add var to
	 * @param string $name Var name
	 * @param mixed $value Value
	 * @return $this
	 */
	public function addVarToField($field, $name, $value) {
		$this->vars\[$field\]\[$name\] = [$value];
		return $this;
	}

	/**
	* @inheritdoc
	*/	  
	public function finishView(FormView $view, FormInterface $form, array $options) {	  
		parent::finishView($view, $form, $options);
		foreach ($this->vars as $field => $var) {		  
			foreach ($var as $key => $val) {			  
				$view[$field]->vars[$key] = $val;		  
			}	  
		}
	}

	/**
	* @inheritdoc
	*/	  
	public function getName() {	  
		return $this->name;	  
	}

	# endregion Public	  
}

3. A new file in the page bundle, PageBundle/Form/PageForm will extend the Global Page Form that we’ve just created to generate a form specific to the creation of a new page.

namespace SelenaSmall\Bundle\PageBundle\Form;

use SelenaSmall\Bundle\GlobalBundle\Component\Form\FormType;

use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class PageForm extends FormType {
	# region Properties

	protected $name = 'page';

	# endregion Properties

	#region Public

	/**
	 * @param FormBuilderInterface $builder
	 * @param array $options
	 */
	public function buildForm(FormBuilderInterface $builder, array $options) {
		// Add files
		$builder
			->add('name', null, array(				  
				'label' => 'entity.page.name'
			))->add('slug', null, array(
				'label' => 'entity.page.slug'	  
			))->add('description', null, array(			  
				'required' => false, 'label' => 'entity.page.description'  
			))->add('content', 'textarea', array(	  
				'required' => false, 'label' => 'entity.page.content'  
			))->add('publishAt', null, array(	  
				'required' => false,	  
				'label' => 'entity.page.publishAt',	  
				'empty_value' => array(		  
				'year' => 'YYYY',					  
				'month' => 'MM',				  
				'day' => 'DD',				  
				'hour' => 'HH',				  
				'minute'=> 'mm'		  
			)))		  
			->add('expireAt', null, array(		  
				'required' => false,			  
				'label' => 'entity.page.expireAt',			  
				'empty_value' => array(			  
				'year' => 'YYYY',			  
				'month' => 'MM',			  
				'day' => 'DD',			  
				'hour' => 'HH',			  
				'minute'=> 'mm'		  
			)))
			->add('isUnlisted', null, array(	  
				'required' => false,			  
				'label' => 'entity.page.isUnlisted'))	  
			->add('isActive', null, array(	  
				'required' => false,		  
				'label' => 'entity.page.isActive'));

		$this->addVarToField('slug', 'extra_attr', array('data-slug-target' => sprintf('#%s_name', $this->name)));  
	}

	/**
	* Set default options
	*
	* @param OptionsResolverInterface $resolver
	*/	  
	public function setDefaultOptions(OptionsResolverInterface $resolver) {
		$resolver->setDefaults(array(  
			'data_class' => 'SelenaSmall\Bundle\PageBundle\Entity\Page',	  
			'translation_domain' => 'PageBundle'  
		));
	}

	# endregion Public 
}

4. Back in the PageBundle/Admin Controller, the Build Form component will need to be updated. Replace the ‘page’ with “new PageForm()” as follows to generate the PageForm we have just created:

$form = $this->createForm(new PageForm(), $page);

This should automatically include:

use SelenaSmall\Bundle\PageBundle\Form\PageForm; at the beginning of this file.

5. Now to create the actual Form View in PageBundle/Resources/Views/Admin/create.html.twig

{% extends 'PageBundle:Admin:index.html.twig' %}
{% set domain = 'PageBundle' %}
{% trans_default_domain domain %}

{% block title -%}
	{{ 'admin.create.title'|trans }} {{ 'space.title_separator'|trans({}, 'GlobalBundle')|raw }}
{%- endblock title %}

{% block main %}
{{ form_start(form) }}
{{ parent() }}
{{ form_end(form) }}
{% endblock main %}

{% block header %}
	<h1>{{ 'admin.create.title'|trans }}</h1>
{% endblock header %}

{% block breadcrumbs %}
	{{ parent() }}
	<a href="{{ path('PageBundle:Admin:create') }}">{{ 'admin.create.title'|trans }}</a>
{% endblock breadcrumbs %}

{% block content %}
	{{ form_widget(form) }}
{% endblock content %}

{% block controls %}
{%   
	include 'AdminBundle:Utility:controls.html.twig' with {  
		controls: [       
			{          
				class: 'save success',        
				label: 'admin.create.submit'|trans          
			},{           
				class: 'back',           
				url: path('PageBundle:Admin:index'),          
				label: 'admin.back.index'|trans,          
				hasPermission: is_granted('VIEW', get_entity_class('PageBundle:Page'))           
			}       
		]   
	}
%}
  
{% endblock controls %}

6. Create a new controls view for the ‘controls block’ under AdminBundle/Resources/views/Utility/controls.html.twig. This controls utility will take care of the control buttons on the Admin’s page ie update/edit/delete.

<aside class="controls">
	{% for control in controls %}
	{% if control.hasPermission is not defined or control.hasPermission is same as(true) %}  
	{% if control.url is defined or control.route is defined %}
	{% if control.route is defined %}
	{% set control = control|merge({url: path(control.route)}) %}
	{% endif %} 
		<a class="btn {{ control.class }}" href="{{ control.url }}"{% if control.isExternal is defined and control.isExternal == true %} target="_blank"{% endif %}><span>{{ control.label }}</span></a>
    {% else %}
	<button class="btn {{  control.class|default('') }}" type="submit"><span>{{ control.label }}</span></button>
	{% endif %}  
	{% endif %} 
	{% endfor %}
</aside> 

7. Include translations for “create” under

PageBundle/Resources/translations/PageBundle.en.yml

8. AdminBundle/Resources/views/layout.html.twig add in lines 43 & 44 to include the controls we have just created for our form:

{% block controls %}
{% endblock controls %}

9. Create a new file in Global Bundle to extend twig: GlobalBundle/Twig/GlobalExtension.php

namespace SelenaSmall\Bundle\GlobalBundle\Twig;

use Doctrine\ORM\EntityManager;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Intl\Intl;

class GlobalExtension extends \Twig_Extension {
	# region Properties

	protected $bundles;
	protected $container;

	# endregion Properties

	# region Contruct

	public function __construct(array $bundles, Container $container) {
		$this->bundles = $bundles;
		$this->container = $container;
		  
	}

	# endregion Contruct

	# region Public

	# region Definitions

	/**
	* Define Twig extension functions
	*
	* @return array
	*/	  
	public function getFunctions() {		  
		return array(			  
			new \Twig\_SimpleFunction('has\_bundle', array($this, 'hasBundleFunction')),			  
			new \Twig\_SimpleFunction('get\_entity_class', array($this, 'getEntityClassFunction')),		  
			new \Twig\_SimpleFunction('get\_parameter', array($this, 'getParamterFunction'))		  
		);	  
	}

	/**
	* Define Twig entension filters
	*
	* @return array
	*/	  
	public function getFilters() {		  
		return array(			  
			new \Twig_SimpleFunction('filesize', array($this, 'filesizeFilter')),		  
			new \Twig_SimpleFunction('country', array($this, 'countryFilter'))		  
		);	  
	}

	# endregion Definitions

	# region Functions

	/**
	* Check if bundle is included
	*
	* @param $bundleName string Bundle name
	* @return boolean
	*/	  
	public function hasBundleFunction($bundleName) {		  
		return isset($this->bundles[$bundleName]);	  
	}

	/**
	* Get the full entity class from BundleName:EntityName syntax
	*
	* @param $entity string BundleName:EntityName syntax
	* @return mixed
	*/	  
	public function getEntityClassFunction($entity) {		  
		return $this->container			  
		->get('doctrine')			  
		->getManager()		  
		->getClassMetadata($entity)		  
		->getName();	  
	}

	/**
	* Get config paramter value
	*
	* @param $parameterName
	* @return mixed
	*/
	public function getParameterFunction($parameterName) {	  
		if ($this->container->hasParameter($parameterName)) {	  
			return $this->container->getParamter($parameterName);	  
		}

		return false;
	}

	# endregion Functions

	# region Filters

	/**
	* Format filesize to nearest unit
	*
	* @param integer $bytes File size in bytes to format
	* @param integer $decimals Decimal places
	* @return string
	*/	  
	public function filessizeFilter($bytes, $decimals = 0) {		  
		$value = null;		  
		$format = null;

		//Define values of each unit		  
		$kilobyte = 1024;		  
		$megabyte = ($kilobyte * $kilobyte);	  
		$gigabyte = ($megabyte * $kilobyte);  
		$terabyte = ($gigabyte * $kilobyte);
		$petabyte = ($terabyte * $kilobyte);

		if ($bytes < $kilobyte) {
			//Bytes $value = $bytes; $format = 'B'; 
		} elseif ($bytes < $megabyte) {
			//Kilobytes $value = ($bytes / $kilobyte); $format = 'KB';
		} elseif ($bytes < $gigabyte) {
			//Megabytes $value = ($bytes / $megabyte); $format = 'MB';
		} elseif ($bytes < $terabyte) {
			//Gigabytes $value = ($bytes / $gigabyte); $format = 'GB';
		} elseif ($bytes < $petabyte) {
			//Terabytes $value = ($bytes / $terabyte); $format = 'TB';
		} else {
			//Petabytes $value = ($bytes / $petabyte); $format = 'PB';
		} 

		return number_format($value, $decimals, '.', '.') . $format;
	} 

	public function countryFilter($countryCode, $locale = null) {
		if ($locale === null) {
			$locale = $this->container->get('request')->getLocale();		  
		}

		return Intl::getRegionBundle()->getCountryName($countryCode, $locale);	  
	}

	# endregion Filters

	public function getName() {	  
		return 'global_extension';  
	}

	# endregion Public
}

10. Register global twig extension in the yml file under GlobalBundle/Resources/config/services.yml

global.twig.extension:
	class: SelenaSmall\Bundle\GlobalBundle\Twig\GlobalExtension
      	arguments: ['%kernel.bundles%', '@service_container']
      	tags:
        	- { name: twig.extension }

11. Add into PageRepository function: FindOneVisibleByURL to create unique pages

PageBundle/Entity/PageRepository.php

/**
 * Find one visible from space by URL
 *
 * @param string $url
 * @return mixed
 */
public function findOneVisibleByUrl($url) {
    $now = new \DateTime();
    $now->setTimezone(new \DateTimeZone('UTC'));
	$url = trim($url, '/');
	
	$query = $this            
	->createQueryBuilder('p')           
	->where('p.url = :url')          
	->andwhere('p.isActive = :isActive')         
	->andWhere('p.publishAt IS NULL OR p.publishAt <= :now') ->andWhere('p.expireAt IS NULL OR p.expireAt >= :now');

	$paramters = array(             
		'url' => $url,            
		'isActive' => true,           
		'now' => $now     
	);

	$page = $query               
	->setMaxResults(1)       
	->setParameters($paramters)          
	->getQuery()         
	->getOneorNullResult();

	return $page;    
}

12. Render page in the FrontController PageBundle/Controller/FrontController

namespace SelenaSmall\Bundle\PageBundle\Controller;

use SelenaSmall\Bundle\GlobalBundle\Component\Controller\Controller as BaseController;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

class FrontController extends BaseController {
	#region Actions

	/**
	 * Render page
	 *
	 * @Route("/{url}/", requirements={"url" = ".*"}, name="PageBundle:Front:view")
	 * @Template("PageBundle:Front:view.html.twig")
	 *
	 * @param string $url
	 * @return array
	 */
	public function viewAction($url) {
		$page = $this
		->getDoctrine()
		->getRepository('PageBundle:Page')		  
		->findOneVisibleByUrl($url);

		if ($page === null) {	  
			throw $this->createNotFoundException();
		}

		return array(		  
			'page' => $page  
		); 
	}
}

13. Create a new view for the new page PageBundle/Resources/views/Front/view.html.twig

{% extends '::base.html.twig' %}
{% set domain = 'PageBundle' %}
{% trans_default_domain 'PageBundle' %}

{% block title -%}
	{{ page.name }} {{ parent() }}
{%- endblock title %}

{% block breadcrumbs %}
	{{ parent() }}
	<a href="{{ url('Pagebundle:Admin:index') }}">{{ 'admin.index.title'|trans }}</a>
{% endblock breadcrumbs %}

{% block content %}
	<h1>This is a new Page</h1>
{% endblock content %}

14. Clear the cache in your console with the following to command to test the code is working correctly with no errors:

$ php app/console cache:clear –env=dev

15. Once the cache has cleared successfully, refresh your browser and go to the appropriate url: http://localhost:8082/admin/pages/create/ and make sure the page is displaying.

16. Now that the page works, the next steps will be to make the page listener which listens for changes on the page entity so that we can then make it automatically update the url.