Skip to content

Commit

Permalink
Merge pull request doctrine#260 from doctrine/filter-params
Browse files Browse the repository at this point in the history
Support filter parameters in configuration
  • Loading branch information
jmikola committed Jul 2, 2014
2 parents a612b90 + b60ab63 commit f01a2ac
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 39 deletions.
13 changes: 13 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ private function addDocumentManagersSection(ArrayNodeDefinition $rootNode)
->arrayNode('filters')
->useAttributeAsKey('name')
->prototype('array')
->fixXmlConfig('parameter')
->beforeNormalization()
->ifString()
->then(function($v) { return array('class' => $v); })
Expand All @@ -117,6 +118,18 @@ private function addDocumentManagersSection(ArrayNodeDefinition $rootNode)
->children()
->scalarNode('class')->isRequired()->end()
->booleanNode('enabled')->defaultFalse()->end()
->arrayNode('parameters')
->treatNullLike(array())
->useAttributeAsKey('name')
->prototype('variable')
->beforeNormalization()
// Detect JSON object and array syntax (for XML)
->ifTrue(function($v) { return is_string($v) && (preg_match('/\[.*\]/', $v) || preg_match('/\{.*\}/', $v)); })
// Decode objects to associative arrays for consistency with YAML
->then(function($v) { return json_decode($v, true); })
->end()
->end()
->end()
->end()
->end()
->end()
Expand Down
3 changes: 2 additions & 1 deletion DependencyInjection/DoctrineMongoDBExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ protected function loadDocumentManager(array $documentManager, $defaultDM, $defa

$enabledFilters = array();
foreach ($documentManager['filters'] as $name => $filter) {
$odmConfigDef->addMethodCall('addFilter', array($name, $filter['class']));
$parameters = isset($filter['parameters']) ? $filter['parameters'] : array();
$odmConfigDef->addMethodCall('addFilter', array($name, $filter['class'], $parameters));
if ($filter['enabled']) {
$enabledFilters[] = $name;
}
Expand Down
11 changes: 11 additions & 0 deletions Resources/config/schema/mongodb-1.0.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,22 @@
</xsd:complexType>

<xsd:complexType name="filter">
<xsd:sequence>
<xsd:element name="parameter" type="filter-parameter" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="class" type="xsd:string" use="required" />
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>

<xsd:complexType name="filter-parameter">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>

<xsd:complexType name="mapping">
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="alias" type="xsd:string" />
Expand Down
70 changes: 56 additions & 14 deletions Resources/doc/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,23 +164,65 @@ The following configuration shows a bunch of mapping examples:
Filters
~~~~~~~

You can easily add filters to a document manager by using the
following syntax:
Filter classes may be used in order to add criteria to ODM queries, regardless
of where those queries are created within your application. Typically, filters
will limit themselves to operating on a particular class or interface. Filters
may also take parameters, which can be used to customize the injected query
criteria.

.. code-block:: yaml
Filters may be registered with a document manager by using the following syntax:

doctrine_mongodb:
document_managers:
default:
filters:
filter-one:
class: Class\ExampleOne\Filter\ODM\ExampleFilter
enabled: true
filter-two:
class: Class\ExampleTwo\Filter\ODM\ExampleFilter
enabled: false
.. configuration-block::

.. code-block:: yaml
doctrine_mongodb:
document_managers:
default:
filters:
basic_filter:
class: Vendor\Filter\BasicFilter
enabled: true
complex_filter:
class: Vendor\Filter\ComplexFilter
enabled: false
parameters:
author: bob
comments: { $gte: 10 }
tags: { $in: [ 'foo', 'bar' ] }
.. code-block:: xml
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:doctrine="http://symfony.com/schema/dic/doctrine/odm/mongodb"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/doctrine/odm/mongodb http://symfony.com/schema/dic/doctrine/odm/mongodb/mongodb-1.0.xsd">
<doctrine:mongodb>
<doctrine:connection id="default" server="mongodb://localhost:27017" />
<doctrine:document-manager id="default" connection="default">
<doctrine:filter name="basic_filter" enabled="true" class="Vendor\Filter\BasicFilter" />
<doctrine:filter name="complex_filter" enabled="true" class="Vendor\Filter\ComplexFilter">
<doctrine:parameter name="author">bob</doctrine:parameter>
<doctrine:parameter name="comments">{ "$gte": 10 }</doctrine:parameter>
<doctrine:parameter name="tags">{ "$in": [ "foo", "bar" ] }</doctrine:parameter>
</doctrine:filter>
</doctrine:document-manager>
</doctrine:mongodb>
</container>
.. note::

Filters are used to append conditions to the queryBuilder regardless of where the query is generated.
Unlike ORM, query parameters in MongoDB ODM may be non-scalar values. Since
such values are difficult to express in XML, the bundle allows JSON strings
to be used in ``parameter`` tags. While processing the configuration, the
bundle will run the tag contents through ``json_decode()`` if the string is
wrapped in square brackets or curly braces for arrays and objects,
respectively.

Multiple Connections
~~~~~~~~~~~~~~~~~~~~
Expand Down
111 changes: 93 additions & 18 deletions Tests/DependencyInjection/AbstractMongoDBExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
use Doctrine\Bundle\MongoDBBundle\DependencyInjection\DoctrineMongoDBExtension;
use Doctrine\Bundle\MongoDBBundle\Tests\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use PHPUnit_Framework_AssertionFailedError;
use PHPUnit_Framework_Constraint;

abstract class AbstractMongoDBExtensionTest extends TestCase
{
Expand Down Expand Up @@ -384,7 +387,7 @@ public function testDependencyInjectionImportsOverrideDefaults()

public function testResolveTargetDocument()
{
$container = $this->getContainer('YamlBundle');
$container = $this->getContainer();
$loader = new DoctrineMongoDBExtension();
$container->registerExtension($loader);

Expand All @@ -395,35 +398,107 @@ public function testResolveTargetDocument()
$container->compile();

$definition = $container->getDefinition('doctrine_mongodb.odm.listeners.resolve_target_document');
$this->assertDICDefinitionMethodCallOnce($definition, 'addResolveTargetDocument', array('Symfony\Component\Security\Core\User\UserInterface', 'MyUserClass', array()));
$this->assertDefinitionMethodCallOnce($definition, 'addResolveTargetDocument', array('Symfony\Component\Security\Core\User\UserInterface', 'MyUserClass', array()));
$this->assertEquals(array('doctrine_mongodb.odm.event_listener' => array(array('event' => 'loadClassMetadata'))), $definition->getTags());
}

public function testFilters()
{
$container = $this->getContainer();
$loader = new DoctrineMongoDBExtension();
$container->registerExtension($loader);

$this->loadFromFile($container, 'odm_filters');

$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->compile();

$complexParameters = array(
'integer' => 1,
'string' => 'foo',
'object' => array('key' => 'value'),
'array' => array(1, 2, 3),
);

$definition = $container->getDefinition('doctrine_mongodb.odm.default_configuration');
$this->assertDefinitionMethodCallAny($definition, 'addFilter', array('disabled_filter', 'Vendor\Filter\DisabledFilter', array()));
$this->assertDefinitionMethodCallAny($definition, 'addFilter', array('basic_filter', 'Vendor\Filter\BasicFilter', array()));
$this->assertDefinitionMethodCallAny($definition, 'addFilter', array('complex_filter', 'Vendor\Filter\ComplexFilter', $complexParameters));

$enabledFilters = array('basic_filter', 'complex_filter');

$definition = $container->getDefinition('doctrine_mongodb.odm.default_manager_configurator');
$this->assertEquals($enabledFilters, $definition->getArgument(0), 'Only enabled filters are passed to the ManagerConfigurator.');
}

/**
* Asserts that the given definition contains a call to the method that uses
* the specified parameters.
*
* @param Definition $definition
* @param string $methodName
* @param array $params
*/
private function assertDefinitionMethodCallAny(Definition $definition, $methodName, array $params)
{
$calls = $definition->getMethodCalls();
$called = false;
$lastError = null;

foreach ($calls as $call) {
if ($call[0] !== $methodName) {
continue;
}

$called = true;

try {
$this->assertSame($params, $call[1], "Expected parameters to method '" . $methodName . "' did not match the actual parameters.");
return;
} catch (PHPUnit_Framework_AssertionFailedError $e) {
$lastError = $e;
}
}

if ( ! $called) {
$this->fail("Method '" . $methodName . "' is expected to be called, but it was never called.");
}

if ($lastError) {
throw $lastError;
}
}

/**
* Assertion for the DI Container, check if the given definition contains a method call with the given parameters.
* Asserts that the given definition contains exactly one call to the method
* and that it uses the specified parameters.
*
* @param \Symfony\Component\DependencyInjection\Definition $definition
* @param string $methodName
* @param array $params
* @param Definition $definition
* @param string $methodName
* @param array $params
*/
protected function assertDICDefinitionMethodCallOnce($definition, $methodName, array $params = null)
private function assertDefinitionMethodCallOnce(Definition $definition, $methodName, array $params)
{
$calls = $definition->getMethodCalls();
$called = false;

foreach ($calls as $call) {
if ($call[0] == $methodName) {
if ($called) {
$this->fail("Method '" . $methodName . "' is expected to be called only once, a second call was registered though.");
} else {
$called = true;
if ($params !== null) {
$this->assertEquals($params, $call[1], "Expected parameters to methods '" . $methodName . "' do not match the actual parameters.");
}
}
if ($call[0] !== $methodName) {
continue;
}

if ($called) {
$this->fail("Method '" . $methodName . "' is expected to be called only once, but it was called multiple times.");
}

$called = true;

$this->assertEquals($params, $call[1], "Expected parameters to method '" . $methodName . "' did not match the actual parameters.");
}
if (!$called) {
$this->fail("Method '" . $methodName . "' is expected to be called once, definition does not contain a call though.");

if ( ! $called) {
$this->fail("Method '" . $methodName . "' is expected to be called once, but it was never called.");
}
}

Expand Down
20 changes: 18 additions & 2 deletions Tests/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,25 @@ public function testFullConfiguration($config)
'logging' => '%kernel.debug%',
'auto_mapping' => false,
'filters' => array(
'test_filter' => array(
'class' => 'TestClass',
'disabled_filter' => array(
'class' => 'Vendor\Filter\DisabledFilter',
'enabled' => false,
'parameters' => array(),
),
'basic_filter' => array(
'class' => 'Vendor\Filter\BasicFilter',
'enabled' => true,
'parameters' => array(),
),
'complex_filter' => array(
'class' => 'Vendor\Filter\ComplexFilter',
'enabled' => true,
'parameters' => array(
'integer' => 1,
'string' => 'foo',
'object' => array('key' => 'value'),
'array' => array(1, 2, 3),
),
),
),
'metadata_cache_driver' => array(
Expand Down
9 changes: 8 additions & 1 deletion Tests/DependencyInjection/Fixtures/config/xml/full.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,14 @@
<doctrine:instance-class>instance_val</doctrine:instance-class>
</doctrine:metadata-cache-driver>
<doctrine:profiler enabled="true" pretty="false" />
<doctrine:filter name="test_filter" enabled="true" class="TestClass" />
<doctrine:filter name="disabled_filter" enabled="false" class="Vendor\Filter\DisabledFilter" />
<doctrine:filter name="basic_filter" enabled="true" class="Vendor\Filter\BasicFilter" />
<doctrine:filter name="complex_filter" enabled="true" class="Vendor\Filter\ComplexFilter">
<doctrine:parameter name="integer">1</doctrine:parameter>
<doctrine:parameter name="string">foo</doctrine:parameter>
<doctrine:parameter name="object">{"key":"value"}</doctrine:parameter>
<doctrine:parameter name="array">[1,2,3]</doctrine:parameter>
</doctrine:filter>
</doctrine:document-manager>

<doctrine:document-manager
Expand Down
24 changes: 24 additions & 0 deletions Tests/DependencyInjection/Fixtures/config/xml/odm_filters.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:doctrine="http://symfony.com/schema/dic/doctrine/odm/mongodb"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/doctrine/odm/mongodb http://symfony.com/schema/dic/doctrine/odm/mongodb/mongodb-1.0.xsd">

<doctrine:mongodb>
<doctrine:connection id="default" server="mongodb://localhost:27017" />

<doctrine:document-manager id="default" connection="default">
<doctrine:filter name="disabled_filter" enabled="false" class="Vendor\Filter\DisabledFilter" />
<doctrine:filter name="basic_filter" enabled="true" class="Vendor\Filter\BasicFilter" />
<doctrine:filter name="complex_filter" enabled="true" class="Vendor\Filter\ComplexFilter">
<doctrine:parameter name="integer">1</doctrine:parameter>
<doctrine:parameter name="string">foo</doctrine:parameter>
<doctrine:parameter name="object">{"key":"value"}</doctrine:parameter>
<doctrine:parameter name="array">[1,2,3]</doctrine:parameter>
</doctrine:filter>
</doctrine:document-manager>

</doctrine:mongodb>
</container>
17 changes: 14 additions & 3 deletions Tests/DependencyInjection/Fixtures/config/yml/full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,20 @@ doctrine_mongodb:
enabled: true
pretty: false
filters:
test-filter:
class: TestClass
enabled: true
disabled_filter:
class: Vendor\Filter\DisabledFilter
enabled: false
basic_filter:
class: Vendor\Filter\BasicFilter
enabled: true
complex_filter:
class: Vendor\Filter\ComplexFilter
enabled: true
parameters:
integer: 1
string: foo
object: { key: value }
array: [ 1, 2, 3 ]
dm2:
connection: dm2_connection
database: db1
Expand Down
21 changes: 21 additions & 0 deletions Tests/DependencyInjection/Fixtures/config/yml/odm_filters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
doctrine_mongodb:
connections:
default:
server: mongodb://localhost:27017
document_managers:
default:
filters:
disabled_filter:
class: Vendor\Filter\DisabledFilter
enabled: false
basic_filter:
class: Vendor\Filter\BasicFilter
enabled: true
complex_filter:
class: Vendor\Filter\ComplexFilter
enabled: true
parameters:
integer: 1
string: foo
object: { key: value }
array: [ 1, 2, 3 ]

0 comments on commit f01a2ac

Please sign in to comment.