diff --git a/manuscript-src/03-programmatically-modifying-php-code.md b/manuscript-src/03-programmatically-modifying-php-code.md index 72717f9..dc33f58 100644 --- a/manuscript-src/03-programmatically-modifying-php-code.md +++ b/manuscript-src/03-programmatically-modifying-php-code.md @@ -37,10 +37,19 @@ Looking for occurrences in the repository class alone is not enough. If the repo Of course, we all have smart IDEs like PhpStorm, which make this kind of refactoring very easy and relatively safe for us. What if we don't want to make this change in an IDE. What if we want to do it with a PHP script? What's the best way to make the change? The first thing to try is some kind of global find and replace action, possibly using a function like `str_replace()`. -This most likely results in a number of wrong changes. For example: +This most likely results in a number of wrong changes: -- If we're not careful about upper versus lower case characters, a class called `Finder` would be renamed to `getByIder`. -- Even if we're careful about casing, a `refind()` method would be renamed to `regetById()`. +```diff +-Finder ++getByIder +``` + +or + +```diff +-refind() ++regetById() +``` Once we realize a plain-text find/replace action isn't going to help, we could try to write a regular expression. The expression would find all the occurrences we're actually looking for, and would rule out a lot of *false positives*. For instance, all the currently existing `find()` methods, in classes and interfaces alike could possibly be found with the expression `/\s(find)\(/`. The space in front of the `find` word is important, because a method definition also has that space (e.g. `public function find()`). @@ -83,14 +92,20 @@ The output is (abbreviated): {crop-end: 28, format: text, generator: php_script_output, source: src/03-programmatically-modifying-php-code/Tokenization/print_r_tokens.php} ![](src/03-programmatically-modifying-php-code/Tokenization/print_r_tokens.php_script_output.txt) -Each token, or "potentially meaningful chunk of code", is represented by a `PhpToken` object. It exposes the type of the token (returned by `getTokenName()`), starting with `T_*`. Amongst other things, you can find out on which line of the source code the token was found, by fetching the object's `line` property. A token also has a textual value which can be retrieved from its `text` property. Looping over the tokens we can create this nice little table: +Each token, or "potentially meaningful chunk of code", is represented by a `PhpToken` object. It exposes: + +* the type of the token (returned by `getTokenName()`), starting with `T_*`, +* line of the source code the token was found in `line` property, +* and a textual value which can be retrieved from its `text` property. + +Looping over the tokens we can create this nice little table: {generator: table_of_tokens, source: src/03-programmatically-modifying-php-code/Tokenization/original_code.php} ![](src/03-programmatically-modifying-php-code/Tokenization/original_code.table_of_tokens.md) -Note that the semi-colon (`;`) doesn't have a corresponding token type. Tokens like this don't have a variable value, they are just single characters. In the case of the semi-colon, you can't use other characters to indicate the end of a statement. The same goes for brackets, parentheses, etc. +Some characters like semi-colon (`;`), brackets or parentheses don't have a corresponding token type. They are just simple single characters. -Although tokenization is the first step in the compilation process, other tools may find it useful to do their own type of specialized work. For example, coding standard tools like [PHP-CS-fixer](https://github.com/FriendsOfPhp/PHP-CS-Fixer) and [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) operate at the level of tokens. +Although tokenization is the first step in the compilation process, other tools already use it to do their own type of specialized work. For example, coding standard tools like [PHP-CS-fixer](https://github.com/FriendsOfPhp/PHP-CS-Fixer) and [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer). ## Parsing PHP Tokens: the Abstract Syntax Tree diff --git a/manuscript-src/04-php-tools-in-the-game.md b/manuscript-src/04-php-tools-in-the-game.md index c21d454..e37ab67 100644 --- a/manuscript-src/04-php-tools-in-the-game.md +++ b/manuscript-src/04-php-tools-in-the-game.md @@ -94,12 +94,12 @@ It also helped with another issue. The development of PHP_CodeSniffer 2.8 and la Both PHP CS Fixer and PHP_CodeSniffer use `PhpToken::tokenize()`, which we used in the previous chapter as well. We won't go deeper in coding standards in here, but if you'd like to learn more about the internals of these tools see ["How PHP Coding Standard Tools Actually Work"](https://tomasvotruba.com/blog/2017/07/31/how-php-coding-standard-tools-actually-work/) post. -### EasyCodingStandard +### Easy Coding Standard - Modifies code - Is based on tokens -I saw many projects that use both PHP CS Fixer and PHP_CodeSniffer, yet very poorly because split attention divides the focus in the same ratio. Which is why I created [EasyCodingStandard](https://github.com/easy-coding-standard/easy-coding-standard) with this mission: *to help new generations adopt coding standards with almost no effort*. EasyCodingStandard was born in 2016 in the Czech Republic. +I saw many projects that use both PHP CS Fixer and PHP_CodeSniffer, yet very poorly because split attention divides the focus in the same ratio. Which is why I created [EasyCodingStandard](https://github.com/easy-coding-standard/easy-coding-standard) with this mission: *to help new generations adopt coding standards with almost no effort*. ECS was born in 2016 in the Czech Republic. ## 2. Static Analyzers diff --git a/manuscript-src/05-creating-your-first-rector-rule.md b/manuscript-src/05-creating-your-first-rector-rule.md index 28b1053..b9a64f3 100644 --- a/manuscript-src/05-creating-your-first-rector-rule.md +++ b/manuscript-src/05-creating-your-first-rector-rule.md @@ -69,7 +69,7 @@ vendor/bin/rule-doc-generator generate utils The command creates `/docs/rules_overview.md` with documentation of your rules. {/blurb} -We shouldn't implement just the `RectorInterface`, because most of the work is done by `AbstractRector`. The `enterNode()` method of this class has all the logic needed to determine if the rule applies to the current AST node, and it knows what to do in case the rule actually manipulates the current node. `AbstractRector` only needs three things from us: +We shouldn't implement a bare `RectorInterface`, because most of the work is done by `AbstractRector`. The `enterNode()` method of this class has all the logic needed to determine if the rule applies to the current AST node, and it knows what to do in case the rule actually manipulates the current node. `AbstractRector` only needs three things from us: 1. A description of the rule. 2. A list of one or more node types that should be considered candidates for this refactoring. @@ -125,7 +125,6 @@ vendor/bin/rector custom-rule ``` It creates the basic file structure above, including test for you. It also updates your `composer.json` with autoload. - {/blurb} After creating the new `UseRequestRequestGetRector` class, the first thing we do is provide a default implementation for the required methods. It's the least we can do to make "the compiler" happy. @@ -141,7 +140,7 @@ Although we only provide one before and after code sample here, `RuleDefinition` ### Finding the Right Node Class -The next method we have to implement is `getNodeTypes()`. This method works in a similar way as an event subscriber announcing the event types it wants to subscribe to. A Rector rule acts like a subscriber, but instead of events, Rector will "dispatch" AST nodes to the rule. Unlike a node visitor, a Rector rule isn't interested in any node type, so the rule declares a list of node types that it wants to subscribe to. These node types are the AST node classes that this rule considers to be candidates for refactoring. +The next method we have to implement is `getNodeTypes()`. This method works in a similar way as an event subscriber announcing the event types it wants to subscribe to. A Rector rule acts like a subscriber, but instead of events, Rector will "dispatch" AST nodes to the rule. These node types are the AST node classes that this rule considers to be candidates for refactoring. In our case we're looking for calls to `$request->get()` because we want to change them to `$request->request->get()`. To find out the right node type that we should return here, we use the PHP-Parser command-line utility, just like we did before. First, create a minimal example and store it in a temporary file (be sure not to commit this file): @@ -188,6 +187,47 @@ We don't know exactly what we have to do inside the `refactor()` method in order The current version of the rule will change *all* method calls to call a different method, namely `modified()`. Let's find out what the result of running this rule will be. + +### Expr vs Stmt + +Just a short note about the `PhpParser\Node\Expr` and `PhpParser\Node\Stmt` node types. They have different mechanics that help you to pick the right node type. + +A node classes that inherit from `Stmt`, are standalone elements that do not have any behavior, e.g: + +* `ClassMethod` +* `Property` +* `Class_` +* `If_` +* `Foreach_` +* `Expression` + +You can remove them using `PhpParser\NodeTraverser::REMOVE_NODE`, you can move them up and down, and they exists by themselves. The usually end with `}` or standalone line `;`. The `;` is typical end for `Expression` which is a special kind of `Stmt` that contains an `Expr` inside. + +E.g. `Expression` with `StaticCall` inside will be printed like: + +```php +SomeClass::someStaticMethod(); +``` + +On the other hand, the `Expr` node classes usually hold a value, behavior or do some operation: + +* `StaticCall` +* `LNumber` +* `Concat` +* `Identical` +* `FuncCall` + +The `StaticCall()` itself is not valid syntax, mind the missing `;`: + +```php +SomeClass::someStaticMethod() +``` + +Rule of the thumb: in case you want to remove, add nodes or replace `if()` with `foreach()`, pick a `Stmt` class. +In case you want to modify value or optimize expr, target `Expr`. + +Now back to the rule. + ## Running a Single Rule You can configure which Rector rules to run on your project by creating a `rector.php` configuration file. It's a PHP file that allows you to manipulate the configuration of the Symfony service container that Rector uses internally. Any class implementing `RectorInterface` that you register on the container will be used by Rector to refactor the code in your project. diff --git a/manuscript-src/99-book-revisions.md b/manuscript-src/99-book-revisions.md index 1164e2f..48103d0 100644 --- a/manuscript-src/99-book-revisions.md +++ b/manuscript-src/99-book-revisions.md @@ -7,7 +7,9 @@ Here we list changes to the book, so you can see what's new and when we made the ## Update 2024-01-30 and Rector 0.19.3 -* add new `RectorConfig` minimalist config +* add new chapter - *Node Type and Refactor Examples* - with typical `refactor()` use-cases +* add new `RectorConfig::configure()` minimalist config +* add section about `Stmt` vs `Expr` to *Creating Your First Rector Rule* chapter * add `setup-ci` command * add `custom-rule` command * add repository with complete code examples - [rectorphp/rector-book-code-examples](https://github.com/rectorphp/rector-book-code-examples) diff --git a/manuscript-src/src/03-programmatically-modifying-php-code/Tokenization/original_code_reformatted.php b/manuscript-src/src/03-programmatically-modifying-php-code/Tokenization/original_code_reformatted.php deleted file mode 100644 index fc84278..0000000 --- a/manuscript-src/src/03-programmatically-modifying-php-code/Tokenization/original_code_reformatted.php +++ /dev/null @@ -1,3 +0,0 @@ -withPaths([__DIR__ . '/app']) + ->withPaths([__DIR__ . '/src']) ->withRules([UseRequestRequestGetRector::class]); diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/composer.json b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/composer.json index 1f37a6c..81c0946 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/composer.json +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/composer.json @@ -3,6 +3,11 @@ "rector/rector": "^0.19.4", "symfony/framework-bundle": "^6.4" }, + "autoload": { + "psr-4": { + "App\\": "src" + } + }, "autoload-dev": { "psr-4": { "Utils\\Rector\\": "utils/rector/src" diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/rector-output.diff b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/rector-output.diff index c857c1b..18c6571 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/rector-output.diff +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/rector-output.diff @@ -2,7 +2,7 @@ 1 file with changes =================== -1) app/Controller/UserController.php:11 +1) src/Controller/UserController.php:11 ---------- begin diff ---------- @@ @@ diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/rector.php b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/rector.php index 309a88d..ec2dcd9 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/rector.php +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/rector.php @@ -6,5 +6,5 @@ use Rector\Config\RectorConfig; return RectorConfig::configure() - ->withPaths([__DIR__ . '/app']) + ->withPaths([__DIR__ . '/src']) ->withRules([UseRequestRequestGetRector::class]); diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/app/Controller/UserController.php b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/src/Controller/UserController.php similarity index 100% rename from manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/app/Controller/UserController.php rename to manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version3/src/Controller/UserController.php diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/composer.json b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/composer.json index 1f37a6c..81c0946 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/composer.json +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/composer.json @@ -3,6 +3,11 @@ "rector/rector": "^0.19.4", "symfony/framework-bundle": "^6.4" }, + "autoload": { + "psr-4": { + "App\\": "src" + } + }, "autoload-dev": { "psr-4": { "Utils\\Rector\\": "utils/rector/src" diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/rector-output.diff b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/rector-output.diff index 99608fb..26edb30 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/rector-output.diff +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/rector-output.diff @@ -2,7 +2,7 @@ 1 file with changes =================== -1) app/Controller/UserController.php:11 +1) src/Controller/UserController.php:11 ---------- begin diff ---------- @@ @@ diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/rector.php b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/rector.php index 309a88d..ec2dcd9 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/rector.php +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/rector.php @@ -6,5 +6,5 @@ use Rector\Config\RectorConfig; return RectorConfig::configure() - ->withPaths([__DIR__ . '/app']) + ->withPaths([__DIR__ . '/src']) ->withRules([UseRequestRequestGetRector::class]); diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/app/Controller/UserController.php b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/src/Controller/UserController.php similarity index 100% rename from manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/app/Controller/UserController.php rename to manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version4/src/Controller/UserController.php diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/composer.json b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/composer.json index 1f37a6c..81c0946 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/composer.json +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/composer.json @@ -3,6 +3,11 @@ "rector/rector": "^0.19.4", "symfony/framework-bundle": "^6.4" }, + "autoload": { + "psr-4": { + "App\\": "src" + } + }, "autoload-dev": { "psr-4": { "Utils\\Rector\\": "utils/rector/src" diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/rector-output.diff b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/rector-output.diff index 7f573ca..6abde60 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/rector-output.diff +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/rector-output.diff @@ -2,7 +2,7 @@ 1 file with changes =================== -1) app/Controller/UserController.php:11 +1) src/Controller/UserController.php:11 ---------- begin diff ---------- @@ @@ diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/rector.php b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/rector.php index 309a88d..ec2dcd9 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/rector.php +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/rector.php @@ -6,5 +6,5 @@ use Rector\Config\RectorConfig; return RectorConfig::configure() - ->withPaths([__DIR__ . '/app']) + ->withPaths([__DIR__ . '/src']) ->withRules([UseRequestRequestGetRector::class]); diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/app/Controller/UserController.php b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/src/Controller/UserController.php similarity index 100% rename from manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/app/Controller/UserController.php rename to manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version5/src/Controller/UserController.php diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/composer.json b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/composer.json index 99dd18c..81c0946 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/composer.json +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/composer.json @@ -5,7 +5,7 @@ }, "autoload": { "psr-4": { - "App\\": "app" + "App\\": "src" } }, "autoload-dev": { diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/rector-output.diff b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/rector-output.diff index 8eb272b..ffc7022 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/rector-output.diff +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/rector-output.diff @@ -2,7 +2,7 @@ 1 file with changes =================== -1) app/Controller/UserController.php:11 +1) src/Controller/UserController.php:11 ---------- begin diff ---------- @@ @@ diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/rector.php b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/rector.php index 309a88d..ec2dcd9 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/rector.php +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/rector.php @@ -6,5 +6,5 @@ use Rector\Config\RectorConfig; return RectorConfig::configure() - ->withPaths([__DIR__ . '/app']) + ->withPaths([__DIR__ . '/src']) ->withRules([UseRequestRequestGetRector::class]); diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/app/Controller/UserController.php b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/src/Controller/UserController.php similarity index 100% rename from manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/app/Controller/UserController.php rename to manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version6/src/Controller/UserController.php diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/composer.json b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/composer.json index fbe1147..261133b 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/composer.json +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/composer.json @@ -6,7 +6,7 @@ }, "autoload": { "psr-4": { - "App\\": "app" + "App\\": "src" } }, "autoload-dev": { diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/rector.php b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/rector.php index 309a88d..ec2dcd9 100644 --- a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/rector.php +++ b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/rector.php @@ -6,5 +6,5 @@ use Rector\Config\RectorConfig; return RectorConfig::configure() - ->withPaths([__DIR__ . '/app']) + ->withPaths([__DIR__ . '/src']) ->withRules([UseRequestRequestGetRector::class]); diff --git a/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/app/Controller/UserController.php b/manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/src/Controller/UserController.php similarity index 100% rename from manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/app/Controller/UserController.php rename to manuscript-src/src/05-creating-your-first-rector-rule/ChangeRequestGetToRequestRequestGet/Version7/src/Controller/UserController.php diff --git a/manuscript/book.md b/manuscript/book.md index 994ae64..d49430f 100644 --- a/manuscript/book.md +++ b/manuscript/book.md @@ -204,10 +204,19 @@ Looking for occurrences in the repository class alone is not enough. If the repo Of course, we all have smart IDEs like PhpStorm, which make this kind of refactoring very easy and relatively safe for us. What if we don't want to make this change in an IDE. What if we want to do it with a PHP script? What's the best way to make the change? The first thing to try is some kind of global find and replace action, possibly using a function like `str_replace()`. -This most likely results in a number of wrong changes. For example: +This most likely results in a number of wrong changes: -- If we're not careful about upper versus lower case characters, a class called `Finder` would be renamed to `getByIder`. -- Even if we're careful about casing, a `refind()` method would be renamed to `regetById()`. +```diff +-Finder ++getByIder +``` + +or + +```diff +-refind() ++regetById() +``` Once we realize a plain-text find/replace action isn't going to help, we could try to write a regular expression. The expression would find all the occurrences we're actually looking for, and would rule out a lot of *false positives*. For instance, all the currently existing `find()` methods, in classes and interfaces alike could possibly be found with the expression `/\s(find)\(/`. The space in front of the `find` word is important, because a method definition also has that space (e.g. `public function find()`). @@ -315,7 +324,13 @@ Array [3] => PhpToken Object ``` -Each token, or "potentially meaningful chunk of code", is represented by a `PhpToken` object. It exposes the type of the token (returned by `getTokenName()`), starting with `T_*`. Amongst other things, you can find out on which line of the source code the token was found, by fetching the object's `line` property. A token also has a textual value which can be retrieved from its `text` property. Looping over the tokens we can create this nice little table: +Each token, or "potentially meaningful chunk of code", is represented by a `PhpToken` object. It exposes: + +* the type of the token (returned by `getTokenName()`), starting with `T_*`, +* line of the source code the token was found in `line` property, +* and a textual value which can be retrieved from its `text` property. + +Looping over the tokens we can create this nice little table: | Line | Token | Value | | --- | --- | --- | @@ -326,9 +341,9 @@ Each token, or "potentially meaningful chunk of code", is represented by a `PhpT | 2 | `;` | `;` | | 2 | `T_WHITESPACE` | `\n` | -Note that the semi-colon (`;`) doesn't have a corresponding token type. Tokens like this don't have a variable value, they are just single characters. In the case of the semi-colon, you can't use other characters to indicate the end of a statement. The same goes for brackets, parentheses, etc. +Some characters like semi-colon (`;`), brackets or parentheses don't have a corresponding token type. They are just simple single characters. -Although tokenization is the first step in the compilation process, other tools may find it useful to do their own type of specialized work. For example, coding standard tools like [PHP-CS-fixer](https://github.com/FriendsOfPhp/PHP-CS-Fixer) and [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) operate at the level of tokens. +Although tokenization is the first step in the compilation process, other tools already use it to do their own type of specialized work. For example, coding standard tools like [PHP-CS-fixer](https://github.com/FriendsOfPhp/PHP-CS-Fixer) and [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer). ## Parsing PHP Tokens: the Abstract Syntax Tree @@ -1227,12 +1242,12 @@ It also helped with another issue. The development of PHP_CodeSniffer 2.8 and la Both PHP CS Fixer and PHP_CodeSniffer use `PhpToken::tokenize()`, which we used in the previous chapter as well. We won't go deeper in coding standards in here, but if you'd like to learn more about the internals of these tools see ["How PHP Coding Standard Tools Actually Work"](https://tomasvotruba.com/blog/2017/07/31/how-php-coding-standard-tools-actually-work/) post. -### EasyCodingStandard +### Easy Coding Standard - Modifies code - Is based on tokens -I saw many projects that use both PHP CS Fixer and PHP_CodeSniffer, yet very poorly because split attention divides the focus in the same ratio. Which is why I created [EasyCodingStandard](https://github.com/easy-coding-standard/easy-coding-standard) with this mission: *to help new generations adopt coding standards with almost no effort*. EasyCodingStandard was born in 2016 in the Czech Republic. +I saw many projects that use both PHP CS Fixer and PHP_CodeSniffer, yet very poorly because split attention divides the focus in the same ratio. Which is why I created [EasyCodingStandard](https://github.com/easy-coding-standard/easy-coding-standard) with this mission: *to help new generations adopt coding standards with almost no effort*. ECS was born in 2016 in the Czech Republic. ## 2. Static Analyzers @@ -1492,7 +1507,7 @@ vendor/bin/rule-doc-generator generate utils The command creates `/docs/rules_overview.md` with documentation of your rules. {/blurb} -We shouldn't implement just the `RectorInterface`, because most of the work is done by `AbstractRector`. The `enterNode()` method of this class has all the logic needed to determine if the rule applies to the current AST node, and it knows what to do in case the rule actually manipulates the current node. `AbstractRector` only needs three things from us: +We shouldn't implement a bare `RectorInterface`, because most of the work is done by `AbstractRector`. The `enterNode()` method of this class has all the logic needed to determine if the rule applies to the current AST node, and it knows what to do in case the rule actually manipulates the current node. `AbstractRector` only needs three things from us: 1. A description of the rule. 2. A list of one or more node types that should be considered candidates for this refactoring. @@ -1645,7 +1660,7 @@ Although we only provide one before and after code sample here, `RuleDefinition` ### Finding the Right Node Class -The next method we have to implement is `getNodeTypes()`. This method works in a similar way as an event subscriber announcing the event types it wants to subscribe to. A Rector rule acts like a subscriber, but instead of events, Rector will "dispatch" AST nodes to the rule. Unlike a node visitor, a Rector rule isn't interested in any node type, so the rule declares a list of node types that it wants to subscribe to. These node types are the AST node classes that this rule considers to be candidates for refactoring. +The next method we have to implement is `getNodeTypes()`. This method works in a similar way as an event subscriber announcing the event types it wants to subscribe to. A Rector rule acts like a subscriber, but instead of events, Rector will "dispatch" AST nodes to the rule. These node types are the AST node classes that this rule considers to be candidates for refactoring. In our case we're looking for calls to `$request->get()` because we want to change them to `$request->request->get()`. To find out the right node type that we should return here, we use the PHP-Parser command-line utility, just like we did before. First, create a minimal example and store it in a temporary file (be sure not to commit this file): @@ -1746,6 +1761,46 @@ final class UseRequestRequestGetRector extends AbstractRector The current version of the rule will change *all* method calls to call a different method, namely `modified()`. Let's find out what the result of running this rule will be. +### Expr vs Stmt + +Just a short note about the `PhpParser\Node\Expr` and `PhpParser\Node\Stmt` node types. They have different mechanics that help you to pick the right node type. + +A node classes that inherit from `Stmt`, are standalone elements that do not have any behavior, e.g: + +* `ClassMethod` +* `Property` +* `Class_` +* `If_` +* `Foreach_` +* `Expression` + +You can remove them using `PhpParser\NodeTraverser::REMOVE_NODE`, you can move them up and down, and they exists by themselves. The usually end with `}` or standalone line `;`. The `;` is typical end for `Expression` which is a special kind of `Stmt` that contains an `Expr` inside. + +E.g. `Expression` with `StaticCall` inside will be printed like: + +```php +SomeClass::someStaticMethod(); +``` + +On the other hand, the `Expr` node classes usually hold a value, behavior or do some operation: + +* `StaticCall` +* `LNumber` +* `Concat` +* `Identical` +* `FuncCall` + +The `StaticCall()` itself is not valid syntax, mind the missing `;`: + +```php +SomeClass::someStaticMethod() +``` + +Rule of the thumb: in case you want to remove, add nodes or replace `if()` with `foreach()`, pick a `Stmt` class. +In case you want to modify value or optimize expr, target `Expr`. + +Now back to the rule. + ## Running a Single Rule You can configure which Rector rules to run on your project by creating a `rector.php` configuration file. It's a PHP file that allows you to manipulate the configuration of the Symfony service container that Rector uses internally. Any class implementing `RectorInterface` that you register on the container will be used by Rector to refactor the code in your project. @@ -1762,7 +1817,7 @@ use Utils\Rector\Rector\UseRequestRequestGetRector; use Rector\Config\RectorConfig; return RectorConfig::configure() - ->withPaths([__DIR__ . '/app']) + ->withPaths([__DIR__ . '/src']) ->withRules([UseRequestRequestGetRector::class]); ``` @@ -1785,7 +1840,7 @@ The `--dry-run` option ensures that Rector doesn't copy the modified code back t 1 file with changes =================== -1) app/Controller/UserController.php:11 +1) src/Controller/UserController.php:11 ---------- begin diff ---------- @@ @@ @@ -1886,7 +1941,7 @@ Running Rector again, we get a much better result: 1 file with changes =================== -1) app/Controller/UserController.php:11 +1) src/Controller/UserController.php:11 ---------- begin diff ---------- @@ @@ @@ -1951,7 +2006,7 @@ Running Rector again, the resulting diff is: 1 file with changes =================== -1) app/Controller/UserController.php:11 +1) src/Controller/UserController.php:11 ---------- begin diff ---------- @@ @@ @@ -2060,7 +2115,7 @@ Running Rector again, we now get a very promising diff: 1 file with changes =================== -1) app/Controller/UserController.php:11 +1) src/Controller/UserController.php:11 ---------- begin diff ---------- @@ @@ @@ -4805,7 +4860,9 @@ Here we list changes to the book, so you can see what's new and when we made the ## Update 2024-01-30 and Rector 0.19.3 -* add new `RectorConfig` minimalist config +* add new chapter - *Node Type and Refactor Examples* - with typical `refactor()` use-cases +* add new `RectorConfig::configure()` minimalist config +* add section about `Stmt` vs `Expr` to *Creating Your First Rector Rule* chapter * add `setup-ci` command * add `custom-rule` command * add repository with complete code examples - [rectorphp/rector-book-code-examples](https://github.com/rectorphp/rector-book-code-examples)