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;
}
Related
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
]);
}
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;
}
I am trying to write a Unit Test using dataProvider to test a controller method.
However the $this->contents always return null. Does dataProvider work in CakePHP's controller test case?
public function initValidateDataProvider() {
$data['Event']['event_type'] = 1;
$data['Event']['event_id'] = 1;
$data['Event']['year'] = date('Y');
$data['Event']['name'] = 'test';
$param[0] = $data;
$provider[0] = $param;
return $provider;
}
/**
* #dataProvider initValidateDataProvider
*
*/
public function testInitValidationAll($data) {
$this->testAction(
'/sb_organizers/events/init',array(
'return' => 'contents',
'data' => $data,
'method' => 'POST'
)
);
debug($this->contents);
}
debug($this->contents); always return null.
But if I run it without dataProvider:
public function testInitValidateAll() {
$data['Event']['event_type'] = '';
$data['Event']['etag_id'] = 0;
$data['Event']['year'] = '';
$data['Event']['name'] = '';
$this->testAction(
'/sb_organizers/events/init',array(
'return' => 'contents',
'data'=>$data
)
);
debug($this->contents);
}
It works fine. Does dataProvider work in CakePHP's controller test case? I suspect it is the $this->testAction does not work with dataProvider.
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
I want to create a test for the following function in Cakephp2.0 with PHPUnit:
public function matching($check, $field) {
$return = true;
foreach ($check as $c) {
if ($c != $this->data['User'][$field]) {
$return = false;
}
}
return $return;
}
How do I set the value of:
$this->data['User'][$field]
My test function is:
public function testMatching() {
$this->data['User']['password'] = 'testpass';
$check = array('testpass');
$result = $this->User->matching($check, 'password');
$this->assertEquals(true, $result);
}
Thanks.