Custom Validator doesn't output error message - symfony

I have installed a custom validator which checks if the generated slug is unique.
Now I am testing the validator and it seems that the validator works (form doesn't get persisted) but I don't get an error message...
class Unique extends Constraint
{
public $message = 'The value of "%property%" already exists.';
public $property;
public function getDefaultOption()
{
return 'property';
}
public function getRequiredOptions()
{
return array('property');
}
public function validatedBy()
{
return 'loc_article_validator_unique_alias';
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
The form errors are rendered through {{ form_rest(form) }} in twig
So I found the issue.
The Issue was that that Custom Constraints errors can't get rendered over foreach. They have to get rendered through
{{ form_errors(form) }}
My remaining questions are now:
1.) How can I render the Custom Constrain Errors like all other errors?
2.) Why does the Custom class extending Constrain requires an alias of the CustomValidator service?

By these lines
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
You make the constraint a class constraint wich means the errors will show up on top of the whole form and not next to the field.
Try defining it as property constraint
public function getTargets()
{
return self::PROPERTY_CONSTRAINT;
}
If this does not help, please post your validation definition and the form builder code that generates the form.

Related

Symfony: internal forwarding vs render controller

I have simple action in simple controller:
public function _targetAction(RequestStack $requestStack)
{
$request = $requestStack->getMasterRequest();
// ...
}
And two ways to call it. First:
// The same or other controller
public function topAction(Request $request)
{
// forward to SimpleController:_target
return $this->forward('AppBundle:Simple:_target');
}
Second from twig (subrequest):
// SimpleController
public function topAction(Request $request)
{
// render
return $this->render('AppBundle:Simple:top.html.twig');
}
// top.html.twig
{{ render(controller('AppBundle:Simple:_target')) }}
How can i idenitfy which way i get to the SimpleController::_targetAction in this method:
public function _targetAction(RequestStack $requestStack)
{
// what can i do here to uniquely identify current way
// Note: $requestStack->getParentRequest() is not null in both cases
}
In my opinion, if you need to execute different code depending on the call type, you should considere create separate routes for each action.
In case you really want to use the same, my best shot is to add a parameter on the route to identify the request.
/**
*
* #Route("/target/{from}", name="_target")
*/
public function _targetAction($from)
{
if($from == 'view'){
// execute code for view call
} else {
// execute code for controller call
}
}
And then, when you call it, pass a different parameter depending on caller type:
TWIG
{{ render(controller('AppBundle:Simple:_target', { 'from': 'view' })) }}
CONTROLLER
return $this->forward('_target', array('from' => 'controller'));

Symfony error with form entity class

I'm trying to add some validation rules for my form by using callbacks for a few choice fields.
This callback I saw in the documentation should be in the entity class for that form. But in my case I'm having all the time this problem:
"ContextErrorException: Notice: Array to string conversion in /Applications/MAMP/htdocs/bbc/vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php line 462"
My code in my entity looks like this:
namespace Cgboard\AppBundle\Forms;
use Cgboard\AppBundle\Classes\Iplayer;
class SearchEntity
{
public $category;
public $coming_soon_within;
public $local_radio;
public $masterbrand;
public $max_tleos;
public $media_set;
public $page;
public $perPage;
public $q;
public $search_availibity;
public $service_type;
public $signed;
public static function getCategories() // all this getters are firing the error, why?
{
return ['undef',
'arts',
'cbcc',
'comedy'];
}
public static function getLocalRadio()
{
......
I want to use these callbacks from my validation.yml file, that looks like this one:
Cgboard\AppBundle\Forms\SearchEntity:
properties:
category:
- Choice: { callback: getCategories }
coming_soon_within:
- Range:
min: 1
max: 168
local_radio:
- Choice: { callback: getLocalRadio }
masterbrand:
- Choice: { callback: getMasterbrand }
....
Even if I delete the whole content from validation.yml I still have the error. So I think the problem is just with the entity form class. Any idea about how to work around this problem?
Because I think can be handy for someone else. The problem was that I was using getters (getLocalRadio for instance) that were creating conflicts for the internal getters used by SYmfony. I just changed the name of these getter and everything worked fine

How can I fetch a value from the DB and display it in all templates?

Assume I have a User and a Message entity in my Symfony2 app, with a oneToMany relation between both entities, so a User can have multiple messages, and I want to display the number of unread messages in my main navigation like this
Home
Whatever
Messages (6)
My Profile
where (6) is the number of unread messages. In order to fetch this amount of unread messages per user Controller independently, I could think of defining a User entity method:
public function getUnreadMessagesCount() {
$unreadMessagesCount = 0;
foreach($this->messages as $message) {
if($message->isUnread()) {
$unreadMessagesCount++;
}
}
}
and call it in my menu.html.twig
{% set umc = app.user.getUnreadMessagesCount() %}
{% if umc > 0 %}
({{ umc }})
{% endif %}
But for performance reasons, I would want to avoid looping through the entire set of messages just to check whether it is unread or not. So I would prefer to fetch it via a DQL call (and place this fetch in the UserRepository class)
$query = $em->createQuery("
SELECT COUNT(m.id) AS umc
FROM AcmeBundle:User u
JOIN AcmeBundle:Message m
WITH u.id = m.user_id
WHERE m.unread = true
AND u.id = :uid");
But afaik it's bad practice to use repository methods or to use the entitiy manager (or Doctrine) in entity classes.
I thought about fetching the value in a service and inject this into my entity. But it also is not recommended to inject services into entites.
So what is a legit way to fetch this value and output it in all templates (may also be without entity methods, e.g. by injecting the value into all Controllers)?
I used another solution to get data from a repository.
First, define the service:
## in bundle/Resources/config/services.yml
services:
YOUR_BUNDLE.twig.NAME_OF_THE_EXTENSION:
class: YOUR\BUNDLE\Twig\CLASSNAME
arguments:
- #service_container
tags:
- { name: twig.extension }
Then define the Twig extension class:
# YOUR_BUNDLE/Twig/CLASSNAME.php
<?php
namespace YOUR\BUNDLE\Twig;
class CLASSNAME extends \Twig_Extension
{
protected $container;
public function __construct($container)
{
$this->container = $container;
}
public function getGlobals()
{
return(array(
'unreadMessagesCount' => $this->container->get('doctrine')
->getManager()
->getRepository('YOUR_BUNDLE:UserRepository')
->getUnreadMessagesCount()
));
}
public function getName()
{
return 'NAME_OF_THE_EXTENSION';
}
}
I don't really like this approach, but - for completeness - I found this:
You can set the service a twig global variable in config.yml, e.g
#app/config/config.yml
twig:
globals:
your_service: "#your_service"
See here.
So simply define your service
class AcmeService {
public function __construct() {
// construct
}
public function myMethod() {
// do stuff
}
}
declare this service in your config.yml, define it as twig globale (see quoted answer above) and you can use your method in your twig file like this
{{ your_service.myMethod() }}

FOSUserBundle : one of two fields mandatory

I'm using FOSUserBundle in Symfony2, and I would like the user to enter at least one phone number between the home phone number and the mobile phone number, but I can't find where to add a check for at least one field filled.
Any clues ?
Thanks
You could make yourself a class constraint. Look at Symfony's cookbook section on custom constraints. At the bottom there is a small section on class constraint validators.
You would have a constraint like this:
class RegistrationPhones extends Constraint
{
public $message = '';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function validatedBy()
{
return 'user_phones_validator';
}
}
Then your validator would look like this:
class RegistrationPhonesValidator extends ConstraintValidator
{
public function validate($registration, Constraint $constraint)
{
$homePhone = $registration->getHomePhone();
$mobilePhone = $registration->getMobilePhone();
if (empty($homePhone) && empty($mobilePhone)) {
$this->context->addViolationAt('homePhone', $constraint->message, array(), null);
}
}
}
Finally, your validation.yml would have an entry like so:
User\MyBundle\Form\Model\Registration:
constraints:
- User\MyBundle\Validator\Constraints\RegistrationPhonesValidator:
message: phones.at_least_one_must_be_set
This should do the trick.

Creating my first twig extension to provide global variables to base templates

I need to populate a variable with some HTML code and make it available to my base.html.twig file.
To achive this I have made a twig extension. This is my first time using twig extentions so im not sure if this is the correct way of doing things.
Here is what I have so far:
Extension code:
class GlobalFooterExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_Filter_Function('GlobalFooter', array($this, 'GlobalFooter')),
);
}
public function GlobalFooter()
{
$GlobalFooter = file_get_contents('http://mysite.co.uk/footer/footer.html.twig');
return $GlobalFooter;
}
public function getName()
{
return 'GlobalFooter_extention';
}
}
config.yml:
services:
imagine.twig.GlobalFooterExtension:
class: Imagine\GdmBundle\Twig\GlobalFooterExtension
tags:
- { name: twig.extension }
base.html.twig:
{{GlobalFooter}}
This give the following error:
Twig_Error_Runtime: Variable "GlobalFooter" does not exist in "ImagineGdmBundle:Default:product.html.twig" at line 2
Im sure im missing something really obvious. How do I make $GlobalFooter from my GlobalFooterExtension class available to my base.hmtl.twig file?
You want to set a global variable, not a function.
Just use getGlobals and return your variable:
class GlobalFooterExtension extends \Twig_Extension
{
public function getGlobals()
{
return array(
"GlobalFooter" => file_get_contents('http://mysite.co.uk/footer/footer.html.twig'),
);
}
public function getName()
{
return 'GlobalFooter_extention';
}
}
Or, if you want to lazy load the value of the variable, create a function and change your template to:
{{ GlobalFooter() }}
Besides this, if the footer file is on the same site, it's better to use the {% include '...' %} tag.
rename function getFilters to getFunctions

Resources