Skip to content

[Routing] support for array values in route defaults #11394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 23, 2016

Conversation

xabbuh
Copy link
Member

@xabbuh xabbuh commented Jul 15, 2014

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets
License MIT
Doc PR

As pointed out in symfony/symfony-docs#4017, the XmlFileLoader was not capable of defining array default values.

  • array values
  • integer values
  • float values
  • boolean

@Tobion
Copy link
Contributor

Tobion commented Jul 15, 2014

We would also need to find a way to represent other datatypes in xml like integer, float and boolean. Only supporting array without the other datatypes would be arbitrary.
Also possibly DateTime etc.

@xabbuh
Copy link
Member Author

xabbuh commented Jul 15, 2014

@Tobion Do you think that both array types and different scalar types should be done in the same pull request?

@Tobion
Copy link
Contributor

Tobion commented Jul 15, 2014

Yes because if we don't find a solution for all of them, I'd argue it's not worth implementing only one part.
Then we could just say, xml only supports flat structure with strings.

@xabbuh
Copy link
Member Author

xabbuh commented Jul 16, 2014

I don't fully agree. Even if you were not able to use integer, float or boolean values natively in XML, you would at least be able to retain the array structure. Scalar types can be casted in the controller. Achieving the same with arrays is not always that easy (at least with nested arrays).

But I also don't think that it is too hard adding support for the other data types. So I will look into this tonight.

@stof
Copy link
Member

stof commented Jul 16, 2014

@Tobion the XML loading in Symfony already converts values to boolean or number types when they look like that in the DI component. However, this is not applied in the routing loader (and applying it could be considered a BC break btw).

However, this way to represent arrays looks good (a bit painful if you want to represent lists as you have to map the keys explicitly though)

@xabbuh
Copy link
Member Author

xabbuh commented Jul 16, 2014

@Tobion the XML loading in Symfony already converts values to boolean or number types when they look like that in the DI component. However, this is not applied in the routing loader (and applying it could be considered a BC break btw).

@stof I thought about adding an optional type attribute to the default element. If present, the content will be casted accordingly. Otherwise it is treated as a string. This way we can maintain backward compatibility.

However, this way to represent arrays looks good (a bit painful if you want to represent lists as you have to map the keys explicitly though)

We can make the key attribute optional. This should work and shouldn't cause any BC issue. What do you think?

@Tobion
Copy link
Contributor

Tobion commented Jul 16, 2014

We can make the key attribute optional. This should work and shouldn't cause any BC issue. What do you think?

The problem is that the first level needs a string key name. Otherwise you would have numeric defaults which won't work (because they get removed since preg_match also adds them).

@Tobion
Copy link
Contributor

Tobion commented Jul 16, 2014

I thought about adding an optional type attribute to the default element. If present, the content will be casted accordingly.

This will not support XSD validation with types, can it? So you could add a string within a type=integer and XSD cannot raise an error.

@xabbuh
Copy link
Member Author

xabbuh commented Jul 16, 2014

The problem is that the first level needs a string key name. Otherwise you would have numeric defaults which won't work (because they get removed since preg_match also adds them).

Should be possible to validate that in the XSD.

This will not support XSD validation with types, can it? So you could add a string within a type=integer and XSD cannot raise an error.

No, unfortunately not. If we want that, we'll have to use different XML elements for each data type I guess.

Another possibility I see is to introduce a new attribute like infer-type which defaults to false. When this is set to true, the loader can try to infer the data type from the actual value instead of simply assuming a string value.

@xabbuh
Copy link
Member Author

xabbuh commented Jul 16, 2014

I think a solution that fully supports the mentioned data types both in the XML schema and in PHP and which is backward compatible requires some more work.

What I would suggest for the moment, is creating a new defaults (the name may need to be discussed) element on the same level as the current default elements. Inside it we would nest collection, string, double, integer and boolean elements. Keys can be defined the same way as before with the key attribute. To allow numerical indices, this attribute would be optional on all but the top level of the tree.

An example configuration may then look like this:

<defaults>
  <string key="foo">bar</string>
  <collection key="list">
    <boolean>true</boolean>
    <boolean>false</boolean>
    <collection>
      <string key="foobar">hash value</string>
    </collection>
  </collection>
  <integer key="baz">10</integer>
</defaults>

The process defaults would then be equal to this array:

$defaults = array(
    'foo' => 'bar',
    'list' => array(
        true,
        false,
        array('foobar' => 'hash value'),
    ),
    'baz' => 10,
);

The already existing default element would be kept with the current behaviour for backward compatibility reasons.

What do you think?

@stof
Copy link
Member

stof commented Jul 17, 2014

@xabbuh using a single <defaults> instead of multiple default elements would be totally inconsistent with the way other places look in Symfony XML files. for me, it is a -1.

@xabbuh
Copy link
Member Author

xabbuh commented Jul 17, 2014

Staying with multiple default elements would mean something like this (the attribute should then be forbidden on the top level default child elements):

<default key="foo">
  <string>bar</string>
</default>
<default key="list">
  <collection>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <collection>
      <string key="foobar">hash value</string>
    </collection>
  </collection>
</default>
<default key="baz">
  <integer>10</integer>
</default>

For backward compatibility,

<default key="foo">
  <string>bar</string>
</default>

and

<default key="foo">bar</default>

would be equivalent.

@Tobion
Copy link
Contributor

Tobion commented Jul 17, 2014

The last proposal was also what I had in mind. But I would propose to distinguish between arrays (as data structure, not PHPs implementation) and maps/associative arrays. So you can validate in XSD that elements inside maps require a key and inside arrays must not have a key.
Otherwise you could mix elements with key and without inside collections which is possible in PHP but rather magical in general and not needed here. This would also prevent accidential user errors.

@xabbuh
Copy link
Member Author

xabbuh commented Jul 17, 2014

That's a good point since mixing list and maps also isn't possible with YAML. So, we can simply use a list and a map element.

If we agree on this, I will update the pull request to match this structure.

@Tobion
Copy link
Contributor

Tobion commented Jul 17, 2014

Yeah I agree with list and map. In php the list will still be an array that allows random access, so is not really a list. But that can be considered implementation detail and semantically people usually want to express a list a values in XML when not using an associative array/map.

@@ -217,14 +217,13 @@ private function parseConfigs(\DOMElement $node, $path)
$condition = null;

foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anyone of you know if there was a special reason to use getElementsByTagNameNs() instead of iterating over the DOMNodeList in $node->childNodes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to support namespaces

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that covered by the elements from the childNodes too? It could then need some check for the namespaces afterwards but doesn't require to scan the complete subtree.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was to ignore elements from another namespace and not throw an exception for unknown tag. So one could extend the loader and xsd to support custom elements, like in FosRestBundle.
I think this won't work with childNodes because it also returns element from other namespaces within the current node.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But throwing an exception contradicts this, doesn't it?

By the way, the new method just test for the elements local name. Should I add an additional check for the element's namespace?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only throw for unkown elements in same namespace

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, of course you're right. I updated the new code to also ignore elements with a different namespace.

@xabbuh
Copy link
Member Author

xabbuh commented Jul 18, 2014

I updated the code to support the several data types.

$routeCollection = $loader->load('list_in_map_defaults.xml');
$route = $routeCollection->get('blog');

$this->assertEquals(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use assertSame everywhere, so the type and order is checked

@xabbuh
Copy link
Member Author

xabbuh commented Jul 19, 2014

I just noticed that the new parseDefaultNode() method was capable to properly parse nodes with a namespace prefix. I fixed that and modified the already existing testLoadWithNamespacePrefix() to cover this too.

{
switch ($node->localName) {
case 'boolean':
return 'true' === trim($node->nodeValue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xsd:boolean also allwos 0 and 1 AFAIK. So this won't work again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please add a test for all values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a test for false. And as far as I know, you cannot use 1 and 0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you added a test for false 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Legal values for boolean are true, false, 1 (which indicates true), and 0 (which indicates false).
http://www.w3schools.com/schema/schema_dtypes_misc.asp

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can check that again. But I'm pretty sure that the schemaValidateSource() method reported an error for 0 and 1. But I can of course add support for 0 and 1 though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. It works with 0 and 1.

@Tobion
Copy link
Contributor

Tobion commented Jul 20, 2014

Can you please add a note about this in the routing changelog. Then I'm 👍 to merge this.

xabbuh added a commit to xabbuh/symfony that referenced this pull request Aug 15, 2014
As @Tobion pointed out in symfony#11394, true and 1 are valid values in
boolean XML attributes. The XmlFileLoader didn't handle 1 values
properly.
Tobion added a commit that referenced this pull request Aug 20, 2014
This PR was merged into the 2.3 branch.

Discussion
----------

[Routing] fix handling of nullable XML attributes

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

As @Tobion pointed out in #11394, ``true`` and ``1`` are valid values in boolean XML attributes. The XmlFileLoader didn't handle ``1`` values properly.

Commits
-------

7b4d4b6 fix handling of nullable XML attributes
@Tobion
Copy link
Contributor

Tobion commented Oct 6, 2014

@fabpot considering all other loaders except xml already "supported" array defaults, we have two ways to deal with this

  1. "remove" array default support in the other loaders, which will probably be a bc break for some
  2. add support for array defaults in xml to be consistent and merge this PR

I'd vote for 2. because its not a bc break and it should not be a problem in php to support arrays. we deprecated apache dumper where it would probably be tedious to support arrays. so thats not a problem. also the xml loader already explicitly supported null, so also supporting other scalars like int makes sense. fabien, what do you prefer?

@fabpot
Copy link
Member

fabpot commented Oct 7, 2014

The support for non-string values in other formats was not a conscious decision; it just happens to work because there is no restrictions in place. So, I'm still against this change.

@Tobion
Copy link
Contributor

Tobion commented Oct 7, 2014

Well actually it was, at least for null: #7635

@Tobion
Copy link
Contributor

Tobion commented Oct 8, 2014

And according to RequestContext::setParameter a parameter can be mixed.

@fabpot
Copy link
Member

fabpot commented Jun 22, 2016

Re-reading this old PR, I still thing that defaults should only be strings or null. When parsing a path, you can only get back strings anyway, so allowing defaults to be something else seems wrong to me. I'm in favor of deprecating the possibility to use any other kind of value.

@Tobion
Copy link
Contributor

Tobion commented Jun 22, 2016

One point of routing is that you can change URIs without chaning the code to parse/generate them. Since array params can be used for query strings, at one point it would also make sense to allow arrays in the path. For example, URI templates standard supports that by expanding arrays to comma-separated values.
So removing non-string defaults would be a step in the wrong direction IMO.

@Tobion
Copy link
Contributor

Tobion commented Jun 22, 2016

Also with the introduction of scalar type hints, what if a controller parameter is typehinted to int for example? I guess this will be cast implicitly at the moment. But with strict types, it would cause an error. So in theory either the router itself could cast params (for example based on the requirement) or the code that calls the controller. So then route params would need to be non-strings as well.

@fabpot
Copy link
Member

fabpot commented Jun 22, 2016

ok, you're right. Let's finish this one then.

@stof
Copy link
Member

stof commented Jun 22, 2016

@Tobion strict types are irrelevant here, because the strict mode for arguments is determined by the calling code, not by the file declaring the controller, and the Kernel class (which is calling the controller) is not in strict mode

@Tobion
Copy link
Contributor

Tobion commented Jun 22, 2016

@stof I know. But who says Symfony 4 will not add scalar type hints in certain places and maybe strict types?

@xabbuh xabbuh force-pushed the route-collection-defaults-xml branch from 72a3f87 to 9eb6682 Compare June 23, 2016 12:26
As pointed out in symfony/symfony-docs#4017, the XmlFileLoader was not
capable of defining array default values. Additionally, this commit
adds support for handling associative arrays, boolean, integer, float
and string data types.
@xabbuh xabbuh force-pushed the route-collection-defaults-xml branch from 9eb6682 to 120b35c Compare June 23, 2016 12:28
@xabbuh
Copy link
Member Author

xabbuh commented Jun 23, 2016

rebased here, if I don't miss anything all comments made so far have already been addressed

@fabpot
Copy link
Member

fabpot commented Jun 23, 2016

Thank you @xabbuh.

@fabpot fabpot merged commit 120b35c into symfony:master Jun 23, 2016
fabpot added a commit that referenced this pull request Jun 23, 2016
…xabbuh)

This PR was merged into the 3.2-dev branch.

Discussion
----------

[Routing] support for array values in route defaults

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

As pointed out in symfony/symfony-docs#4017, the ``XmlFileLoader`` was not capable of defining array default values.

- [x] array values
- [x] integer values
- [x] float values
- [x] boolean

Commits
-------

120b35c [Routing] data type support for defaults
@xabbuh xabbuh deleted the route-collection-defaults-xml branch June 23, 2016 13:38
@Tobion
Copy link
Contributor

Tobion commented Jun 23, 2016

Maybe we should rename boolean and integer elements to bool and int elements as the short names are now more standard considering scalar typehints etc.

fabpot added a commit that referenced this pull request Jul 1, 2016
…bbuh)

This PR was merged into the 3.2-dev branch.

Discussion
----------

[Routing] rename boolean and integer to bool and int

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #11394 (comment)
| License       | MIT
| Doc PR        |

This addresses @Tobion's comment in #11394 (comment).

@symfony/deciders What do you think? Should we change the element names to how we use the types in PHP code or should we stick with XML element names that rather match the types as defined by the XML schema?

Commits
-------

808dcc8 rename boolean and integer to bool and int
@fabpot fabpot mentioned this pull request Oct 27, 2016
xabbuh added a commit to xabbuh/symfony that referenced this pull request May 23, 2017
This was initially removed in symfony#13361 and accidentally added again
in symfony#11394.
fabpot added a commit that referenced this pull request May 24, 2017
This PR was merged into the 3.2 branch.

Discussion
----------

[Routing] remove an unused routing fixture

| Q             | A
| ------------- | ---
| Branch?       | 3.2
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

This was initially removed in #13361 and accidentally added again
in #11394.

Commits
-------

6f67221 [Routing] remove an unused routing fixture
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants