Skip to content

[RFR] Issues/1246 #1324

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 8 commits into from
Nov 9, 2017
Merged

[RFR] Issues/1246 #1324

merged 8 commits into from
Nov 9, 2017

Conversation

vincentchalamon
Copy link
Contributor

@vincentchalamon vincentchalamon commented Aug 22, 2017

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #1246, #1323 (comment)
License MIT
Doc PR api-platform/docs#269

$field = $order;
$order = 'ASC';
}
$queryBuilder->addOrderBy('o.'.$field, $order);
if (false === strstr($field, '.')) {
Copy link
Member

Choose a reason for hiding this comment

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

Can't you use strpos and store the result in a variable to use it if it doesn't return false? It will remove one function call.

// Ensure query join is done on association
$associationName = substr($field, 0, $pos);
if (!in_array($associationName, $queryBuilder->getRootAliases(), true)) {
$queryBuilder->join('o.'.$associationName, $associationName);
Copy link
Member

Choose a reason for hiding this comment

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

No conflicts with src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php#L117-L124 here (extensions priorities)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dunglas WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Can you use the JoinOnce thing? This may lead to multiple joins indeed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@soyuka What do you mean? Do you have any example please?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe this:

protected function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $association): string
{
$join = $this->getExistingJoin($queryBuilder, $alias, $association);
if (null === $join) {
$associationAlias = $queryNameGenerator->generateJoinAlias($association);
if (true === QueryChecker::hasLeftJoin($queryBuilder)) {
$queryBuilder
->leftJoin(sprintf('%s.%s', $alias, $association), $associationAlias);
} else {
$queryBuilder
->innerJoin(sprintf('%s.%s', $alias, $association), $associationAlias);
}
} else {
$associationAlias = $join->getAlias();
}
return $associationAlias;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you think about a QueryBuilderHelper, which can be shared with filters & extensions?

Copy link
Member

Choose a reason for hiding this comment

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

Would be nice yeah, maybe we can do this in another PR!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I better think it could be done in this PR as its totally related to it. I could create this QueryBuilderHelper and move addJoinsForNestedProperty, getExistingJoin & addJoinOnce methods in it.

But I'm not sure it's the best approach, I think it should be better to call these methods on the QueryBuilder object directly instead of using an external service which modify the query builder object. Something like $queryBuilder->addJoinOnce instead of QueryBuilderHelper::addJoinOnce($queryBuilder), wdyt @soyuka?

Copy link
Member

Choose a reason for hiding this comment

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

The helper (as long as it is marked @internal) looks good to me. The QueryBuilder is provided by Doctrine, it's not really wanted to modify it.

Copy link
Member

Choose a reason for hiding this comment

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

I'd keep static helpers instead of extending the QueryBuilder.

@vincentchalamon vincentchalamon changed the title Issues/1246 [WIP] Issues/1246 Aug 30, 2017
@vincentchalamon vincentchalamon changed the title [WIP] Issues/1246 [RFR] Issues/1246 Sep 18, 2017
*
* @return string the new association alias
*/
public static function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $association, ?string $joinType = null, ?string $conditionType = null, ?string $condition = null): string
Copy link
Member

Choose a reason for hiding this comment

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

You can't use the ?string syntax because we ensure compatibility with PHP 7.0 (this syntax was introduced in 7.1).

/** @var Join $joinPart */
$joinString = str_replace($aliases, $replacements, $joinPart->getJoin());
$alias = substr($joinString, 0, strpos($joinString, '.'));
$association = substr($joinString, strpos($joinString, '.')+1);
Copy link
Member

Choose a reason for hiding this comment

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

can we avoid multiple calls on strpos?

Do you think it's possible that strpos returns false here? Maybe we kinda need a test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@soyuka As it's from a cloned query builder, I don't think it's even possible that strpos could return false. It would mean that the original query builder is invalid, which report the bug to doctrine.

@@ -42,15 +41,13 @@ class Foo
* @var string The foo name
*
* @ORM\Column
* @Assert\NotBlank
Copy link
Member

Choose a reason for hiding this comment

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

Really not needed? I'd have think those are for a given behat test, if not np just remove them though it's not part of the fix (or is it?).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Originally, I created this Foo entity for my tests. It was copied/pasted from another one. But I think the assertion is useless on this entity as I only create the objects by myself without validation.
This modification (in this PR) is a clean :-)

@vincentchalamon vincentchalamon changed the base branch from master to 2.1 September 21, 2017 14:06
Copy link
Member

@meyerbaptiste meyerbaptiste left a comment

Choose a reason for hiding this comment

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

It would be nice to compare SQL queries before and after these changes to verify that they are identical. These ones seem to me very sensitive...

*
* @internal
*/
final class QueryBuilderHelper
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a private constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why for?

Copy link
Member

Choose a reason for hiding this comment

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

Because he likes private constructors :P.

Nah just that it's a convention when classes have no instances (ie are never called with new because they only have statics).

@vincentchalamon
Copy link
Contributor Author

It would be nice to compare SQL queries before and after these changes to verify that they are identical. These ones seem to me very sensitive...

@meyerbaptiste on which class: FilterEagerLoadingExtension or QueryBuilderHelper?

@soyuka
Copy link
Member

soyuka commented Oct 16, 2017

@meyerbaptiste He didn't changed the tests and we do test queries there.

{
$parts = $queryBuilder->getDQLPart('join');

if (!isset($parts['o'])) {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could put this o alias in some constant somewhere (in another PR).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It could be easily retried by $queryBuilder->getRootAliases()[0]. Should I replace it here?

Copy link
Member

Choose a reason for hiding this comment

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

don't IMO it's fine like this. It's just that we have a lot of these 'o' alias hard-coded in the bridge.

@soyuka
Copy link
Member

soyuka commented Nov 6, 2017

Would you mind fixing phpstan?

@vincentchalamon
Copy link
Contributor Author

@soyuka I don't understand where the error comes from in my code:

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
  Error                                                                                                                                                                                                                    
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
  Ignored error pattern #Parameter \#2 \$dqlPart of method Doctrine\\ORM\\QueryBuilder::add\(\) expects Doctrine\\ORM\\Query\\Expr\\Base, Doctrine\\ORM\\Query\\Expr\\Join\[\] given# was not matched in reported errors.  
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
                                                                                
 [ERROR] Found 1 error                                                          
                                                                                
The command "if [[ $lint = 1 ]]; then phpstan analyse -c phpstan.neon -l5 --ansi src tests; fi" exited with 1.

@soyuka
Copy link
Member

soyuka commented Nov 6, 2017

$queryNameGenerator->generateJoinAlias('item')->shouldBeCalled()->willReturn('item_2');
$queryNameGenerator->generateJoinAlias('label')->shouldBeCalled()->willReturn('label_2');
$queryNameGenerator->generateJoinAlias('compositeItem')->shouldBeCalled()->willReturn('item_2');
$queryNameGenerator->generateJoinAlias('compositeLabel')->shouldBeCalled()->willReturn('label_2');
Copy link
Member

Choose a reason for hiding this comment

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

This change is wrong, item and label are correct I think (at least according to failing tests).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nop, cause this call is made through QueryBuilderHelper::addJoinOnce from association name.

I don't understand why travis is failing…on a non-existing unit test 😮 (failing method testCompositeIdentifiersWithoutAssociation does not exist in FilterEagerLoadingExtensionTest)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll try to squash my changes to force travis to rebuild its environment

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rebase missing, I fixed it, thanks @soyuka

@soyuka
Copy link
Member

soyuka commented Nov 7, 2017

👍 waiting for some more review but this LGTM!

Copy link
Member

@dunglas dunglas left a comment

Choose a reason for hiding this comment

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

Some minor changes. 👍 otherwise

/**
* Adds a join to the queryBuilder if none exists.
*
* @param QueryBuilder $queryBuilder
Copy link
Member

Choose a reason for hiding this comment

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

You can remove most of these @param.

*/
public static function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $association, $joinType = null, $conditionType = null, $condition = null): string
{
$join = self::getExistingJoin($queryBuilder, $alias, $association);
Copy link
Member

Choose a reason for hiding this comment

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

What about:

if (null !== $join) {
    return $join->getAlias();
}

$associationAlias = $queryNameGenerator->generateJoinAlias($association);
$query = "$alias.$association";

if (Join::LEFT_JOIN === $joinType || QueryChecker::hasLeftJoin($queryBuilder)) {
    $queryBuilder->leftJoin($query, $associationAlias, $conditionType, $condition);
} else {
    $queryBuilder->innerJoin($query, $associationAlias, $conditionType, $condition);
}

return $associationAlias;

To reduce the complexity of this method.

/**
* Get the existing join from queryBuilder DQL parts.
*
* @param QueryBuilder $queryBuilder
Copy link
Member

Choose a reason for hiding this comment

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

All @param can be removed

@dunglas dunglas merged commit 5d54089 into api-platform:2.1 Nov 9, 2017
@dunglas
Copy link
Member

dunglas commented Nov 9, 2017

Thanks @vincentchalamon!

@vincentchalamon vincentchalamon deleted the issues/1246 branch November 9, 2017 13:24
hoangnd25 pushed a commit to hoangnd25/core that referenced this pull request Feb 23, 2018
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.

5 participants