mkjpryor / transform
Requires
- php: >=5.3.0
- doctrine/cache: *
Requires (Dev)
This package is auto-updated.
Last update: 2024-09-09 02:19:00 UTC
README
Transform 是一个用于在加载的文件上进行源到源转换的实验性 PHP 库。
源到源转换对于许多目的都很有用。例如
- 在某些情况下,PHP 中缺乏 元编程 功能限制了可以使用该语言完成的事情。使用源到源转换可以缓解这种情况。
- 可以在加载时将完全不同语言的源代码转换为 PHP,但是请注意,转换只能一次性应用于单个文件。
概念
Transform 的核心是源转换器。源转换器接收一个输入字符串,对其进行处理,并返回一个输出字符串。可以连接多个转换器,使一个转换器的输出成为下一个转换器的输入。
内部,转换器可以使用它们喜欢的任何技术来转换代码,只要输入和输出是字符串。当与 PHP-Token-Reflection 或 PHP-Parser 结合使用时,这变得非常强大。
可以将转换的结果进行缓存,以便仅在原始源代码更改时重新应用。这意味着应用转换的代价并不是问题。
通过一个加载器对象处理将转换后的代码加载到 PHP 中,该加载器对象接收一个源转换器,将其应用于加载的文件内容。该加载器对象旨在作为一个 类自动加载器 使用,并提供在不需要自动加载的情况下加载单个文件的功能(例如,包含函数定义的文件)。
安装
Transform 通过 composer 安装
#!json
{
"require" : {
"mkjpryor/transform" : "0.1"
}
}
源转换器
任何实现 Transform\Transformer\SourceTransformer
接口的对象都可以作为源转换器
<?php
namespace Transform\Transformer;
interface SourceTransformer {
/**
* Apply the source transformation to the given source and return the
* transformed source
*
* Metadata can be injected by source transformers, but is guaranteed to have
* the key 'originalFile' containing the fully qualified path to the original
* source file
* ArrayObject is used rather than a plain array to get pass-by-reference
* semantics without worrying about passing by reference...
*
* @param string $source
* @param \ArrayObject $metadata
* @return string
*/
public function apply($source, \ArrayObject $metadata);
}
使用 Transform\Transformer\Pipeline
将源转换器连接起来,它本身也是一个转换
<?php
$transformer = new \Transform\Transformer\Pipeline([
new SomeSourceTransformer(), new AnotherSourceTransformer()
]);
// Pass $transformer to a loader object...
在这种情况下,SomeSourceTransformer::apply
的输出用作 AnotherSourceTransformer::apply
的输入,而 AnotherSourceTransformer::apply
的输出成为管道的输出。
Transform 提供的唯一执行代码修改的源转换器是 Transform\Transformer\MagicConstantTransformer
。 MagicConstantTransformer
接收任何有效的 PHP 代码,并将其中的魔法常量 __FILE__
和 __DIR__
替换为适合原始文件中的字符串。这种转换默认不应用 - 要将其应用于加载的代码,必须像配置任何其他转换器一样配置它。
转换加载器
源转换通过 Transform\ClassLoader\TransformingLoader
实例应用于代码。
包含单个文件
可以在加载时使用 TransformingLoader::includeFile
应用转换。如果需要转换,应使用此方法代替 include
或 require
。
<?php
// Set $transformer to the required transformer (e.g. a pipeline of other transformers)
$loader = new \Transform\ClassLoader\TransformingLoader($transformer);
$loader->includeFile('/path/to/file/to/include.php');
这将应用给定的转换到指定的文件,并将结果作为PHP代码进行评估。注意,如果转换后的代码包含已经定义的类/函数等,这将导致错误。
自动加载类(以及特质/接口)
TransformingLoader
扩展了 Composer自动加载器,以提供其自动加载能力。加载器必须告知它负责加载和转换哪些类,然后注册为自动加载器。
<?php
// Set $transformer to the required transformer (e.g. a pipeline of other transformers)
$loader = new \Transform\ClassLoader\TransformingLoader($transformer);
// Tell the loader to apply transformations to classes in a particular namespace
$loader->add('Some\\Namespace', '/path/to/package/src');
// Or using PEAR naming conventions
$loader->add('Some_Namespace_', '/path/to/package/src');
// Or using a class map
$loader->addClassMap([
'Some\\Namespace\\SomeClass' => '/path/to/class/file.php',
'Some\\Other\\Namespace\\OtherClass' => '/path/to/other/class/file.php'
]);
// Register the autoloader - it is important to set the $prepend argument to true
// so that it gets executed before other autoloaders
$loader->register(true);
// The autoloader will then be triggered when a class it knows about is used
$obj = new \Some\Other\Namespace\OtherClass();
显然,转换后的代码必须仍然包含类定义,否则PHP将报错。
与Composer一起使用TransformingLoader
如果您使用Composer来管理项目依赖,则需要做一些努力,以使其与TransformingLoader
良好协作。
假设我们有一个现有的项目,该项目目前没有使用我们想要应用的任何转换。我们目前正在使用Composer来自动加载项目的依赖项和项目本身的类,以及包括一个包含一些函数的文件。
#!javascript
// composer.json
{
"require" : {
"some/component" : "*",
"some/other-component" : "*"
},
"autoload" : {
"files" : [ "src/functions/file_with_functions.php" ],
"psr-0" : {
"MyNamespace" : "src/"
}
}
}
为了将转换应用于加载的文件,我们必须告诉Composer包括一个引导文件,其中我们配置和注册一个合适的TransformingLoader
。
#!javascript
// composer.json
{
"require" : {
"some/component" : "*",
"some/other-component" : "*"
},
"autoload" : {
"files" : [ "src/bootstrap.php" ]
}
}
src/bootstrap.php
的外观取决于我们想要实现什么。
仅对项目文件应用转换(即不针对依赖项)
为了只对我们项目的文件应用转换,我们只需创建一个TransformingLoader
,添加我们项目的命名空间,将其注册为自动加载器,并包含函数文件。
<?php
/**
* src/bootstrap.php
*/
// Set $transformer to the required transformer (e.g. a pipeline of other transformers)
// Create a transforming loader
$loader = new \Transform\Transformer\TransformingLoader($transformer);
// Add our project's namespace (we use __DIR__ since this file sits in src
$loader->add("MyNamespace", __DIR__);
// Register the transforming loader as an autoloader
$loader->register(true);
// Include our functions file
$loader->includeFile(__DIR__ . "/functions/file_with_functions.php");
将转换应用于Composer加载的所有类(即完全替换Composer自动加载器)
也可以完全替换所有未来的类加载的Composer自动加载器(遗憾的是,我们无法处理已经加载的类或已经包含的文件...)
<?php
/**
* src/bootstrap.php
*/
// Get the Composer autoloader responsible for loading dependencies
$composerLoader = require __DIR__ . '/../vendor/autoload.php';
// Set $transformer to the required transformer (e.g. a pipeline of other transformers)
// Create a transforming loader
$loader = new \Transform\Transformer\TransformingLoader($transformer);
// Copy the configuration of the Composer autoloader to our transforming loader
foreach( $composerLoader->getPrefixes() as $prefix => $path ) {
$loader->add($prefix, $path);
}
$loader->addClassMap($composerLoader->getClassMap());
$loader->setUseIncludePath($composerLoader->getUseIncludePath());
// Add our project's namespace (as above)
$loader->add("MyNamespace", __DIR__);
// Unregister the Composer autoloader and register the transforming loader
$composerLoader->unregister();
$loader->register(true);
// Include our functions file
$loader->includeFile(__DIR__ . "/functions/file_with_functions.php");
缓存以提高性能
由于一些转换可能会很昂贵,因此可以使用Transform\Tranformer\CachingTransformer
来缓存转换的结果。CachingTransformer
实现了Transform\Transformer\SourceTransformer
,并使用装饰器模式包装现有的转换器,并将结果缓存在一个Doctrine Cache中。
<?php
// Set $transformer to the required transformer (e.g. a pipeline of other transformers)
// Create a cache
$cache = new \Doctrine\Common\Cache\FilesystemCache('/path/to/cache/dir');
// Wrap $transformer in a CachingTransformer
$transformer = new \Transform\Transformer\CachingTransformer($transformer, $cache);
// Pass $transformer on to a transforming loader
使用Doctrine Cache组件的优点是,它可以根据情况使用许多不同的缓存。在开发期间,最有可能使用\Doctrine\Common\Cache\ArrayCache
(即转换仅在脚本运行时进行缓存)。在生产环境中,您可能会使用\Doctrine\Common\Cache\FilesystemCache
来持久化转换的结果。如果您想在多台机器之间共享缓存的转换,Doctrine Cache组件还提供了对许多分布式缓存/数据库的实现(例如Memcached、Redis、Riak)。
警告:缓存大小
CachingTransformer
通过获取其提供的源哈希值并使用该哈希值作为缓存键(如果键存在则返回缓存的版本,如果键不存在则应用包装的转换器并将结果缓存)来工作。
因此,CachingTransformer
没有关于先前源的概念,因此不会从缓存中删除旧键,这可能导致缓存变得很大,如果预转换的源频繁更改。遗憾的是,唯一的方法是清除缓存并重新开始。
另一方面,由于缓存使用源哈希值,这意味着只有当源实际上更改时才会重新计算转换,而不仅仅是源文件的修改时间更新(例如更改然后撤销更改)。