Skip to content

Commit 604e54c

Browse files
committed
Added README.md
1 parent 8f22e9f commit 604e54c

File tree

1 file changed

+296
-0
lines changed

1 file changed

+296
-0
lines changed

README.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
<!--suppress HtmlDeprecatedAttribute -->
2+
<h1 align="center">PHP Code Transformer</h1>
3+
4+
<!-- Main Badges -->
5+
<p align="center">
6+
<!-- License: MIT -->
7+
<a href="https://opensource.org/licenses/MIT" target="_blank">
8+
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-9C0000.svg?labelColor=ebdbb2&style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCI+PHBhdGggdmVjdG9yLWVmZmVjdD0ibm9uLXNjYWxpbmctc3Ryb2tlIiBkPSJNMCAyLjk5NWgxLjI4djguMDFIMHpNMi41NCAzaDEuMjh2NS4zNEgyLjU0em0yLjU1LS4wMDVoMS4yOHY4LjAxSDUuMDl6bTIuNTQuMDA3aDEuMjh2MS4zMzZINy42M3oiIGZpbGw9IiM5YzAwMDAiLz48cGF0aCB2ZWN0b3ItZWZmZWN0PSJub24tc2NhbGluZy1zdHJva2UiIGQ9Ik03LjYzIDUuNjZoMS4yOFYxMUg3LjYzeiIgZmlsbD0iIzdjN2Q3ZSIvPjxwYXRoIHZlY3Rvci1lZmZlY3Q9Im5vbi1zY2FsaW5nLXN0cm9rZSIgZD0iTTEwLjE3NyAzLjAwMmgzLjgyNnYxLjMzNmgtMy44MjZ6bS4wMDMgMi42NThoMS4yOFYxMWgtMS4yOHoiIGZpbGw9IiM5YzAwMDAiLz48L3N2Zz4="/>
9+
</a>
10+
11+
<!-- Twitter: @WalterWoshid -->
12+
<a href="https://twitter.com/WalterWoshid" target="_blank">
13+
<img alt="Twitter: @WalterWoshid" src="https://img.shields.io/badge/@WalterWoshid-Twitter?labelColor=ebdbb2&style=flat&logo=twitter&logoColor=458588&color=458588&label=Twitter"/>
14+
</a>
15+
16+
<!-- PHP: >=8.1 -->
17+
<a href="https://www.php.net" target="_blank">
18+
<img alt="PHP: >=8.1" src="https://img.shields.io/badge/PHP->=8.1-4C5789.svg?labelColor=ebdbb2&style=flat&logo=php&logoColor=4C5789"/>
19+
</a>
20+
21+
<!-- Packagist -->
22+
<a href="https://packagist.org/packages/okapi/code-transformer" target="_blank">
23+
<img alt="Packagist" src="https://img.shields.io/packagist/v/okapi/code-transformer?label=Packagist&labelColor=ebdbb2&style=flat&color=fe8019&logo=packagist"/>
24+
</a>
25+
26+
<!-- Build -->
27+
<!--suppress HtmlUnknownTarget -->
28+
<a href="../../actions/workflows/tests.yml" target="_blank">
29+
<img alt="Build" src="https://img.shields.io/github/actions/workflow/status/okapi-web/php-code-transformer/tests.yml?label=Build&labelColor=ebdbb2&style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGhlaWdodD0iMTYiPjxwYXRoIGZpbGw9IiM2YWFiMjAiIGQ9Ik0zMy45MTQgNDEzLjYxMmgxNDkuNTV2MjcuNTk1SDI3LjQ5NGMtMjYuMzQ4IDAtMzQuMTM2LTEzLjE5NC0yMS43MjktMzQuMzFMMTM3LjkxIDE4Ny43NTNWNjEuOTc1aC0yNi4wNzVjLTE5LjUwNCAwLTE5LjUwNC0yNy41OTUgMC0yNy41OTVoMTg5LjkzYzE5LjUwNSAwIDE5LjUwNSAyNy41OTUgMCAyNy41OTVIMjc1LjY5djEzMi44MjhoLTI3Ljk2M1Y2MS45NzVoLTgxLjg1NHYxMzIuODI4TDMzLjkxNCA0MTMuNjEyem0xMzUuNi0xNjkuMTg3TDg0LjY5MiAzODYuNTc0aDcwLjYwMWwxMDQuMzc1LTExMi45MDctMTUuNTgyLTI5LjI0MmgtNzQuNTd6bTE0NS45OTYgOS43ODNMMjA5LjUgMzY3LjUwNmwxMDYuMDEgMTEwLjI4NiAzMy41MzgtMzMuNTM4LTgwLjY1LTc2Ljc0OCA4MC42NS03OS43Ni0zMy41MzgtMzMuNTM4em01Ni45NDMgMzMuNTM3IDgwLjY1IDc5Ljc2LTgwLjY1IDc2Ljc1IDMzLjUzOCAzMy41MzdMNTEyIDM2Ny41MDYgNDA1Ljk5IDI1NC4yMDhsLTMzLjUzNyAzMy41Mzd6Ii8+PC9zdmc+">
30+
</a>
31+
</p>
32+
33+
<!-- Coverage -->
34+
<p align="center">
35+
<!-- Coverage - PHP 8.1 -->
36+
<a href="https://app.codecov.io/gh/okapi-web/php-code-transformer/flags" target="_blank">
37+
<img alt="Coverage - PHP 8.1" src="https://img.shields.io/codecov/c/github/okapi-web/php-code-transformer?flag=os-ubuntu-latest_php-8.1&label=Coverage - PHP 8.1&labelColor=ebdbb2&style=flat&logo=codecov&logoColor=FFC107&color=FFC107"/>
38+
</a>
39+
40+
<!-- Coverage - PHP 8.2 -->
41+
<a href="https://app.codecov.io/gh/okapi-web/php-code-transformer/flags" target="_blank">
42+
<img alt="Coverage - PHP 8.2" src="https://img.shields.io/codecov/c/github/okapi-web/php-code-transformer?flag=os-ubuntu-latest_php-8.2&label=Coverage - PHP 8.2&labelColor=ebdbb2&style=flat&logo=codecov&logoColor=FFC107&color=FFC107"/>
43+
</a>
44+
</p>
45+
46+
<h2 align="center">PHP Code Transformer is a PHP library that allows you to modify and transform the source code of a loaded PHP class.</h2>
47+
48+
49+
50+
## Installation
51+
52+
```shell
53+
composer require okapi/code-transformer
54+
```
55+
56+
57+
58+
# Usage
59+
60+
## 📖 List of contents
61+
62+
- [Create a kernel](#create-a-kernel)
63+
- [Create a transformer](#create-a-transformer)
64+
- [Initialize the kernel](#initialize-the-kernel)
65+
- [Result](#result)
66+
67+
68+
69+
## Create a kernel
70+
71+
```php
72+
<?php
73+
74+
use Okapi\CodeTransformer\CodeTransformerKernel;
75+
76+
class Kernel extends CodeTransformerKernel
77+
{
78+
// Define a list of transformer classes
79+
protected array $transformers = [
80+
StringTransformer::class,
81+
UnPrivateTransformer::class,
82+
];
83+
}
84+
```
85+
86+
87+
## Create a transformer
88+
89+
```php
90+
// String Transformer
91+
92+
<?php
93+
94+
use Okapi\CodeTransformer\Service\StreamFilter\Metadata\Code;
95+
use Okapi\CodeTransformer\Transformer;
96+
97+
class StringTransformer extends Transformer
98+
{
99+
public function getTargetClass(): string|array
100+
{
101+
// You can specify a single class or an array of classes
102+
// You can also use wildcards, see https://github.com/okapi-web/php-wildcards
103+
return MyTargetClass::class;
104+
}
105+
106+
public function transform(Code $code): void
107+
{
108+
// Get the source file node which contains all the nodes of the source code
109+
$sourceFileNode = $code->sourceFileNode;
110+
111+
// I recommend using the Microsoft\PhpParser library to parse the source code,
112+
// but you can also use any other library or manually parse the source code
113+
// or just use basic PHP functions with `$code->getOriginalSource()`
114+
115+
// Iterate over all nodes
116+
foreach ($sourceFileNode->getDescendantNodes() as $node) {
117+
// Find 'Hello World!' string
118+
if ($node instanceof StringLiteral
119+
&& $node->getStringContentsText() === 'Hello World!'
120+
) {
121+
// Replace it with 'Hello from Code Transformer!'
122+
// Edit method accepts a Token class
123+
$code->edit(
124+
$node->children,
125+
"'Hello from Code Transformer!'",
126+
);
127+
128+
// You can also manually edit the source code
129+
$code->editAt(
130+
$node->getStartPosition(),
131+
$node->getWidth(),
132+
"'Hello from Code Transformer!'",
133+
);
134+
135+
// Append a new line of code
136+
$code->append('$iAmAppended = true;');
137+
}
138+
}
139+
}
140+
}
141+
```
142+
143+
```php
144+
// UnPrivate Transformer
145+
146+
<?php
147+
148+
namespace Okapi\CodeTransformer\Tests\Stubs\Transformer;
149+
150+
use Microsoft\PhpParser\TokenKind;
151+
use Okapi\CodeTransformer\Service\StreamFilter\Metadata\Code;
152+
use Okapi\CodeTransformer\Transformer;
153+
154+
class UnPrivateTransformer extends Transformer
155+
{
156+
public function getTargetClass(): string|array
157+
{
158+
return MyTargetClass::class;
159+
}
160+
161+
public function transform(Code $code): void
162+
{
163+
$sourceFileNode = $code->sourceFileNode;
164+
165+
// Iterate over all tokens
166+
foreach ($sourceFileNode->getDescendantTokens() as $token) {
167+
// Replace all private keywords with public
168+
if ($token->kind === TokenKind::PrivateKeyword) {
169+
$code->edit($token, 'public');
170+
}
171+
}
172+
}
173+
}
174+
```
175+
176+
177+
## Initialize the kernel
178+
179+
```php
180+
// Initialize the kernel early in the application lifecycle
181+
182+
<?php
183+
184+
use MyKernel;
185+
186+
require_once __DIR__ . '/vendor/autoload.php';
187+
188+
$kernel = new MyKernel(
189+
// The directory where the transformed source code will be stored
190+
cacheDir: __DIR__ . '/var/cache',
191+
192+
// The cache file mode
193+
cacheFileMode: 0777,
194+
);
195+
```
196+
197+
198+
## Result
199+
200+
```php
201+
<?php
202+
203+
// Just use your classes as usual
204+
$myTargetClass = new MyTargetClass();
205+
206+
$myTargetClass->myPrivateProperty; // You can't get me!
207+
$myTargetClass->myPrivateMethod(); // Hello from Code Transformer!
208+
```
209+
210+
211+
```php
212+
// MyTargetClass.php
213+
214+
<?php
215+
216+
class MyTargetClass
217+
{
218+
private string $myPrivateProperty = "You can't get me!";
219+
220+
private function myPrivateMethod(): void
221+
{
222+
echo 'Hello World!';
223+
}
224+
}
225+
```
226+
227+
```php
228+
// MyTargetClass.php (transformed)
229+
230+
<?php
231+
232+
class MyTargetClass
233+
{
234+
public string $myPrivateProperty = "You can't get me!";
235+
236+
public function myPrivateMethod(): void
237+
{
238+
echo 'Hello from Code Transformer!';
239+
}
240+
}
241+
$iAmAppended = true;
242+
```
243+
244+
245+
# How it works
246+
247+
- The `Kernel` registers multiple services
248+
249+
- The `TransformerContainer` service stores the list of transformers and their configuration
250+
251+
- The `CacheStateManager` service manages the cache state
252+
253+
- The `StreamFilter` service registers a [PHP Stream Filter](https://www.php.net/manual/wrappers.php.php#wrappers.php.filter)
254+
which allows to modify the source code before it is loaded by PHP
255+
256+
- The `AutoloadInterceptor` service overloads the Composer autoloader, which handles the loading of classes
257+
258+
259+
## General workflow when a class is loaded
260+
261+
- The `AutoloadInterceptor` service intercepts the loading of a class
262+
- It expects a class file path
263+
264+
- The `TransformerContainer` matches the class name with the list of transformer target classes
265+
266+
- If the class is matched, we query the cache state to see if the transformed source code is already cached
267+
- We check if the cache is valid
268+
- Modification time of the caching process is less than the modification time of the source file or the transformers
269+
- Check if the cache file, the source file and the transformers exist
270+
- If the cache is valid, we load the transformed source code from the cache
271+
- If not, we convert the class file path to a stream filter path
272+
273+
- The `StreamFilter` modifies the source code by applying the matching transformers
274+
- If the modified source code is different from the original source code, we cache the transformed source code
275+
- If not, we cache it anyway, but without a cached source file path, so that the transformation process is not repeated
276+
277+
278+
279+
## Testing
280+
281+
- Run `composer run-script test`<br>
282+
or
283+
- Run `composer run-script test-coverage`
284+
285+
286+
287+
## Show your support
288+
289+
Give a ⭐ if this project helped you!
290+
291+
292+
293+
## 📝 License
294+
295+
Copyright © 2023 [Valentin Wotschel](https://github.com/WalterWoshid).<br>
296+
This project is [MIT](https://opensource.org/licenses/MIT) licensed.

0 commit comments

Comments
 (0)