Skip to content

Commit dd338c9

Browse files
authored
Merge pull request #20 from okapi-web/develop
Fixed issues with debugging
2 parents 3c2c916 + 1ed8706 commit dd338c9

8 files changed

+157
-29
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "okapi/code-transformer",
33
"description": "PHP Code Transformer is a PHP library that allows you to modify and transform the source code of a loaded PHP class.",
4-
"version": "1.3.5",
4+
"version": "1.3.6",
55
"type": "library",
66
"homepage": "https://github.com/okapi-web/php-code-transformer",
77
"license": "MIT",

src/CodeTransformerKernel.php

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use DI\Attribute\Inject;
77
use Okapi\CodeTransformer\Core\AutoloadInterceptor;
88
use Okapi\CodeTransformer\Core\Cache\CacheStateManager;
9+
use Okapi\CodeTransformer\Core\CachedStreamFilter;
910
use Okapi\CodeTransformer\Core\Container\TransformerManager;
1011
use Okapi\CodeTransformer\Core\DI;
1112
use Okapi\CodeTransformer\Core\Exception\Kernel\DirectKernelInitializationException;
@@ -46,6 +47,9 @@ abstract class CodeTransformerKernel
4647
#[Inject]
4748
private StreamFilter $streamFilter;
4849

50+
#[Inject]
51+
private CachedStreamFilter $cachedStreamFilter;
52+
4953
#[Inject]
5054
private AutoloadInterceptor $autoloadInterceptor;
5155

@@ -226,6 +230,7 @@ protected function registerServices(): void
226230
$this->cacheStateManager->register();
227231

228232
$this->streamFilter->register();
233+
$this->cachedStreamFilter->register();
229234
}
230235

231236
/**

src/Core/AutoloadInterceptor/ClassContainer.php

+19-7
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,28 @@ class ClassContainer
1313
/**
1414
* The class paths.
1515
*
16-
* @var array<string, string>
16+
* @var array<string, array{namespacedClass: class-string, cachedFilePath: string|null}>
1717
*/
18-
private array $namespacedClassPaths = [];
18+
private array $classContext = [];
1919

2020
/**
2121
* Add a class path.
2222
*
2323
* @param string $path
24-
* @param string $class
24+
* @param class-string $namespacedClass
25+
* @param string|null $cachedFilePath
2526
*
2627
* @return void
2728
*/
28-
public function addNamespacedClassPath(string $path, string $class): void
29-
{
30-
$this->namespacedClassPaths[$path] = $class;
29+
public function addClassContext(
30+
string $path,
31+
string $namespacedClass,
32+
?string $cachedFilePath = null,
33+
): void {
34+
$this->classContext[$path] = [
35+
'namespacedClass' => $namespacedClass,
36+
'cachedFilePath' => $cachedFilePath,
37+
];
3138
}
3239

3340
/**
@@ -39,6 +46,11 @@ public function addNamespacedClassPath(string $path, string $class): void
3946
*/
4047
public function getNamespacedClassByPath(string $path): string
4148
{
42-
return $this->namespacedClassPaths[$path];
49+
return $this->classContext[$path]['namespacedClass'];
50+
}
51+
52+
public function getCachedFilePath(string $filePath): string
53+
{
54+
return $this->classContext[$filePath]['cachedFilePath'];
4355
}
4456
}

src/Core/AutoloadInterceptor/ClassLoader.php

+29-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use DI\Attribute\Inject;
77
use Okapi\CodeTransformer\Core\AutoloadInterceptor;
88
use Okapi\CodeTransformer\Core\Cache\CacheStateManager;
9+
use Okapi\CodeTransformer\Core\CachedStreamFilter;
910
use Okapi\CodeTransformer\Core\Matcher\TransformerMatcher;
1011
use Okapi\CodeTransformer\Core\Options;
1112
use Okapi\CodeTransformer\Core\Options\Environment;
@@ -120,26 +121,50 @@ public function findFile($namespacedClass): false|string
120121
&& $cacheState
121122
) {
122123
// Use the cached file if transformations have been applied
124+
if ($cacheFilePath = $cacheState->getFilePath()) {
125+
$this->classContainer->addClassContext(
126+
$filePath,
127+
$namespacedClass,
128+
$cacheFilePath,
129+
);
130+
131+
// For cached files, the debugger will have trouble finding the
132+
// original file, that's why we rewrite the file path with a PHP
133+
// stream filter
134+
/** @see CachedStreamFilter::filter() */
135+
return $this->filterInjector->rewriteCached($filePath);
136+
}
137+
123138
// Or return the original file if no transformations have been applied
124-
return $cacheState->getFilePath() ?? $filePath;
139+
return $filePath;
125140
}
126141

127142
// In development mode, check if the cache is fresh
128143
elseif ($this->options->getEnvironment() === Environment::DEVELOPMENT
129144
&& $cacheState
130145
&& $cacheState->isFresh()
131146
) {
132-
return $cacheState->getFilePath() ?? $filePath;
147+
if ($cacheFilePath = $cacheState->getFilePath()) {
148+
$this->classContainer->addClassContext(
149+
$filePath,
150+
$namespacedClass,
151+
$cacheFilePath,
152+
);
153+
154+
return $this->filterInjector->rewriteCached($filePath);
155+
}
156+
157+
return $filePath;
133158
}
134159

135160

136161
// Check if the class should be transformed
137-
if (!$this->transformerMatcher->match($namespacedClass, $filePath)) {
162+
if (!$this->transformerMatcher->matchAndStore($namespacedClass, $filePath)) {
138163
return $filePath;
139164
}
140165

141166
// Add the class to store the file path
142-
$this->classContainer->addNamespacedClassPath($filePath, $namespacedClass);
167+
$this->classContainer->addClassContext($filePath, $namespacedClass);
143168

144169
// Replace the file path with a PHP stream filter
145170
/** @see StreamFilter::filter() */

src/Core/CachedStreamFilter.php

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace Okapi\CodeTransformer\Core;
4+
5+
use Okapi\CodeTransformer\Core\AutoloadInterceptor\ClassContainer;
6+
use Okapi\CodeTransformer\Core\StreamFilter\Metadata;
7+
use Okapi\Filesystem\Filesystem;
8+
use php_user_filter as PhpStreamFilter;
9+
10+
/**
11+
* # Cached Stream Filter
12+
*
13+
* This class is used to register the cached stream filter.
14+
*
15+
* Because the PHP debugger has trouble finding the original file, we always
16+
* rewrite the file path with a PHP stream filter.
17+
*/
18+
class CachedStreamFilter extends PhpStreamFilter implements ServiceInterface
19+
{
20+
public const CACHED_FILTER_ID = 'okapi.code-transformer.cached';
21+
22+
private string $data = '';
23+
24+
public function register(): void
25+
{
26+
stream_filter_register(static::CACHED_FILTER_ID, static::class);
27+
}
28+
29+
public function filter($in, $out, &$consumed, bool $closing): int
30+
{
31+
// Read stream until EOF
32+
while ($bucket = stream_bucket_make_writeable($in)) {
33+
$this->data .= $bucket->data;
34+
}
35+
36+
// If stream is closed, return the cached file
37+
if ($closing || feof($this->stream)) {
38+
$consumed = strlen($this->data);
39+
40+
$metadata = DI::make(Metadata::class, [
41+
'stream' => $this->stream,
42+
'originalSource' => $this->data,
43+
]);
44+
45+
$classContainer = DI::get(ClassContainer::class);
46+
$cachedFilePath = $classContainer->getCachedFilePath($metadata->uri);
47+
48+
$source = Filesystem::readFile($cachedFilePath);
49+
50+
$bucket = stream_bucket_new($this->stream, $source);
51+
stream_bucket_append($out, $bucket);
52+
53+
// Pass the (cached) source code to the next filter
54+
return PSFS_PASS_ON;
55+
}
56+
57+
// No data has been consumed
58+
return PSFS_PASS_ON;
59+
}
60+
}

src/Core/Matcher/TransformerMatcher.php

+24-12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Okapi\CodeTransformer\Core\Container\TransformerManager;
1111
use Okapi\CodeTransformer\Core\DI;
1212
use Okapi\CodeTransformer\Transformer;
13+
use Okapi\Path\Path;
1314
use Okapi\Wildcards\Regex;
1415

1516
/**
@@ -44,7 +45,7 @@ class TransformerMatcher
4445
*
4546
* @return bool
4647
*/
47-
public function match(string $namespacedClass, string $filePath): bool
48+
public function matchAndStore(string $namespacedClass, string $filePath): bool
4849
{
4950
// Get the transformers
5051
$transformerContainers = $this->transformerContainer->getTransformerContainers();
@@ -80,24 +81,35 @@ function (bool $carry, TransformerContainer $container) use ($transformerContain
8081

8182
// Cache the result
8283
if (!$matchedTransformerContainers) {
83-
$cacheState = DI::make(EmptyResultCacheState::class, [
84-
CacheState::DATA => [
85-
CacheState::ORIGINAL_FILE_PATH_KEY => $filePath,
86-
CacheState::NAMESPACED_CLASS_KEY => $namespacedClass,
87-
CacheState::MODIFICATION_TIME_KEY => filemtime($filePath),
88-
],
89-
]);
90-
91-
// Set the cache state
92-
$this->cacheStateManager->setCacheState(
84+
$this->cacheEmptyResult(
85+
$namespacedClass,
9386
$filePath,
94-
$cacheState,
9587
);
9688
}
9789

9890
return (bool)$matchedTransformerContainers;
9991
}
10092

93+
private function cacheEmptyResult(
94+
string $namespacedClass,
95+
string $filePath,
96+
): void {
97+
$filePath = Path::resolve($filePath);
98+
$cacheState = DI::make(EmptyResultCacheState::class, [
99+
CacheState::DATA => [
100+
CacheState::ORIGINAL_FILE_PATH_KEY => $filePath,
101+
CacheState::NAMESPACED_CLASS_KEY => $namespacedClass,
102+
CacheState::MODIFICATION_TIME_KEY => filemtime($filePath),
103+
],
104+
]);
105+
106+
// Set the cache state
107+
$this->cacheStateManager->setCacheState(
108+
$filePath,
109+
$cacheState,
110+
);
111+
}
112+
101113
/**
102114
* Get the matched transformers for the given class.
103115
*

src/Core/StreamFilter/FilterInjector.php

+11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Okapi\CodeTransformer\Core\StreamFilter;
44

5+
use Okapi\CodeTransformer\Core\CachedStreamFilter;
56
use Okapi\CodeTransformer\Core\StreamFilter;
67

78
/**
@@ -41,4 +42,14 @@ public function rewrite(string $filePath): string
4142
$filePath
4243
);
4344
}
45+
46+
public function rewriteCached(string $filePath): string
47+
{
48+
return sprintf(
49+
"%s%s/resource=%s",
50+
static::PHP_FILTER_READ,
51+
CachedStreamFilter::CACHED_FILTER_ID,
52+
$filePath,
53+
);
54+
}
4455
}

tests/ClassLoaderMockTrait.php

+8-5
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
namespace Okapi\CodeTransformer\Tests;
44

55
use Okapi\CodeTransformer\Core\AutoloadInterceptor\ClassLoader;
6-
use Okapi\CodeTransformer\Core\Cache\CachePaths;
7-
use Okapi\CodeTransformer\Core\DI;
6+
use Okapi\CodeTransformer\Core\CachedStreamFilter;
87
use Okapi\CodeTransformer\Core\StreamFilter;
98
use Okapi\CodeTransformer\Core\StreamFilter\FilterInjector;
109
use Okapi\Path\Path;
@@ -65,9 +64,13 @@ public function assertWillBeTransformed(string $className): void
6564

6665
public function assertTransformerLoadedFromCache(string $className): void
6766
{
68-
$filePath = $this->findOriginalClassMock($className);
69-
$cachePaths = DI::get(CachePaths::class);
70-
$cachePath = $cachePaths->getTransformedCachePath($filePath);
67+
$filePath = Path::resolve($this->findOriginalClassMock($className));
68+
69+
$cachePath =
70+
FilterInjector::PHP_FILTER_READ .
71+
CachedStreamFilter::CACHED_FILTER_ID . '/resource=' .
72+
$filePath;
73+
7174
$filePathMock = $this->findClassMock($className);
7275

7376
Assert::assertEquals(

0 commit comments

Comments
 (0)