Skip to content

Commit

Permalink
Merge pull request #20 from okapi-web/develop
Browse files Browse the repository at this point in the history
Fixed issues with debugging
  • Loading branch information
WalterWoshid authored May 10, 2024
2 parents 3c2c916 + 1ed8706 commit dd338c9
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 29 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "okapi/code-transformer",
"description": "PHP Code Transformer is a PHP library that allows you to modify and transform the source code of a loaded PHP class.",
"version": "1.3.5",
"version": "1.3.6",
"type": "library",
"homepage": "https://github.com/okapi-web/php-code-transformer",
"license": "MIT",
Expand Down
5 changes: 5 additions & 0 deletions src/CodeTransformerKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use DI\Attribute\Inject;
use Okapi\CodeTransformer\Core\AutoloadInterceptor;
use Okapi\CodeTransformer\Core\Cache\CacheStateManager;
use Okapi\CodeTransformer\Core\CachedStreamFilter;
use Okapi\CodeTransformer\Core\Container\TransformerManager;
use Okapi\CodeTransformer\Core\DI;
use Okapi\CodeTransformer\Core\Exception\Kernel\DirectKernelInitializationException;
Expand Down Expand Up @@ -46,6 +47,9 @@ abstract class CodeTransformerKernel
#[Inject]
private StreamFilter $streamFilter;

#[Inject]
private CachedStreamFilter $cachedStreamFilter;

#[Inject]
private AutoloadInterceptor $autoloadInterceptor;

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

$this->streamFilter->register();
$this->cachedStreamFilter->register();
}

/**
Expand Down
26 changes: 19 additions & 7 deletions src/Core/AutoloadInterceptor/ClassContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@ class ClassContainer
/**
* The class paths.
*
* @var array<string, string>
* @var array<string, array{namespacedClass: class-string, cachedFilePath: string|null}>
*/
private array $namespacedClassPaths = [];
private array $classContext = [];

/**
* Add a class path.
*
* @param string $path
* @param string $class
* @param class-string $namespacedClass
* @param string|null $cachedFilePath
*
* @return void
*/
public function addNamespacedClassPath(string $path, string $class): void
{
$this->namespacedClassPaths[$path] = $class;
public function addClassContext(
string $path,
string $namespacedClass,
?string $cachedFilePath = null,
): void {
$this->classContext[$path] = [
'namespacedClass' => $namespacedClass,
'cachedFilePath' => $cachedFilePath,
];
}

/**
Expand All @@ -39,6 +46,11 @@ public function addNamespacedClassPath(string $path, string $class): void
*/
public function getNamespacedClassByPath(string $path): string
{
return $this->namespacedClassPaths[$path];
return $this->classContext[$path]['namespacedClass'];
}

public function getCachedFilePath(string $filePath): string
{
return $this->classContext[$filePath]['cachedFilePath'];
}
}
33 changes: 29 additions & 4 deletions src/Core/AutoloadInterceptor/ClassLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use DI\Attribute\Inject;
use Okapi\CodeTransformer\Core\AutoloadInterceptor;
use Okapi\CodeTransformer\Core\Cache\CacheStateManager;
use Okapi\CodeTransformer\Core\CachedStreamFilter;
use Okapi\CodeTransformer\Core\Matcher\TransformerMatcher;
use Okapi\CodeTransformer\Core\Options;
use Okapi\CodeTransformer\Core\Options\Environment;
Expand Down Expand Up @@ -120,26 +121,50 @@ public function findFile($namespacedClass): false|string
&& $cacheState
) {
// Use the cached file if transformations have been applied
if ($cacheFilePath = $cacheState->getFilePath()) {
$this->classContainer->addClassContext(
$filePath,
$namespacedClass,
$cacheFilePath,
);

// For cached files, the debugger will have trouble finding the
// original file, that's why we rewrite the file path with a PHP
// stream filter
/** @see CachedStreamFilter::filter() */
return $this->filterInjector->rewriteCached($filePath);
}

// Or return the original file if no transformations have been applied
return $cacheState->getFilePath() ?? $filePath;
return $filePath;
}

// In development mode, check if the cache is fresh
elseif ($this->options->getEnvironment() === Environment::DEVELOPMENT
&& $cacheState
&& $cacheState->isFresh()
) {
return $cacheState->getFilePath() ?? $filePath;
if ($cacheFilePath = $cacheState->getFilePath()) {
$this->classContainer->addClassContext(
$filePath,
$namespacedClass,
$cacheFilePath,
);

return $this->filterInjector->rewriteCached($filePath);
}

return $filePath;
}


// Check if the class should be transformed
if (!$this->transformerMatcher->match($namespacedClass, $filePath)) {
if (!$this->transformerMatcher->matchAndStore($namespacedClass, $filePath)) {
return $filePath;
}

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

// Replace the file path with a PHP stream filter
/** @see StreamFilter::filter() */
Expand Down
60 changes: 60 additions & 0 deletions src/Core/CachedStreamFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace Okapi\CodeTransformer\Core;

use Okapi\CodeTransformer\Core\AutoloadInterceptor\ClassContainer;
use Okapi\CodeTransformer\Core\StreamFilter\Metadata;
use Okapi\Filesystem\Filesystem;
use php_user_filter as PhpStreamFilter;

/**
* # Cached Stream Filter
*
* This class is used to register the cached stream filter.
*
* Because the PHP debugger has trouble finding the original file, we always
* rewrite the file path with a PHP stream filter.
*/
class CachedStreamFilter extends PhpStreamFilter implements ServiceInterface
{
public const CACHED_FILTER_ID = 'okapi.code-transformer.cached';

private string $data = '';

public function register(): void
{
stream_filter_register(static::CACHED_FILTER_ID, static::class);
}

public function filter($in, $out, &$consumed, bool $closing): int
{
// Read stream until EOF
while ($bucket = stream_bucket_make_writeable($in)) {
$this->data .= $bucket->data;
}

// If stream is closed, return the cached file
if ($closing || feof($this->stream)) {
$consumed = strlen($this->data);

$metadata = DI::make(Metadata::class, [
'stream' => $this->stream,
'originalSource' => $this->data,
]);

$classContainer = DI::get(ClassContainer::class);
$cachedFilePath = $classContainer->getCachedFilePath($metadata->uri);

$source = Filesystem::readFile($cachedFilePath);

$bucket = stream_bucket_new($this->stream, $source);
stream_bucket_append($out, $bucket);

// Pass the (cached) source code to the next filter
return PSFS_PASS_ON;
}

// No data has been consumed
return PSFS_PASS_ON;
}
}
36 changes: 24 additions & 12 deletions src/Core/Matcher/TransformerMatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Okapi\CodeTransformer\Core\Container\TransformerManager;
use Okapi\CodeTransformer\Core\DI;
use Okapi\CodeTransformer\Transformer;
use Okapi\Path\Path;
use Okapi\Wildcards\Regex;

/**
Expand Down Expand Up @@ -44,7 +45,7 @@ class TransformerMatcher
*
* @return bool
*/
public function match(string $namespacedClass, string $filePath): bool
public function matchAndStore(string $namespacedClass, string $filePath): bool
{
// Get the transformers
$transformerContainers = $this->transformerContainer->getTransformerContainers();
Expand Down Expand Up @@ -80,24 +81,35 @@ function (bool $carry, TransformerContainer $container) use ($transformerContain

// Cache the result
if (!$matchedTransformerContainers) {
$cacheState = DI::make(EmptyResultCacheState::class, [
CacheState::DATA => [
CacheState::ORIGINAL_FILE_PATH_KEY => $filePath,
CacheState::NAMESPACED_CLASS_KEY => $namespacedClass,
CacheState::MODIFICATION_TIME_KEY => filemtime($filePath),
],
]);

// Set the cache state
$this->cacheStateManager->setCacheState(
$this->cacheEmptyResult(
$namespacedClass,
$filePath,
$cacheState,
);
}

return (bool)$matchedTransformerContainers;
}

private function cacheEmptyResult(
string $namespacedClass,
string $filePath,
): void {
$filePath = Path::resolve($filePath);
$cacheState = DI::make(EmptyResultCacheState::class, [
CacheState::DATA => [
CacheState::ORIGINAL_FILE_PATH_KEY => $filePath,
CacheState::NAMESPACED_CLASS_KEY => $namespacedClass,
CacheState::MODIFICATION_TIME_KEY => filemtime($filePath),
],
]);

// Set the cache state
$this->cacheStateManager->setCacheState(
$filePath,
$cacheState,
);
}

/**
* Get the matched transformers for the given class.
*
Expand Down
11 changes: 11 additions & 0 deletions src/Core/StreamFilter/FilterInjector.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Okapi\CodeTransformer\Core\StreamFilter;

use Okapi\CodeTransformer\Core\CachedStreamFilter;
use Okapi\CodeTransformer\Core\StreamFilter;

/**
Expand Down Expand Up @@ -41,4 +42,14 @@ public function rewrite(string $filePath): string
$filePath
);
}

public function rewriteCached(string $filePath): string
{
return sprintf(
"%s%s/resource=%s",
static::PHP_FILTER_READ,
CachedStreamFilter::CACHED_FILTER_ID,
$filePath,
);
}
}
13 changes: 8 additions & 5 deletions tests/ClassLoaderMockTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
namespace Okapi\CodeTransformer\Tests;

use Okapi\CodeTransformer\Core\AutoloadInterceptor\ClassLoader;
use Okapi\CodeTransformer\Core\Cache\CachePaths;
use Okapi\CodeTransformer\Core\DI;
use Okapi\CodeTransformer\Core\CachedStreamFilter;
use Okapi\CodeTransformer\Core\StreamFilter;
use Okapi\CodeTransformer\Core\StreamFilter\FilterInjector;
use Okapi\Path\Path;
Expand Down Expand Up @@ -65,9 +64,13 @@ public function assertWillBeTransformed(string $className): void

public function assertTransformerLoadedFromCache(string $className): void
{
$filePath = $this->findOriginalClassMock($className);
$cachePaths = DI::get(CachePaths::class);
$cachePath = $cachePaths->getTransformedCachePath($filePath);
$filePath = Path::resolve($this->findOriginalClassMock($className));

$cachePath =
FilterInjector::PHP_FILTER_READ .
CachedStreamFilter::CACHED_FILTER_ID . '/resource=' .
$filePath;

$filePathMock = $this->findClassMock($className);

Assert::assertEquals(
Expand Down

0 comments on commit dd338c9

Please sign in to comment.