Using convention over configuration in Symfony2 controllers/views - symfony

I have the following Symfony controller:
/**
* Says thanks to the user for signing up.
*
* #Route("/thanks", name="user")
* #Template()
*/
public function thanksAction()
{
return $this->render('VNNPressboxBundle:User:thanks.html.twig');
}
If I don't include the return statement, I get an error saying the controller must return a response. It's interesting that I have to manually specify which template my action needs to use, considering Symfony could easily figure that out based on my controller and action. Plus that's how Symfony 1.x worked.
I have to imagine that I'm missing something. It doesn't seem like they would apply the convention over configuration concept in Symfony 1.x and then abandon it in Symfony >= 2.0.
Is it possible to tell Symfony to figure out which template to use based on my controller and action, and if so, how?

You have to return something. You're using #Template annotation so you don't have to render the response but you still have to return an array of parameters for the template (in your case empty):
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* Says thanks to the user for signing up.
*
* #Route("/thanks", name="user")
* #Template()
*/
public function thanksAction()
{
return array();
}
Read more on #Template annotation in the docs: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view.html
P.S. Don't compare symfony 1.x to Symfony 2.x. These are two different frameworks. Symfony 2 favors being explicit over magic.

Return an array. In your case it'll be an empty array, but normally you would fill it with variables you want to pass to a template.

Related

defines manually routes using Symfony2

I've set up my entities, now I want to
use the doctrine:generate:crud, during this command it asks what route
prefix I would like. I would expect that this means that the routes
would automatically be generate, this is not happening. So I need to
know if it is supposed to generate the routes, or if I'm supposed to
create them all manually? If it is the case that I need to generate
them manually is there a route class, to define all the routes for the
CRUD operations?
When you generate a CRUD with Symfony, it will ask you to choose a configuration format.
By default, it's annotation. If you haven't changed it, then your routes are in the entity controller, as annotation.
In the example below, you can see the #Route anotation, which is how to define the URL in anotation.
/**
* Finds and displays a user entity.
*
* #Route("/user/{id}", name="user_show")
* #Method("GET")
*
* #param User $user
* #return \Symfony\Component\HttpFoundation\Response
*/
public function showAction(User $user) {
$deleteForm=$this->createDeleteForm($user);
return $this->render('security/show.html.twig', array(
'security'=>$user,
'delete_form'=>$deleteForm->createView(),
));
}
In the end, it's not that "It didn't happen", it's simply and most likely that you haven't read some doc, and didn't knew about it... ;)
Symfony doc: Routing

Symfony Form Validation getters always triggering/failing

I'm using Symfony's Validator Getter Component In conjunction with symfony forms.
In one of my entities files, I have:
use Symfony\Component\Validator\Constraints as Assert;
class StudentPaper
{
.....
/**
* #Assert\IsTrue(message = "You must include a paper with your submission")
*/
public function hasPaper()
{
// I originally had logic that checked the validity, but just
// changed the return value to 'true' to prove that it's not working.
return true;
}
}
Unfortunately, the validation always fails (even when I hardcore the return value to be true). The validation code doesn't seem to be executed, and the form triggers the error. I even tried replacing it with IsFalse and hard coding false. Same result.
Anyone come across this?
Symfony 2.8.
PHP 5.6.15
Well, I can't fully explain what the actual problem is (because I don't know), but I did find a solution.
In my StudentPaper entity I had
/**
* Bidirectional - Student Papers have one file.
*
* #ORM\OneToOne(targetEntity="StudentPaperFile", inversedBy="student_paper", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\JoinColumn()
* #Assert\Valid()
*/
protected $paper;
as a property. Turns out that having a property named paper AND a validation getter called hasPaper() was causing unexpected behavior. As soon as I changed the function name from hasPaper() to hasTesting() or hasSubmittedPaper then the getter worked as it was intended.
So the solution is that the getter function cannot be get/is/has + a mapped property name.

Choosing custom template file in Symfony controller

I'm relatively new to Symfony, and currently using v 2.8. I've been using the #Template annotation successfully like this:
/**
* #Route("/editleague/")
* #Template()
*/
public function editAction() {
return $array;
}
And that successfully renders the twig template at Bundle/Resources/views/Default/edit.html.twig
I decided I want a different response (not a Twig template) if it was a post request, so just to start off, I changed the above code to:
/**
* #Route("/editleague/")
*/
public function editAction() {
return $this->render("Default/edit.html.twig",$array);
}
But I get a 500 error. I've tried various combinations, but haven't gotten anything to work where I can control the rendered template in the function itself. I believe it's a simple issue that someone with more experience will be able to figure out in seconds.
Since your template is in your bundle's Resources directory, you should use the Symfony logical name in your call to render:
'AppBundle:Default:edit.html.twig'
or:
':Default:edit.html.twig'
See the difference between paths to templates stored in a bundle versus templates in your app directory.

How do I implement dynamic (i.e. not cached) Doctrine Asserts in Symfony2?

I have a Doctrine-Entity in my Symfony2-Project, which uses a custom Assert/Constraint to check, if a given date value is before and/or after a given date. This looks like the following simplified code:
In my entity class:
/**
* #var \DateTime
*
* #ORM\Column(name="entry_entered_at", type="date", nullable=true)
* #AppBundleAssert\DateRangeConstraint(max = "today")
*/
private $entryEnteredAt;
The relevant snippet of the corresponding DateRangeConstraint-class:
new \DateTime($this->max)
As you can see, I want to check, if a date is before today. The \DateTime-constructor is able to resolve this to a DateTime-object of today. Nice thing, works fine.
The problem
But it turns out, that Symfony2 caches all those Doctrine-annotations, so today is always resolved to the day, the cache was lastly cleared and my constraint produces nice form errors.
As a workaround for now, I clear the cache on a daily basis, but I need a better solution.
The question
So the question is, what would you suggest, how to implement such a dynamic assert/constraint in Symfony2?
I could implement the constraint inside the form, but it should be in the domain of the entity.
Edit:
I posted as answer and marked it as solution.
The solution and some answers
It turned out, that the built in Range validator is also able to validate a date-range. So I don't need my custom validator at all.
Digging a bit deeper into the built in Range constraint and the base Constraint class gives the reason, why the built in validators can use dynamic parameters like today, but not my incorrect implemented custom validator. The Constraint base class has a __sleep() method that just stores the object vars and its current values on serialization. Thus, when we don't reinitialize the object with a custom __wakeup() method, which would be a false workaround, we only get the cached parameters.
So besides the fact, that the builtin Range constraint already solves my problem, I simply should have done my dynamic new \DateTime($constraint->max) stuff inside the custom DateRangeConstraintValidator and not the cached custom DateRangeConstraint. Just have a look into Symfony\Component\Validator\Constraints\Range and Symfony\Component\Validator\Constraints\RangeValidator to see this in action.
Lessons learned
Your custom Constraint class will be serialized and cached and thus shouldn't do any dynamic things. Just validate the options and define the messages and stuff. Your dynamic validation things (and especially the initialization of dynamic parameters) must be done within your custom ConstraintValidator class.
I suggest you to look at Custom validator, especially Class Constraint Validator.
I won't copy paste the whole code, just the parts which you will have to change.
Extends the Constraint class.
src/Acme/DemoBundle/Validator/Constraints/CheckEntryEnteredAt.php
<?php
namespace Acme\DemoBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class CheckEntryEnteredAt extends Constraint
{
public $message = 'Your error message.';
public function validatedBy()
{
return 'CheckEntryEnteredAtValidator';
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
Define the validator by extending the ConstraintValidator class, entryEnteredAt is the field you want to check:
src/Acme/DemoBundle/Validator/Constraints/CheckEntryEnteredAtValidator.php
namespace Acme\DemoBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class CheckEntryEnteredAtValidator extends ConstraintValidator
{
public function validate($entity, Constraint $constraint)
{
$today = new \Datetime('today'); // = midnight
if ($entity->entryEnteredAt < $today) {
$this->context->addViolationAt('entryEnteredAt',
$constraint->message, array(), null);
}
}
}
Use the validator:
src/Acme/DemoBundle/Resources/config/validation.yml
Acme\DemoBundle\Entity\AcmeEntity:
constraints:
- Acme\DemoBundle\Validator\Constraints\CheckEntryEnteredAt: ~
(adapted from a previous answer)
public function __construct()
{
$this->entryEnteredAt = new \DateTime();
}
is something like that a solution for your use case? (on new YourEntity() you'll have a today date set for the entryEnteredAt property)
You could also use LifecycleCallbacks, here is an exemple with preUpdate (there is some more, like PrePersist):
on top of your class entity:
* #ORM\HasLifecycleCallbacks()
and
/**
* Set updatedAt
*
* #ORM\PreUpdate
*/
public function setUpdatedAt()
{
$this->updatedAt = new \DateTime();
}

Can I implement my own Symfony2 annotations easily?

Is there anything in the Symfony annotations modules that allow me to use them for other uses?
I know for #Route and #Method you need to extend existing libraries, so its just not that easy i'm guessing.
Currently, i'm working with the JS History API, and would LOVE to put the popState data for my JS files in the annotations. So they are already available when the routing generates the URL.
Q Doesn't this makes sense to have a, HTML5 annotated title, or some attribute here? It would be great to have the ability to define this data, as annotated, right next to the already existing route name and stuff.
Q Is there anybody that has tweaked with the annotations before?
I wanted to clarify my intentions here as I think I left out some crucial details (the mention of History API) for understanding my use case.
There is a few SPA front ends that have been integrated through a front-end bundle, and this connected via AJAX calls to a backend bundle which was a straight RESTful API, with the addition of a very fun-to-develop PHP API class I made that intereprets and processes (routes) the AJAX in a fashion that directly executes other PHP class controller `methods.
I use a lot of ajax for this Symfony 2 app (fosjsrouter) to handle routing. So instead of URLs triggering the routes and actions, the SPA click event fires off AJAX to the back end router, with a large JSON payload, not limited to PHP control parameter's (class/method/var names), and data sets.
OK, so getting back on track; Given the above scenario; In the JS class object end of the router, inside this I thought it was the best place to add some JS History API functionality to it, (state, back button, etc.)
The above class can be called if a history flag was called, which could become responsible for assigning initial state data. Primarily, this is because the JSON data object that's being around in this JS method contains already a lot of the crucial route data, and param information for that route needed in the backend PHP, which comes from the annotations.
So the idea is if I add accessibility for a history state title and URL to the annotations, then I will have access to that information right there available to define the initial state, if flagged, right inside the an ajax.done(), inside this main JS routing method.
Now we can pass state back and forth two ways between the db and realtime client-side async. You can use an observer, or anything, from there front-end, and jobs/queues on the backend to keep it fully reactive. (use React too :-))
EDIT I'm not so sure that's what I was thinking, it looks like its making me set the values of the title and url for this inside the return statement of the PHP function, where I want it set in the annotation (see return 'Matthias Noback';)
So I'm trying this, but where do I set these titles at?
<?php
namespace Blah\CoreBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* #Annotation
*/
class HistoryAnnotationController
{
//history state params are out properties here..
/**
* #var
*/
private $url;
/**
* #var
*/
private $title;
/**
*
*/
public function __construct()
{
}
/**
* #return mixed
*/
public function getTitle()
{
return $this->title;
}
/**
* #return mixed
*/
public function getUrl()
{
return $this->url;
}
}
I want to set it WAY back here, so the ajax that calls this route has access to it.. (look for #historyApiTitle in this code, etc..)
<?php
namespace Blah\Bundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller,
Symfony\Component\HttpFoundation\JsonResponse,
Sensio\Bundle\FrameworkExtraBundle\Configuration\Method,
Sensio\Bundle\FrameworkExtraBundle\Configuration\Route,
Sensio\Bundle\FrameworkExtraBundle\Configuration\Template,
Blah\Bundle\Entity\Test,
Doctrine\ORM\Query; //for hydration
class StuffController
{
/**
* #Route("/some/route/name/{test}", name="some_route_name", options={"expose"=true})
* #param $test
* #return mixed
* #historyApiTitle('This is the get something page')
* #historyApiUrl('/get_something')
*/
public function getSomethingAction($test)
{
$em = $this->getDoctrine()->getManager();
$dql = "
SELECT s
FROM BlahBundle:Stuff s
WHERE s.test = :test";
$query = $em->createQuery($dql);
$query->setParameter('test', $test);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate($query,
$this->get('request')->query->get('page', 1), 1000);
return $this->render('BlahBundle:Stuff:get_something.html.twig', array('pagination' => $pagination));
}
}
Q So looking at these TWO code examples, how do I connect the dots between the two to get this to work?
Yes you can annotations classes you can follow the following tutorial Creating Custom annotations Classes
Basic rules are the follows:
Your class should have the #Annotation -phpdoc comment
/**
* #Annotation
*/
class CustomAnnotation
{
public function __construct($options) {}
}
In Your Needed class just use it in standard way;
class Person
{
/**
* #CustomAnnotation("option")
*/
public function getName()
{
return 'some stuff';
}
}
You should looks at the AOPBundle, it allows you to do treatement from your personnals annotations. But I don't thinks trying to do annotations in the view is a good idea. You need to parse the javascript with php, and it sounds bad.

Resources