Dynamic Prefix in Symfony4 Controller Route Annotation - php

I want to achieve this annotation in my controller, but i can not find any documentation or a way to have a wildcard(*) prefix in my annotation inside the controller.
/**
* #Route("/project/*/{Alias}", name="front-story-page" )
*/
public function ShowStoryFront(Story $story)
{
..
}
I tried a whole bunch of different ways but nothing seems to work!

have you simply tried to add another param ?
/**
* #Route("/project/{WildCardParam}/{Alias}", name="front-story-page" )
*/

Related

Organize the models in Symfony 3

I just read the official Symfony 3 docs and point when I need to retrieve objects from database I should use something like this:
$repository = $em->getRepository('AppBundle:Product');
Here Product is just an entity class with no parent, so Doctrine work with it through annotations. But I'm not sure it's a good idea to hardcode the model name in quotes. What if later I conclude to name the model Good, should I search through the whole project and replace Product on Good. I Laravel for example, each model extends the base model class so I could write : Product::model()->find('nevermind') . Is there any such option in Symfony 3.3?
I'm not shure if it is a solution for your problem, but you can write:
$repository = $em->getRepository(Product::class);
The $em->getRepository('...') returns a mixed datatype (depends on the first parameter). Even you write $repository = $em->getRepository(Product::class); the IDE cannot resolve the real datatype. I suggest this method (pseudo code):
/**
* Get product repo
*
* #return ProductRepository
*/
public function getProductRepository()
{
return $this->getEntityManager()->getRepository(Product::class);
}
/**
* #return \Doctrine\ORM\EntityManager
*/
public function getEntityManager(): EntityManager
{
return $this->getDoctrine()->getManager();
}
You can declare the repository as a service like this:
services:
app.product_repo:
class: AppBundle\Entity\ProductRepository
factory: ['#doctrine.orm.default_entity_manager', getRepository]
arguments:
- AppBundle\Entity\Product
Then in your controller:
$repository = $em->get('app.product_repo');
Well at least it works with PHPStorm and its Symfony plugin. Having auto-complete for services is really a must.

How to make a Symfony Callback validator Doctrine aware?

As said in the title, I actually need to create a validation process with Symfony.
I'm using YAML file, everything is okay.
But in some cases, I need to check the database before saying that the data is validated.
I was searching in the Callback method, but it actually only allows me to basically check the values. I searched to make dependency injection, or even passing a defined service as a Callback, but it does not help too.
So the question, in short is: is it possible to achieve it? In which way?
With what #dragoste said in comments, I searched how to made it with my own constraint.
The solution is so to use a Custom Constraint. It is a bit messy to know what file to make and what to do, so here is what I have done.
To explain you what are my files, the goal was to validate a rent, not by how it is made but just check that there is no rent at the same moment. That's why I have to use a constraint with Doctrine inside it.
Creating the Validator folder inside the root of your bundle. Then, adding a Constraints folder inside the Validator folder.
Creating a file RentDatesConstraint.php in Validaor/Constraints folder.
Here is how it looks:
<?php
namespace ApiBundle\Validator\Constraints;
use ApiBundle\Validator\RentDatesValidator;
use Symfony\Component\Validator\Constraint;
class RentDatesConstraint extends Constraint
{
public $message = 'The beginning and ending date of the rent are not available for this vehicle.'; // note that you could use parameters inside it, by naming it with % to surround it
/**
* #inheritdoc
*/
public function validatedBy()
{
return RentDatesValidator::class; // this is the name of the class that will be triggered when you need to validate this constraint
}
/**
* #inheritdoc
*/
public function getTargets()
{
return self::CLASS_CONSTRAINT; // says that this constraints is a class constraint
}
}
Now you have created your own class constraint, you have to create your own validator.
Create a file RentDatesValidator.php in Validator folder.
<?php
namespace ApiBundle\Validator;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class RentDatesValidator extends ConstraintValidator
{
/**
* #var Registry $doctrine
*/
private $doctrine;
/**
* RentDatesValidator constructor.
* #param Registry $_doctrine
*/
public function __construct(Registry $_doctrine)
{
$this
->setDoctrine($_doctrine)
;
}
/**
* #param Registry $_doctrine
* #return $this
*/
public function setDoctrine(Registry $_doctrine)
{
$this->doctrine = $_doctrine;
return $this;
}
/**
* #inheritdoc
* #param Rent $_value
*/
public function validate($_value, Constraint $_constraint)
{
//do your stuff here
if ($testFails) {
$this
->context
->buildViolation($_constraint->message) // here you can pass an array to set the parameters of the string, surrounded by %
->addViolation()
;
}
}
}
We are almost finished, we have to declare it as a service, so here we edit services.yml in Resources/config
services:
# [...]
validator.rent_dates:
class: ApiBundle\Validator\RentDatesValidator
tags:
- { name: validator.constraint_validator }
arguments: [ "#doctrine" ]
You can notice here that I passed #doctrine service, but you can actually pass any service you want, even many, as long as you are defining the RentDatesValidator class properly to accept those services in its constructor.
And now, all you have to do is to use this in your validation.
Here we edit Rent.yml in Resource/config/validation to add this only line:
ApiBundle\Entity\Rent:
constraints:
- ApiBundle\Validator\Constraints\RentDatesConstraint: ~
We are done! The validation will work when passing your object to the validator service.
You can notice that this is made with YAML, I personally prefer this way of doing things as it separate each parts (entity-definition, database schema, validation files, ...) but you can do it with annotation, XML or even pure PHP. It's up to you, so if you want to see more syntax, you can still go on the link to Symfony Documentation to know how to do this.

How to choose the driver to serialize/deserialize with jmsserializer

I have an object which I serialize using annotations. It works fine.
If I am using yaml configuration, it works fine also.
My problem is I want to use both in different context. Lets say that in controller one, I want to use the annotation configuration and in controller two, I want to use the yaml configuration. I want to do that because I need to have different field names in those outputs.
I tried to override the serializer with a new instance using only the annotation.
I change jmsserializer service configuration to not use specific drivers. It worked but I cannot choose which one to activate dynamically.
I tried to select the driver in the container but I couldn't make it work.
Is this possible? Am I missing something?
I don't see how you can achieve this.
But if you to want expose a property differently, you can create different views of your object by using an exclusion strategy.
Example :
/**
* #JMS\ExclusionPolicy("all")
* #ORM\Entity
*/
class FooBar
{
/**
* #ORM\Column(type="string")
* #JMS\Groups({"foo"})
*/
protected $name; // output 'name'
/**
* #ORM\Column(type="string")
* #JMS\SerializedName("foo_bar_name")
* #JMS\Accessor(getter="getName", setter="setName")
* #JMS\Groups({"bar"})
*/
protected $fooName; // output 'foo_bar_name'
// ...
public function setName($name)
{
$this->address = $name;
return $this;
}
public function getName()
{
return $this->name;
}
}
Like this, the property can be serialised in two different names :
use JMS\Serializer\SerializationContext;
$serializer->serialize(new FooBar(), 'json', SerializationContext::create()->setGroups(array('foo')));
//will output $name as 'name'
$serializer->serialize(new FooBar(), 'json', SerializationContext::create()->setGroups(array('bar')));
//will output $fooName as 'foo_bar_name'
Note the #JMS\SerializedName is not mandatory, you can use it for custom names.
See more at the Exclusion strategies part of the documentation.
Hope this could be an alternative for you.

Defining a route in Symfony 2 with variable action

Just imagine, I have a controller with several actions in it.
And to reach each action I have to define it strictly in routing.yml file like this:
admin_edit_routes:
pattern: /administrator/edituser
defaults: { _controller: MyAdminBundle:Default:edituser }
admin_add_routes:
pattern: /administrator/adduser
defaults: { _controller: MyAdminBundle:Default:adduser }
And I can have plenty of such pages. What I want to achieve is to define necessary action in my URI, just like Kohana routes do.
I mean, I want simply to use one route for all actions:
(code below is not valid, just for example)
admin_main_route:
pattern: /administrator/{action}
defaults: { _controller: MyAdminBundle:Default:{action} action:index}
It will pass all requests to /administrator/{anything} to this route, and after that, according to the action, stated in the uri, the needed action is gonna to be called.
If action is not stated in the uri, then we got thrown to index action.
Is it possible in Symfony 2 in any ways and if yes, then how?
Thanking in advance.
This doesn't feel right to do so. By exposing your controller API (methods) "on the fly" directly via GET method, you open your application to security vulnerabilities. This can also easily lead to unwanted behaviours.
Moreover, how would you generate routes in twig for example ? By giving explicit constants tied to the method names?
{{ path('dynamic_route', { 'method': 'addUser' }) }}
This is not how Symfony works nor what it recommands. Symfony is not CodeIgniter nor Kohana. You don't need a second "Front Controller"
If this is a laziness matter, you can switch your routing configurations to annotations. And let Symfony auto-generate the routes you need, with minimal efforts.
namespace Acme\FooBundle\Controller;
/**
* #Route("/administrator")
*/
class DefaultController extends Controller
{
/**
* #Route
*/
public function indexAction(Request $request);
/**
* #Route("/adduser")
*/
public function addUserAction(Request $request);
/**
* #Route("/edituser")
*/
public function editUserAction(Request $request);
/**
* #Route("/{tried}")
*/
public function fallbackAction($tried, Request $request)
{
return $this->indexAction($request);
}
}
The fallbackAction methods, returns any /administrator/* route which does not exist to the indexAction content.
To sum up, I advise not to use dynamic routing "on the fly" directly to method names.
An alternative would be to create a command which will generate routes once executed.
Well, maybe that's not the best way to do that, but it works. Your dynamicAction accepts action string parameter and checks if such action exists. If so, it executes given action with params.
/**
* Default Controller.
*
* #Route("/default")
*/
class DefaultController extends Controller
{
/**
* #Route("/{action}/{params}", name="default_dynamic")
*/
public function dynamicAction($action, $params = null)
{
$action = $action . 'Action';
if (method_exists($this, $action)) {
return $this->{$action}($params);
}
return $this->indexAction();
}
}
I'm curious if someone have any other ideas :>

Symfony2: #Template annotation and PHP templates

When I use Symfony, I name my views with the name of the corresponding action, so that I can use a #Template annotation without any parameters to render the view.
/**
* #Route("/{id}/show", name="entity_show")
* #Template()
*/
public function showAction($id) {
}
Does this only work with Twig? Is there a way to not having to specify the name of the view as a parameter for #Template when I use PHP views?
In addition to what Patt wrote, you must specifiy the engine for each use of the annotation:
#Template(engine="php")

Categories