Symfony - validate empty query parameter values - php

I am using the FOSRestBundle and was wondering is it possible to validate against empty query parameters using annotations?
For example when calling: /comments/1 an exception is thrown since both dealId and source query parameters haven't been set.
However calling /comments/1?dealId=1&source= is fine even though the source value hasn't ben set and doesn't match the regex outlined in the annotation.
Controller function:
/**
* Get a single comment.
*
* #Annotations\QueryParam(name="dealId", requirements="\d+", strict=true, description="The deal the comments belong to.")
* #Annotations\QueryParam(name="source", requirements="(forum|blog)", strict=true, description="The source of the comments.")
*
* #Annotations\View()
*
* #Annotations\Get("/comments/{id}", requirements={"id" = "\d+"})
*
*/
public function getCommentAction(Request $request, ParamFetcherInterface $paramFetcher, $id)
{
$dealId = $paramFetcher->get('dealId');
$source = $paramFetcher->get('source');
// TODO: Implement
return [ 'id' => $id, 'dealId' => $dealId, 'source' => $source ];
}
Update
I raised this issue on the FOSRestBundle's GitHub repo too and it looks as if what I am asking for is currently not possible due to the limitations of the Regex validator that is being used.
https://github.com/FriendsOfSymfony/FOSRestBundle/issues/814#issuecomment-49696288

If you want to force your parameters to be checked, you can change config file as explained in the documentation, Here is the sample:
fos_rest: param_fetcher_listener: force
Then you can set other options like strict, nullable accordingly.
See more details here :
http://symfony.com/doc/current/bundles/FOSRestBundle/configuration-reference.html (archive.org)
https://symfony.com/doc/3.x/bundles/FOSRestBundle/index.html#config-reference
https://symfony.com/doc/3.x/bundles/FOSRestBundle/annotations-reference.html

Just use the allowBlank option of the QueryParam. In your case you would set the allowBlank to false to get the expected behaviour:
The allowBlank option is NOT YET in the FOSRestBundle, but I provided a patch to the FOSRestBundle which has a good chance to land in the next release, version 1.5.0 of the bundle.
This is how your Controller would look like:
/**
* Get a single comment.
*
* #Annotations\QueryParam(name="dealId", requirements="\d+", strict=true, description="The deal the comments belong to.")
* #Annotations\QueryParam(name="source", requirements="(forum|blog)", strict=true, allowBlank=false, description="The source of the comments.")
*
* #Annotations\View()
*
* #Annotations\Get("/comments/{id}", requirements={"id" = "\d+"})
*
*/
public function getCommentAction(Request $request, ParamFetcherInterface $paramFetcher, $id)
{
$dealId = $paramFetcher->get('dealId');
$source = $paramFetcher->get('source');
}

The tricky part is allowing source and dealId to be empty but I think it's possible by
adding these parameters to your route (so they must be specified in order to access the controller) and using a string prefix for each parameter (i.e. dealid_ and source_), so it's possible to specify an empty value.
You'll also need to modify the regex requirements to allow empty values.
/**
* Get a single comment.
*
* #Annotations\View()
* #Annotations\Get("/comments/{id}/dealid_{dealId}/source_{source}",
* requirements={"id" = "\d+", "dealId" = "\d*", "source" = "(forum|blog)*"})
*/
public function getCommentAction(Request $request,
ParamFetcherInterface $paramFetcher, $id, $dealId, $source)
{
return [ 'id' => $id, 'dealId' => $dealId, 'source' => $source ];
}

#Annotations\QueryParam expects a nullable parameter to be set (true or false) if the strict parameter is used. Try setting it.
I guess you want:
#Annotations\QueryParam(name="dealId", requirements="\d+", strict=true, nullable=false, description="The deal the comments belong to.")
#Annotations\QueryParam(name="source", requirements="(forum|blog)", strict=true, nullable=false, description="The source of the comments.")
Also read more about QueryParam in the docs.

I am not familiar with symfony, but I think a simple
$dealId = isset($dealId) ? $dealId : '';
Would help your problem

Related

Symfony 5 / Doctrine: Sorting entity collection by creation DateTime

I am developing a project using Symfony 5. One of my use-cases involves reading a collection from the Database, with the items sorted by creation order, descending (newest first). I am using the "Timestampable" extension from "stof/doctrine-extensions-bundle" to save the createdAt and updatedAt timestamps in my entity.
According to Doctrine documentation, I can sort items ussing the Repository methods:
$sortedEntities = $repository->findBy(array('createdAt' => 'DESC'));
This is the attribute in question:
/**
* #var \DateTime $createdAt
*
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $createdAt;
However, using 'ASC' or 'DESC' seems to have no impact on the ordering of the list.
You are not reading the documentation correctly. The orderBy is the second argument, not the first.
The example given in the docs is
$tenUsers = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20), array('name' => 'ASC'), 10, 0);
Here, you can see the orderBy (name, ASC) is the second arg. The first arg is a where arg - in this case, WHERE age = 20.
Here is the full signature from Doctrine\Persistence\ObjectRepository
/**
* Finds objects by a set of criteria.
*
* Optionally sorting and limiting details can be passed. An implementation may throw
* an UnexpectedValueException if certain values of the sorting or limiting details are
* not supported.
*
* #param array<string, mixed> $criteria
* #param string[]|null $orderBy
* #param int|null $limit
* #param int|null $offset
* #psalm-param array<string, 'asc'|'desc'|'ASC'|'DESC'> $orderBy
*
* #return object[] The objects.
* #psalm-return T[]
*
* #throws UnexpectedValueException
*/
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null);
I hope that clarifies it for you. :-)
[EDIT] In response to your comment, you cannot use true as a value for the first argument. Look at the signature I posted. The first argument is #param array<string, mixed>, so it needs an array. Try this then:
sortedEntities = $repository->findBy(array(), array('createdAt' => 'DESC'));

Validate optional input GET parameters passed in through the querystring as integers in Symfony 3.4

In Symfony 3.4, is there a way to validate optional input GET parameters passed in through the querystring as integers?
If provided, I am using the $ownerId and $courseId to query the corresponding repositories, however the value needs to be an integer otherwise the query falls over.
This is what I have so far and it matches the docs, but it doesn't seem to force any validation or graceful handling of passing through http://www.crmpicco.co.uk/book-teeoff/belleisle/ayrshire/?ownerid=crmpicco&courseId=rfc1872, for example.
/**
* #Route(
* "/book-teeoff/{course}/{area}",
* name = "book_teeoff",
* requirements={"ownerId"="\d+","courseId"="\d+"},
* methods={"GET"}
* )
*
* #param Request $request
*
* #return Response
*/
public function bookTeeoffAction(Request $request): Response
{
// these are *optional*, but if provided need to be integers
$ownerId = $request->get('ownerId');
$courseId = $request->get('courseId');

In Symfony (2.3) is there any difference between equals and colon in route annotations?

There are 2 syntaxes used in the documentation. Separate the values by ':' or '='. Is there any difference?
http://symfony.com/doc/2.3/book/routing.html
/**
* #Route(
* "/articles/{_locale}/{year}/{title}.{_format}",
* defaults={"_format": "html"},
* requirements={
* "_locale": "en|fr",
* "_format": "html|rss",
* "year": "\d+"
* }
* )
*/
http://symfony.com/doc/2.3/bundles/SensioFrameworkExtraBundle/annotations/routing.html
* #Route("/{id}", requirements={"id" = "\d+"}, defaults={"id" = 1})
As far as I'm aware, there is no difference as to which syntax you end up using. The annotation reader accepts both as valid key => value syntax.
I did some debugging to see if there would be any difference if you pass both syntaxes.
A simple controller (with the annotations you provided):
class TestController extends Controller {
/**
* #Route(
* "/articles/{_locale}/{year}/{title}.{_format}",
* defaults={"_format": "html"},
* requirements={
* "_locale" = "en|fr",
* "_format": "html|rss",
* "year": "\d+"
* }
* )
*/
public function testRouteAction() {
return new Response();
}
}
As you can see I'm using "=" for _locale and ":" for _format.
Then I dumped all route collections that are passed to AnnotationLoader. You can find and do some dumps on your own, if you're interested. The namespace of the loader is Symfony\Component\Routing\Loader\AnnotationClassLoader and can be found in vendor symfony/symfony.
Most of the magic happens in method addRoute() which accepts few arguments, one of which is instance of Sensio\Bundle\FrameworkExtraBundle\Configuration\Route, holding your parsed route with all of its options. Dumping the variable $requirements we can see the formatted array with requirements:
array (size=3)
'_locale' => string 'en|fr' (length=5)
'_format' => string 'html|rss' (length=8)
'year' => string '\d+' (length=3)
So, to wrap it up - no, I believe there is not much of a difference which syntax you will use.
- Edit -
Tip: If you try to do some debugging as well, do not forget to clear your cache (environment does not matter) otherwise you will not see the result.

Unable to guess how to get a Doctrine instance from the request information

I've got this "500 Internal Server Error - LogicException: Unable to guess how to get a Doctrine instance from the request information".
Here is my controller's action definition:
/**
* #Route("/gatherplayer/{player_name}/{gather_id}")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
{
// ...
}
And it doesn't work, probably because Doctrine 2 can not "guess"... So how do I make Doctrine 2 guess, and well?
The Doctrine doesn't know how to use request parameters in order to query entities specified in the function's signature.
You will need to help it by specifying some mapping information:
/**
* #Route("/gatherplayer/{player_name}/{gather_id}")
*
* #ParamConverter("player", options={"mapping": {"player_name" : "name"}})
* #ParamConverter("gather", options={"mapping": {"gather_id" : "id"}})
*
* #Template()
*/
public function createAction(Player $player, Gather $gather)
{
// ...
}
/**
* #Route("/gatherplayer/{name}/{id}")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
I didn't find any help in paramconverter's (poor?) documentation, since it doesn't describe how it works, how it guesses with more than one parameters and stuff. Plus I'm not sure it's needed since what I just wrote works properly.
My mystake was not to use the name of my attributs so doctrine couldn't guess right. I changed {player_name} to {name} and {gather_id} to {id}.
Then I changed the names of my id in their entities from "id" to "id_gather" and "id_player" so I'm now able to do that :
/**
* #Route("/gatherplayer/{id_player}/{id_gather}")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
which is a lot more effective than
* #Route("/gatherplayer/{id}/{id}")
Now I'm wondering how I can make this work
/**
* #Route("/gatherplayer/{player}/{gather}")
* #Template()
*/
public function deleteAction(Gather_Player $gather_player)
try this:
/**
* #Route("/gatherplayer/{player_name}/{gather_id}")
* #ParamConverter("player", class="YourBundle:Player")
* #ParamConverter("gather", class="YourBundle:Gather")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
The parameters on the signature of the #Route annotation must match the entities fields, so that Doctrine makes automatically the convertion.
Otherwise you need to do the convertion manually by using the annotation #ParamConverter as it's mentionned on the other responses.
#1ed is right, you should define a #paramConverter in order to get a Player instance or a Gather instance.

Auto-completion for Zend Form Elements

When creating form elements with Zend (using Zend Studio for Eclipse), I'd like some auto completion or hints. Here's what I'm thinking. I'm sure these exist, but I don't know how to get them.
I type createElement and auto-completes gives me the signature createElement($type, $name). Great, I select it.
but when I try to set the $type I don't get any hints like DateTextBox or ValidationTextBox. Being new, I see how this can be useful. What do you do to remember all the options?
for the array of attributes like require, invalidMessage, I'd like to get a list of those to choose from, and/or auto-complete when I start typing one.
// Date field
$date = $this->createElement('DateTextBox', 'date',
array('require' => 'true', 'invalidMessage' => 'Invalid date format')
);
$date->setLabel('date')->setRequired(true);
You have few options to help yourself, without waiting for any plugin:
learn it and remember ;)
extend your phpDoc blocks with all available options:
Example (to be honest I don't know if Eclipse supports html in phpDoc or even any text after variable name in #param, but it works fine in Netbeans):
/**
* [...]
* #param string $type Can be: <ul><li>DateTextBox</li><li>ValidationTextBox</li></ul>
* #param string $name Whatever
* #param array|Zend_Config $options Array with following keys: <ul><li>require</li><li>invalidMessage</li></ul>
* #return Zend_Form_Element
*/
public function createElement($type, $name, $options = null)
extend Zend class and create your own methods to simplify your work
Example:
class My_Zend_Form_Element extends Zend_Form_Element
{
public function createDateTextBox($name, $options = null)
{
return $this->createElement('DateTextBox', $name, $options);
}
}
declare some well named constants and provide some hint in phpDoc
Example: (type ZFE_OPTIONS and IDE should show hint with some constants to use as array keys)
/**
* Can be true or false
*/
define('ZFE_OPTIONS_REQUIRE','require');
create your own helper classes with methods to produce valid options array
Example:
class ZFE_Options
{
protected $opts = array();
/**
* #param bool $req
* #return ZFE_Options
*/
public function setRequired($req){
$this->opts['require'] = (bool)$req;
return $this;
}
/**
* #param string $txt
* #return ZFE_Options
*/
public function setInvalidMessage($txt){
$this->opts['invalidMessage'] = (string)$txt;
return $this;
}
/**
* #return array
*/
public function toArray(){
return $this->opts;
}
}
$zfe_options = new ZFE_Options();
$opts = $zfe_options
->setRequired(true)
->setInvalidMessage('Please provide valid email address')
->toArray();
That's not possible. It's not how autocompletion works. The hints you get are taken directly from ZF's code documentation. Nothing more, nothing less. Everything you see as hints is taken directly from the DocBlock and method signature, e.g.
/**
* Create an element
*
* Acts as a factory for creating elements. Elements created with this
* method will not be attached to the form, but will contain element
* settings as specified in the form object (including plugin loader
* prefix paths, default decorators, etc.).
*
* #param string $type
* #param string $name
* #param array|Zend_Config $options
* #return Zend_Form_Element
*/
public function createElement($type, $name, $options = null)
Eclipse can tell you to insert a string or an array and it will know that the method returns a Zend_Form_Element, but it cannot tell you what these strings should be.
The only place where I know something like what you describe exists is for CSS files. For some reason, when I type in display: it will give me an autocomplete box with possible values for this declaration. If you want more sophisticated autocomplete like this, consider filing this as a feature request to Zend.

Categories