Moving replicated codes somewhere appropriate and access them from all controllers - symfony

I want to be able to access a code from all my controllers so what is the best way of doing it? The code below is to handle form errors and I've been replicating it in every single controller in my project.
So I want to keep getErrorMessages() somewhere else and access it in controller below.
Note: I read about services but got confused and this example!
Example Controller:
class HelloController extends Controller
{
public function processAction(Request $request)
{
//More code here
if ($form->isValid() !== true)
{
$errors = $this->getErrorMessages($form);
return $this->render('SayHelloBundle:hello.html.twig',
array(
'page' => 'Say Hello',
'form' => $form->createView(),
'form_errors' => $errors
));
}
//More code here
}
private function getErrorMessages(FormInterface $form)
{
$errors = array();
foreach ($form->getErrors() as $error)
{
$errors[] = $error->getMessage();
}
foreach ($form->all() as $child)
{
if (! $child->isValid())
{
$options = $child->getConfig()->getOptions();
$field = $options['label'] ? $options['label'] : $child->getName();
$errors[$field] = implode('; ', $this->getErrorMessages($child));
}
}
return $errors;
}
}

You could create a class that has all the base operations in it and call that class (or set of classes) in your controllers.

OK did it :)
/var/www/html/local/sport/app/config/config.yml
imports:
- { resource: services.yml }
/var/www/html/local/sport/app/config/services.yml
parameters:
form_errors.class: Football\TeamBundle\Services\FormErrors
services:
form_errors:
class: %form_errors.class%
/var/www/html/local/sport/src/Football/TeamBundle/Services/FormErrors.php
namespace Football\TeamBundle\Services;
use Symfony\Component\Form\FormInterface;
class FormErrors
{
public function getErrors(FormInterface $form)
{
$errors = array();
//This part get global form errors (like csrf token error)
foreach ($form->getErrors() as $error)
{
$errors[] = $error->getMessage();
}
//This part get errors for form fields
foreach ($form->all() as $child)
{
if (! $child->isValid())
{
$options = $child->getConfig()->getOptions();
$field = $options['label'] ? $options['label'] : ucwords($child->getName());
//There can be more than one field error, that's why implode is here
$errors[strtolower($field)] = implode('; ', $this->getErrors($child));
}
}
return $errors;
}
}
controller
if ($form->isValid() !== true) {
$errors = $this->get('form_errors')->getErrors($form);
echo '<pre>'; print_r($errors); exit;
}

Related

How to implement a nice solution for multilang entity slug based routes in Symfony2

I'd like to create a simple bundle to handle some multilingual pages in a website with translated slugs.
Based on translatable, sluggable and i18nrouting
implemented an entity (Page) with title, content, slug fields + locale property as the doc says
created a new Page set its title and content then translated it by $page->setTranslatableLocale('de'); and set those fields again with the german values, so that the data in the tables looks fine, they are all there
implemented the controller with type hinting signature: public function showAction(Page $page)
generated some urls in the template by: {{ path("page_show", {"slug": "test", "_locale": "en"}) }} and {{ path("page_show", {"slug": "test-de", "_locale": "de"}) }}, routes are generated fine, they look correct (/en/test and /de/test-de)
clicking on them:
Only the "en" translation works, the "de" one fails:
MyBundle\Entity\Page object not found.
How to tell Symfony or the Doctrine or whatever bundle to use the current locale when retrieving the Page? Do I have to create a ParamConverter then put a custom DQL into it the do the job manually?
Thanks!
Just found another solution which I think is much nicer and i'm going to use that one!
Implemented a repository method and use that in the controller's annotation:
#ParamConverter("page", class="MyBundle:Page", options={"repository_method" = "findTranslatedOneBy"})
public function findTranslatedOneBy(array $criteria, array $orderBy = null)
{
$page = $this->findOneBy($criteria, $orderBy);
if (!is_null($page)) {
return $page;
}
$qb = $this->getEntityManager()
->getRepository('Gedmo\Translatable\Entity\Translation')
->createQueryBuilder('t');
$i = 0;
foreach ($criteria as $name => $value) {
$qb->orWhere('t.field = :n'. $i .' AND t.content = :v'. $i);
$qb->setParameter('n'. $i, $name);
$qb->setParameter('v'. $i, $value);
$i++;
}
/** #var \Gedmo\Translatable\Entity\Translation[] $trs */
$trs = $qb->groupBy('t.locale', 't.foreignKey')->getQuery()->getResult();
return count($trs) == count($criteria) ? $this->find($trs[0]->getForeignKey()) : null;
}
It has one disadvantage there is no protection against same translated values ...
I found out a solution which i'm not sure the best, but works.
Implemented a PageParamConverter:
class PageParamConverter extends DoctrineParamConverter
{
const PAGE_CLASS = 'MyBundle:Page';
public function apply(Request $request, ParamConverter $configuration)
{
try {
return parent::apply($request, $configuration);
} catch (NotFoundHttpException $e) {
$slug = $request->get('slug');
$name = $configuration->getName();
$class = $configuration->getClass();
$em = $this->registry->getManagerForClass($class);
/** #var \Gedmo\Translatable\Entity\Translation $tr */
$tr = $em->getRepository('Gedmo\Translatable\Entity\Translation')
->findOneBy(['content' => $slug, 'field' => 'slug']);
if (is_null($tr)) {
throw new NotFoundHttpException(sprintf('%s object not found.', $class));
}
$page = $em->find($class, $tr->getForeignKey());
$request->attributes->set($name, $page);
}
return true;
}
public function supports(ParamConverter $configuration)
{
$name = $configuration->getName();
$class = $configuration->getClass();
return parent::supports($configuration) && $class == self::PAGE_CLASS;
}
}
TranslationWalker nicely gets the entity in active locale:
class PagesRepository extends \Doctrine\ORM\EntityRepository
{
public function findTranslatedBySlug(string $slug)
{
$queryBuilder = $this->createQueryBuilder("p");
$queryBuilder
->where("p.slug = :slug")
->setParameter('slug', $slug)
;
$query = $queryBuilder->getQuery();
$query->setHint(
Query::HINT_CUSTOM_OUTPUT_WALKER,
'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
);
return $query->getSingleResult();
}
}
And in controller
/**
* #Entity("page", expr="repository.findTranslatedBySlug(slug)")
* #param $page
*
* #return Response
*/
public function slug(Pages $page)
{
// thanks to #Entity annotation (Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity)
// Pages entity is automatically retrieved by slug
return $this->render('content/index.html.twig', [
'page' => $page
]);
}

Create my own entity generator on symfony using doctrine generator

I am building an entity generator in order to update my DB's structure and data according to a distant one (DB), via SOAP WS...
I get the structure from an array, and am currently writing my entity classes by myself, with a file_put_content().
But as I advance, I can't help noticing that the Doctrine\ORM\Tools\EntityGenerator already does what I intend to do, way better I would ever be able to!!
So i was wondering if there was a way to use this implemented generator from my controller?
I mean, this is what Symfony is all about right? Using existing classes & bundles from anywhere in my code? :)
Any idea someOne?
Thx :)
You can use this code:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineEntityGenerator;
use Sensio\Bundle\GeneratorBundle\Command\Validators;
class MyController extends Controller
{
private $generator;
public function generateEntityAction()
{
$format = "annotation"; //it can also be yml/php/xml
$fields = "title:string(255) body:text";
$withRepository = false; //true/false
$entity = Validators::validateEntityName("MyBundle:EntityName");
list($bundle, $entity) = $this->parseShortcutNotation($entity);
$format = Validators::validateFormat($format);
$fields = $this->parseFields($fields);
$bundle = $this->get('service_container')->get('kernel')->getBundle($bundle);
$generator = $this->getGenerator();
$generator->generate($bundle, $entity, $format, array_values($fields), $withRepository);
}
protected function createGenerator()
{
return new DoctrineEntityGenerator($this->get('service_container')->get('filesystem'), $this->get('service_container')->get('doctrine'));
}
protected function getSkeletonDirs(BundleInterface $bundle = null)
{
$skeletonDirs = array();
if (isset($bundle) && is_dir($dir = $bundle->getPath() . '/Resources/SensioGeneratorBundle/skeleton')) {
$skeletonDirs[] = $dir;
}
if (is_dir($dir = $this->get('service_container')->get('kernel')->getRootdir() . '/Resources/SensioGeneratorBundle/skeleton')) {
$skeletonDirs[] = $dir;
}
$skeletonDirs[] = __DIR__ . '/../Resources/skeleton';
$skeletonDirs[] = __DIR__ . '/../Resources';
return $skeletonDirs;
}
protected function getGenerator(BundleInterface $bundle = null)
{
if (null === $this->generator) {
$this->generator = $this->createGenerator();
$this->generator->setSkeletonDirs($this->getSkeletonDirs($bundle));
}
return $this->generator;
}
protected function parseShortcutNotation($shortcut)
{
$entity = str_replace('/', '\\', $shortcut);
if (false === $pos = strpos($entity, ':')) {
throw new \InvalidArgumentException(sprintf('The entity name must contain a : ("%s" given, expecting something like AcmeBlogBundle:Blog/Post)', $entity));
}
return array(substr($entity, 0, $pos), substr($entity, $pos + 1));
}
private function parseFields($input)
{
if (is_array($input)) {
return $input;
}
$fields = array();
foreach (explode(' ', $input) as $value) {
$elements = explode(':', $value);
$name = $elements[0];
if (strlen($name)) {
$type = isset($elements[1]) ? $elements[1] : 'string';
preg_match_all('/(.*)\((.*)\)/', $type, $matches);
$type = isset($matches[1][0]) ? $matches[1][0] : $type;
$length = isset($matches[2][0]) ? $matches[2][0] : null;
$fields[$name] = array('fieldName' => $name, 'type' => $type, 'length' => $length);
}
}
return $fields;
}
}
Mostly, it comes from Sensio\Bundle\GeneratorBundle\Command\GenerateDoctrineEntityCommand and Sensio\Bundle\GeneratorBundle\Command\GeneratorCommand

symfony2.3 filter twig inside other filter

i have filter in twig :
class AcmeExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('price', array($this, 'priceFilter')),
);
}
public function priceFilter($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',')
{
$price = number_format($number, $decimals, $decPoint, $thousandsSep);
$price = '$'.$price;
return $price;
}
}
but how can call price filter inside other filter? in symfony 2.0 declared filter with 'price' => new \Twig_Filter_Method($this, 'priceFilter')
and could from within another filter call this.
thanks and sorry for my english
if you want the returned value of the other filter into your price filter, you can chain them in twig:
{{ value|acme_filter|price }}
Or in the other direction, if you need the return value of the price filter in your other filter:
{{ value|price|acme_filter }}
If you really need the price filter inside your other filter, no problem. The extension is a plain php class.
public function acmeFilter($whatever)
{
// do something with $whatever
$priceExtentsion = new PriceExtenstion();
$whatever = $priceExtension->priceFilter($whatever);
// do something with $whatever
return $whatever;
}
class SomeExtension extends \Twig_Extension {
function getName() {
return 'some_extension';
}
function getFilters() {
return [
new \Twig_SimpleFilter(
'filterOne',
function(\Twig_Environment $env, $input) {
$output = dosmth($input);
$filter2Func = $env->getFilter('filterTwo')->getCallable();
$output = call_user_func($filter2Func, $output);
return $output;
},
['needs_environment' => true]
),
new \Twig_SimpleFilter(
'filterTwo',
function ($input) {
$output = dosmth($input);
return $output;
}
)
];
}
}
Your use callback function as "static".
You can define your function as static, or replace to \Twig_Filter_Method($this, 'priceFilter')

symfony2 form bindRequest returns 500 error

so i have a form type "PictureType":
<?php
namespace AB\MyBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class PictureType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder ->add('picture','file');
}
public function getName()
{
return 'ab_mybundle_picturetype';
}
public function useDefaultOptions(array $options)
{
return array(
'data_class' => 'AB\MyBundle\Form\Model\Picture'
);
}
}
entity Picture:
<?php
namespace AB\MyBundle\Form\Model;
use Symfony\Component\Validator\Constraints as Assert;
class Picture
{
/**
* #Assert\Image(maxSize="800000")
*/
public $picture;
}
the controller:
use AB\MyBundle\Form\Type\PictureType;
use AB\MyBundle\Form\Model\Picture;
...
...
$form = $this->createForm(new PictureType, new Picture);
if( $this->get('request')->getMethod() == 'POST' )
{
echo "1"; // 1 is printed
$form->bindRequest($this->get('request')); // this line gives the error
echo "2"; // 2 is not printed
if( $form->isValid() )
{
}
}
return $this->render("ABMyBundle:Default:pic.html.twig",array(
'form' => $form->createview()
));
when i submit the form it gives me an 500 error
"The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?"
can someone help me :( ?
$form = $this->createForm(new PictureType, new Picture);
if( $this->get('request')->getMethod() == 'POST' )
{
echo "1";
$form->bind($request);
if( $form->isValid() )
{
echo "2";
}
}
Refer the link:
http://symfony.com/blog/form-goodness-in-symfony-2-1
https://github.com/symfony/symfony/pull/4811
Don't use echo, controller must return a response:
$form->bindRequest($this->get('request')); // this line gives the error
if( $form->isValid() )
{
return new Response('Hello world!');
}

Symfony 2.1 FOSUser translate error messages

I am using the following code to get all the errors in a form:
// get all the errors in the form in an array
public static function getErrorMessages(\Symfony\Component\Form\Form $form) {
$errors = array();
// actual errors
foreach ($form->getErrors() as $key => $error) {
$errors[] = $error->getMessage();
}
// find children that are not valid
if ($form->count()>0) {
foreach ($form->all() as $child) {
if (!$child->isValid()) {
$errors[$child->getName()] = formHelper::getErrorMessages($child);
}
}
}
That works. The only issue is for FOSUser: I've defined my validation group and the errors aren't getting translated. Instead of getting "The password is too short" I am getting "fos_user.password.short". The issue is with the line:
$errors[] = $error->getMessage();
That does not translate the error. Anybody could help me out figure out this issue?
You should use translator service to translate messages
Try this:
public function getFormErrorsAssoc(FormInterface $form) {
$errors = array();
/* #var \Symfony\Component\Form\FormError $error*/
foreach ($form->getErrors() as $key => $error) {
$message = $this->translator->trans($error->getMessageTemplate(), $error->getMessageParameters(), 'validators');
if ($message) {
$errors[$key] = $message;
}
}
if ($form->hasChildren()) {
/* #var FormInterface $child*/
foreach ($form->getChildren() as $child) {
$label = $child->getAttribute('label') ? : $child->getName();
if (!$child->isValid()) {
$errors[$label] = $this->getFormErrorsAssoc($child);
}
}
}
return $errors;
}

Resources