Skip to content
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

FEATURE: Support search by property & exact value in NodeDataRepository #1

Merged
merged 11 commits into from
Apr 14, 2016
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ class NodeSearchService implements NodeSearchServiceInterface
*
* TODO: Implement a better search when Flow offer the possibility
*
* @param string $term
* @param string|array $term search term
* @param array $searchNodeTypes
* @param Context $context
* @param NodeInterface $startingPoint
* @return array <\TYPO3\TYPO3CR\Domain\Model\NodeInterface>
*/
public function findByProperties($term, array $searchNodeTypes, Context $context, NodeInterface $startingPoint = null)
{
if (strlen($term) === 0) {
if (empty($term)) {
throw new \InvalidArgumentException('"term" cannot be empty: provide a term to search for.', 1421329285);
}
$searchResult = array();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,7 @@ public function findOnPath($pathStartingPoint, $pathEndPoint, Workspace $workspa
*
* This method is internal and will be replaced with better search capabilities.
*
* @param string $term Search term
* @param string|array $term Search term
* @param string $nodeTypeFilter Node type filter
* @param Workspace $workspace
* @param array $dimensions
Expand All @@ -925,7 +925,7 @@ public function findOnPath($pathStartingPoint, $pathEndPoint, Workspace $workspa
public function findByProperties($term, $nodeTypeFilter, $workspace, $dimensions, $pathStartingPoint = null)
{
$pathStartingPoint = strtolower($pathStartingPoint);
if (strlen($term) === 0) {
if (empty($term)) {
throw new \InvalidArgumentException('"term" cannot be empty: provide a term to search for.', 1421329285);
}
$workspaces = array();
Expand All @@ -937,8 +937,19 @@ public function findByProperties($term, $nodeTypeFilter, $workspace, $dimensions
$queryBuilder = $this->createQueryBuilder($workspaces);
$this->addDimensionJoinConstraintsToQueryBuilder($queryBuilder, $dimensions);
$this->addNodeTypeFilterConstraintsToQueryBuilder($queryBuilder, $nodeTypeFilter);
// Convert to lowercase, then to json, and then trim quotes from json to have valid JSON escaping.
$likeParameter = '%' . trim(json_encode(UnicodeFunctions::strtolower($term), JSON_UNESCAPED_UNICODE), '"') . '%';

if (is_array($term)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This would only work for a single element array, right? Either we support multiple keys by iterating over each key and add multiple (ANDed) LIKE constraints or we throw an exception here, so the caller know exactly that this is not supported yet (instead of just finding no results or just by chance of ordering in the JSON blob - shudder).

if (count($term) !== 1) {
throw new \InvalidArgumentException('Currently only a 1-dimensional key => value array term is supported.', 1460437584);
}

// Build the like parameter as "key": "value" to search by a specific key and value
$likeParameter = '%' . UnicodeFunctions::strtolower(trim(json_encode($term, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE), "{}\n\t ")) . '%';
} else {
// Convert to lowercase, then to json, and then trim quotes from json to have valid JSON escaping.
$likeParameter = '%' . trim(json_encode(UnicodeFunctions::strtolower($term), JSON_UNESCAPED_UNICODE), '"') . '%';
}

$queryBuilder->andWhere("LOWER(CONCAT('', n.properties)) LIKE :term")->setParameter('term', $likeParameter);

if (strlen($pathStartingPoint) > 0) {
Expand Down Expand Up @@ -1177,6 +1188,7 @@ protected function addDimensionJoinConstraintsToQueryBuilder(QueryBuilder $query
* @param array $workspaces
* @param array $dimensions
* @return array Array of unique node results indexed by identifier
* @throws Exception\NodeException
*/
protected function reduceNodeVariantsByWorkspacesAndDimensions(array $nodes, array $workspaces, array $dimensions)
{
Expand Down
2 changes: 2 additions & 0 deletions TYPO3.TYPO3CR/Configuration/Testing/NodeTypes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
properties:
test1:
defaultValue: 'default value 1'
test2:
defaultValue: 'default value 2'
constraints:
nodeTypes:
'TYPO3.TYPO3CR.Testing:NodeType': TRUE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,62 @@ public function findNodesByRelatedEntitiesFindsExistingNodeWithMatchingEntityPro

$this->assertCount(1, $result);
}

/**
* @test
*/
public function findNodeByPropertySearch()
{
$this->createNodesForNodeSearchTest();

$result = $this->nodeDataRepository->findByProperties('simpleTestValue', 'TYPO3.TYPO3CR.Testing:NodeType', $this->liveWorkspace, $this->context->getDimensions());
$this->assertCount(2, $result);
$this->assertResultConsistsOfNodes($result, ['test-node-1', 'test-node-2']);
}

/**
* @test
*/
public function findNodesByPropertyKeyAndValue()
{
$this->createNodesForNodeSearchTest();

$result = $this->nodeDataRepository->findByProperties(array('test2' => 'simpleTestValue'), 'TYPO3.TYPO3CR.Testing:NodeType', $this->liveWorkspace, $this->context->getDimensions());
$this->assertCount(1, $result);
$this->assertEquals('test-node-2', array_shift($result)->getName());
}

/**
* @throws \TYPO3\TYPO3CR\Exception\NodeTypeNotFoundException
*/
protected function createNodesForNodeSearchTest()
{
$rootNode = $this->context->getRootNode();

$newNode1 = $rootNode->createNode('test-node-1', $this->nodeTypeManager->getNodeType('TYPO3.TYPO3CR.Testing:NodeType'));
$newNode1->setProperty('test1', 'simpleTestValue');

$newNode2 = $rootNode->createNode('test-node-2', $this->nodeTypeManager->getNodeType('TYPO3.TYPO3CR.Testing:NodeType'));
$newNode2->setProperty('test2', 'simpleTestValue');

$newNode2 = $rootNode->createNode('test-node-3', $this->nodeTypeManager->getNodeType('TYPO3.TYPO3CR.Testing:NodeType'));
$newNode2->setProperty('test1', 'otherValue');

$this->persistenceManager->persistAll();
}


/**
* @param array<\TYPO3\TYPO3CR\Domain\Model\NodeData> $result
* @param array $nodeNames
*/
protected function assertResultConsistsOfNodes($result, $nodeNames)
{
foreach ($result as $node) {
$this->assertTrue(in_array($node->getName(), $nodeNames), sprintf('The node with name %s was not part of the result.', $node->getName()));
unset($nodeNames[array_search($node->getName(), $nodeNames)]);
}

$this->assertCount(0, $nodeNames, sprintf('The expected node names %s were not part of the result', implode(',', $nodeNames)));
}
}