-
-
Notifications
You must be signed in to change notification settings - Fork 93
feat: Rector rules to help migrating away from proxy #941
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
base: feat/proxy-php84
Are you sure you want to change the base?
feat: Rector rules to help migrating away from proxy #941
Conversation
c2eb0fd
to
ee7a8b2
Compare
ee7a8b2
to
85ad1d4
Compare
fcea7d0
to
3ab1e1f
Compare
3ab1e1f
to
8f02e9a
Compare
77b96e2
to
878421d
Compare
/** | ||
* @param MethodCall $node | ||
*/ | ||
public function refactor(Node $node) : ?Node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's this one, right?
I'll make notes as it comes to my mind:
- I'd avoid configuration and use values here instead. The fluent call has to be converted fully, to its split functoins.
- If you want to return multiple stmts = standalone lines with function calls, you need to hook into and
Expression
node. That way we're working with lines, and can return multiple lines. - The
Expression
node must contain$node->expr
ofMethodCall
instance, then you create functions and return them all.
First notes to kick off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, thanks for your help :)
To give you some context, the Proxy mechanism in Foundry is clunky, and PHP 8.4 lazy objects gives us a way to drastically improve this. So I want to remove everything dealing with proxies. We have functions helper that can replace each method from Proxy
class.
I think I'll create a dedicated rector for this case: the other fluent methods will be replaced with functions that actually return the object, so, we could mimic the fluent syntax with them.
eg: $object->_save()->_save()
will become save(save($object))
.
Sadly, the method Proxy::_set()
is the more complex one to replace, and is one of the most used in the wild. If it wasn't the case, I would have let the user deal with edge cases by themselves: this Rector set is a "best effort" thing, they will have to fix stuff manually anyway... More over, not dealing with this specific method will lead to broken code after the Rector set has been run.
If you want to return multiple stmts = standalone lines with function calls, you need to hook into and Expression node. That way we're working with lines, and can return multiple lines.
yes, that's what I finally understood 😅 but dealing with Expression
is a lot more challenging.
if the method I want to remove is in the middle of the expression (eg: $proxy->_save()->_set()->_save()
or $someService->doSomething($proxy->_set()->save())
), I don't know how to deal with it. Is there some special "expression traverser" before I dive into a solution using recursion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code example you gave is pretty clear, thanks
How would you handle the case where you need to use a new variable, I know this pattern is widly used as well:
- SomeFactory::createOne()
- ->_set('prop1', 'value1')
- ->_save()
- ->_set('prop2', 'value2')
- ->_save()
- ->_real();
+ $object = SomeFactory::createOne();
+ Zenstruck\Foundry\set($object, 'prop1', 'value1');
+ $object = $object->save();
+ Zenstruck\Foundry\set($object, 'prop2', 'value2');
+ $object->save()->_real();
is there some helper to generate unused var names?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you handle the case where you need to use a new variable,
If there is convention of naming factories by their entity, I'd simply use the prefix from SomeFactory
→ Some
:)
if the method I want to remove is in the middle of the expression (eg: $proxy->_save()->_set()->_save() or $someService->doSomething($proxy->_set()->save())),
Removing node in the middle of the chain is mental hack. Nested calls work in inversed order.
So:
$some->first()->second()->third();
Is third()
, second()
, then first()
in the AST tree. This might help:
https://getrector.com/ast/008d8f973cd69fa967a1aaaf5c8c04459fd3ea34
Removing a node somewhere in the middle is done like this:
// here $node->var, is `MethodCall` of second()`
// to remove if, just assign it the node above (third())
$node->var = $node;
/** | ||
* @param MethodCall $node | ||
*/ | ||
public function refactor(Node $node) : ?Node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breif example:
if (! $node->expr instanceof MethodCall) {
return null;
}
$stmtsToReturn = [];
$currentMethodCall = $node->expr;
do { ...
// create func call here
$funcCall = new FuncCall(...);
$stmtsToReturn[] = new Expression($funcCall);
// jump to next nested method call
$currentMethodCall = $currentMethodCall->var;
} while ($currentMethodCall instanceof MethodCall);
if ($stmtsToReturn === []) {
return null;
}
return $stmtsToReturn;
see #899 (comment)
I'm intentionally not supporting all "fluent" syntax, because they are way to complex to handle with Rector
ie: something like
proxy($object)->_save();
will be left untouchedThis is a "best effort" stuff, I think everything cannot be removed automatically, but this should make the heavy lifting
(I still need to test it on a big project)