Skip to content

Support multiple finder arguments#12787

Closed
robertpustulka wants to merge 19 commits intocakephp:4.xfrom
robertpustulka:finder-args
Closed

Support multiple finder arguments#12787
robertpustulka wants to merge 19 commits intocakephp:4.xfrom
robertpustulka:finder-args

Conversation

@robertpustulka
Copy link
Copy Markdown
Member

First take on implementing #12784.

Existing tests pass with very few changes.
I didin't want to update the docs and haven't added any new tests as I'm not sure if this is a good way forward.

@ADmad
Copy link
Copy Markdown
Member

ADmad commented Dec 7, 2018

With this change one won't be able to stack finder calls, that's sad.

Let's say one make's a custom finder findCustom(Query $query, Entity), you can't do:

$table->find('all', ['conditions' => '..'])->find('custom', $someEntity);

@robertpustulka
Copy link
Copy Markdown
Member Author

robertpustulka commented Dec 7, 2018

@ADmad why not? What makes it impossible with this implementation?

@ADmad
Copy link
Copy Markdown
Member

ADmad commented Dec 7, 2018

NVM, for some reason I remembered that the options used for 1st find call are passed down to subsequent calls but that's not the case.

We will have to add clarification to the docs that if 2nd argument for find() is array it will always be treated as options for query. Still lot of people will overlook this quirkiness 🙂.

@robertpustulka
Copy link
Copy Markdown
Member Author

We will have to add clarification to the docs that if 2nd argument for find() is array it will always be treated as options for query.

That's my main concern with this.

I was wondering that maybe we should remove this part: https://github.com/cakephp/cakephp/pull/12787/files#diff-d36d5650ddbf69668bdd04f31f4a7a42R2217

And set options on query explicitly in build-in finders that support options array. I imagine that this change would stay BC but would require people to update those custom finders that for some reason expect that the options are set on query (even though those still should be accessible from the argument).

@ADmad
Copy link
Copy Markdown
Member

ADmad commented Dec 7, 2018

...but would require people to update those custom finders...

I am not in favor of any change which requires updating custom finders. 4.0 is supposed to a be smooth upgrade from 3.x and finders are a very important and well used feature of the framework.

* @return \Cake\Datasource\QueryInterface
*/
public function find(string $type = 'all', $options = []);
public function find(string $type = 'all', ...$args);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Doc string needs to be updated too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@markstory from my initial comment:

I didin't want to update the docs and haven't added any new tests as I'm not sure if this is a good way forward.

$options = $query->getOptions();
if ($args) {
$options = $args[0];
if (is_array($options)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about ArrayAccess things? Those were in the old doc string type information.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Query::applyOptions(array $options) accepts array only. Looks like doc type info is wrong?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Possible. We may have not ever actually had checks that were array specific before either.

@robertpustulka
Copy link
Copy Markdown
Member Author

Does it still make sense, can I continue with that?

@dereuromark
Copy link
Copy Markdown
Member

I dont like the change tbh.

@robertpustulka
Copy link
Copy Markdown
Member Author

Why not?

@dereuromark
Copy link
Copy Markdown
Member

Let me ask this:

  • How do static analyzers work with this change compared to array with documented types of keys? Psalm for example can introspect even the types on those assoc arrays
  • How is the usage for IDEs here (for developers)? Can this easily be understood here on what arguments are available and how many are used etc?

@robertpustulka
Copy link
Copy Markdown
Member Author

@dereuromark I'm not able to answer your questions but let it be clear: this is not a breaking change. You can still use options array as before. This change allows you to pass anything to the find() method after a finder name.

For me personally being able to leverage PHP 7 type checking instead doing it manually in every finder would be a great feature and I'd be willing to sacrifice a bit of code discoverability. But this change does not force you to write finders the new way, it just opens a path for being able to write less error-prone code.

@robertpustulka robertpustulka changed the title WIP: Support multiple finder arguments Support multiple finder arguments Apr 30, 2019
Copy link
Copy Markdown
Member

@ADmad ADmad left a comment

Choose a reason for hiding this comment

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

The fact that when 2nd argument to Table::find() is an array it's treated as a special case to maintain BC is my only concern with this change. I hope that doesn't trip users.

Also shouldn't BehaviorRegistry::callFinder() also be updated similar to Table::classFinder()?

public function find(string $finder, array $options = []);

public function find(string $finder, ...$args);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks like whitespace at end of line.

{
/** @var \Cake\ORM\Table $table */
$table = $this->getRepository();
if (!$args) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is sad. Those who have overridden find() in their models won't realize they will have to add similar if block to their method.

Copy link
Copy Markdown
Member Author

@robertpustulka robertpustulka Jul 17, 2019

Choose a reason for hiding this comment

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

What is a chance that someone overridden ORM Query::find() and used it instead? Cake\ORM\Query is more like internal class, isn't it?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not much chance of overridden Query::find(). But I see similar if block in Table::find() as well a table class of test app which has overridden Table::find().

{
$query->applyOptions($options);
$options = $query->getOptions();
if ($args) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps use if (isset($args[0]) && is_array($args[0])) { and avoid the nested if below?

@dereuromark
Copy link
Copy Markdown
Member

ping @robertpustulka
Do you still want to land this into 4.x?

@robertpustulka
Copy link
Copy Markdown
Member Author

Yes. Does it look OK as it is or do we need another approach? I'd like to know before I fix conflicts and add tests.

@othercorey
Copy link
Copy Markdown
Contributor

I like the idea of this, but the interface is awkward now.

I would prefer to see query options pulled out as a separate parameter. Treating the first argument in a variable list as the query options parameter causes a lot of one-off and note style documentation that is hard to read.

The fact that they were merged with other general table-specific finder options is more or less a miracle that just happens to work and feels nicely hidden because everyone can just read examples or docs of a single array argument ... there's never a special case where someone reads an example without an options array as the first argument.

@robertpustulka
Copy link
Copy Markdown
Member Author

@othercorey

I would prefer to see query options pulled out as a separate parameter. Treating the first argument in a variable list as the query options parameter causes a lot of one-off and note style documentation that is hard to read.

I'm not sure I understand you, but you can pass anything to the query with the proposed interface. The first argument is treated as query options by default only for the BC.

Eg:

$this->find('foo', ['foo' => 'bar']); //the BC way
$this->find('bar', $foo, $bar); // $foo and $bar ca be anything and get passed to the finder as below:

public function findBar($query, $foo, $bar) {}

The arrayish options can still be pulled from the query object with $query->getOptions().

I agree that the interface in the 3.x was a bit hacky with mixed finder and general options but this have been done to keep the BC with 2.x, where you called finders like:

$this->Articles->find('all', [
    'conditons' => [...],
]);

Maybe it's too late now to do this for 4.x, but maybe with 5.0 we should remove the "special" options that are proxied to the query methods (like conditions, fields etc)?

@othercorey
Copy link
Copy Markdown
Contributor

I'm not sure I understand you, but you can pass anything to the query with the proposed interface. The first argument is treated as query options by default only for the BC.

I'm saying the backwards compatible part is what makes this extremely awkward.

@robertpustulka
Copy link
Copy Markdown
Member Author

@othercorey OK. Do you have an idea how to handle this more conveniently?

@othercorey
Copy link
Copy Markdown
Contributor

othercorey commented Dec 7, 2019

@othercorey OK. Do you have an idea how to handle this more conveniently?

I would require calling a reserved findXXX() method with an options parameter to start the find chain.

findByArray($options)->find('custom', $param1, $param2)
  • findByArray() should be a declared method, not a magic method.
  • Require the $options array passed to findByArray() to only contain supported keys and throw a deprecation warning when it contains more.
  • Require $param1 to not contain the findByArray() $option keys and throw a deprecation warning when it contains them.

This will enforce splitting the query options from find parameters while letting the user know they need to upgrade.

I'd prefer to just throw exceptions, but depending when the change is made, you can throw deprecation warning.

@robertpustulka
Copy link
Copy Markdown
Member Author

With this solution you'd need to locate and replace all find('all', [..]) calls in existing code with findByArray(). I'm afraid that would be an overkill.

I have another idea - maybe we should have a new find method (both on repository and query) to support the new API? This way we could still use the old API with option array and the new one with variadic arguments. This is can be introduced in a minor release without breaking people's code.

The new method could be named findBy so this stays in line with old findByFieldName convention?

$this->findBy('foo', $object1, $object2)->find('bar', $array);

@othercorey
Copy link
Copy Markdown
Contributor

How is that any different? The finder names will conflict with existing methods.

@robertpustulka
Copy link
Copy Markdown
Member Author

How would they conflict?

@othercorey
Copy link
Copy Markdown
Contributor

With this solution you'd need to locate and replace all find('all', [..]) calls in existing code with findByArray(). I'm afraid that would be an overkill.

Only for those that pass in query options.

@robertpustulka
Copy link
Copy Markdown
Member Author

robertpustulka commented Dec 9, 2019

Only for those that pass in query options.

Yes, but that is still a big breaking change in all community plugins and userland code. Please note that this applies not only to the pre 3.x find calls with conditions etc options but to all finders including custom ones that use option arrays.

@othercorey
Copy link
Copy Markdown
Contributor

Yes, but that is still a big breaking change in all community plugins and userland code. Please note that this applies not only to the pre 3.x find calls with conditions etc options but to the all finders including custom ones that use option arrays.

Well, you can fix the mess or not, lol.

@robertpustulka
Copy link
Copy Markdown
Member Author

I don't think that it's a mess. I don't even think that option arrays are inconvenience, because a lot of people like "named" arguments for options. With #12784 I just wanted to introduce more convenient way of passing objects into finders while leveraging built in PHP7.1+ type checking.

@othercorey
Copy link
Copy Markdown
Contributor

Just offering my opinion that mixing query options + custom finder options in an array that's treated as a special parameter in variadic magic finder methods is really awkward.

But, I it isn't up to me.

@robertpustulka
Copy link
Copy Markdown
Member Author

OK I see that's awkward but it had to be done one way or the other to keep the BC. But now I realise that the variadic parameter support can be introduced as a new method without breaking the existing API and the BC.

I'll come up with a new PR soon but this will probably target 4.1 now.

@ADmad please remind me - we can still introduce new method to interfaces in minors with 4.x?

@dereuromark
Copy link
Copy Markdown
Member

Usually you would soft add new methods through docblock.

@ADmad
Copy link
Copy Markdown
Member

ADmad commented Dec 9, 2019

please remind me - we can still introduce new method to interfaces in minors with 4.x?

We can't update the interface in minor. But we could add the new method to Table class and add annotation to the RepositoryInterface as a compromise.

@markstory markstory modified the milestones: 4.0.0, 4.1.0 Dec 16, 2019
@ADmad
Copy link
Copy Markdown
Member

ADmad commented Dec 30, 2019

@robertpustulka Instead of making find() (or a new method) support variadic args why not just add a findX() method to your table/behavior with the arguments you want? That would also allow you to properly typehint the args.

While Cake conventionally uses methods named like findX() as custom finder and expects a specific signature, it doesn't really matter if you don't try to use your custom methods as finders. Or you can simply chose to not use find prefix.

@robertpustulka
Copy link
Copy Markdown
Member Author

I can’t chain finders using custom method.

@ADmad
Copy link
Copy Markdown
Member

ADmad commented Dec 30, 2019

True that. If only the ORM required Query::all() to be called explicitly, then Query::__call() could be changed to proxy to repository methods instead of resultset decorator methods 🙂.

@markstory markstory closed this Feb 11, 2020
@markstory
Copy link
Copy Markdown
Member

I didn't mean for this to get closed, I was removing the now stale 4.x branch. If you could open the pull request against master that would be appreciated as I cannot change the base branch now.

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