Skip to content

Commit

Permalink
tv tokens less print 2 (#222)
Browse files Browse the repository at this point in the history
* tidy

* build

* be consistent, use always src

* fixup! be consistent, use always src

* add stmts vs expr
  • Loading branch information
TomasVotruba authored Jan 29, 2024
1 parent ad2e397 commit 286bed8
Show file tree
Hide file tree
Showing 27 changed files with 169 additions and 45 deletions.
27 changes: 21 additions & 6 deletions manuscript-src/03-programmatically-modifying-php-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`).

Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions manuscript-src/04-php-tools-in-the-game.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
46 changes: 43 additions & 3 deletions manuscript-src/05-creating-your-first-rector-rule.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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):

Expand Down Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion manuscript-src/99-book-revisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withPaths([__DIR__ . '/app'])
->withPaths([__DIR__ . '/src'])
->withRules([UseRequestRequestGetRector::class]);
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
1 file with changes
===================

1) app/Controller/UserController.php:11
1) src/Controller/UserController.php:11

---------- begin diff ----------
@@ @@
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withPaths([__DIR__ . '/app'])
->withPaths([__DIR__ . '/src'])
->withRules([UseRequestRequestGetRector::class]);
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
1 file with changes
===================

1) app/Controller/UserController.php:11
1) src/Controller/UserController.php:11

---------- begin diff ----------
@@ @@
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withPaths([__DIR__ . '/app'])
->withPaths([__DIR__ . '/src'])
->withRules([UseRequestRequestGetRector::class]);
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
1 file with changes
===================

1) app/Controller/UserController.php:11
1) src/Controller/UserController.php:11

---------- begin diff ----------
@@ @@
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withPaths([__DIR__ . '/app'])
->withPaths([__DIR__ . '/src'])
->withRules([UseRequestRequestGetRector::class]);
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
},
"autoload": {
"psr-4": {
"App\\": "app"
"App\\": "src"
}
},
"autoload-dev": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
1 file with changes
===================

1) app/Controller/UserController.php:11
1) src/Controller/UserController.php:11

---------- begin diff ----------
@@ @@
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withPaths([__DIR__ . '/app'])
->withPaths([__DIR__ . '/src'])
->withRules([UseRequestRequestGetRector::class]);
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"autoload": {
"psr-4": {
"App\\": "app"
"App\\": "src"
}
},
"autoload-dev": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withPaths([__DIR__ . '/app'])
->withPaths([__DIR__ . '/src'])
->withRules([UseRequestRequestGetRector::class]);
Loading

0 comments on commit 286bed8

Please sign in to comment.