mkjpryor/transform

PHP 中动态源代码转换的库

0.1 2013-09-11 12:15 UTC

This package is auto-updated.

Last update: 2024-09-09 02:19:00 UTC


README

Transform 是一个用于在加载的文件上进行源到源转换的实验性 PHP 库。

源到源转换对于许多目的都很有用。例如

  • 在某些情况下,PHP 中缺乏 元编程 功能限制了可以使用该语言完成的事情。使用源到源转换可以缓解这种情况。
  • 可以在加载时将完全不同语言的源代码转换为 PHP,但是请注意,转换只能一次性应用于单个文件。

概念

Transform 的核心是源转换器。源转换器接收一个输入字符串,对其进行处理,并返回一个输出字符串。可以连接多个转换器,使一个转换器的输出成为下一个转换器的输入。

内部,转换器可以使用它们喜欢的任何技术来转换代码,只要输入和输出是字符串。当与 PHP-Token-ReflectionPHP-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\MagicConstantTransformerMagicConstantTransformer 接收任何有效的 PHP 代码,并将其中的魔法常量 __FILE____DIR__ 替换为适合原始文件中的字符串。这种转换默认不应用 - 要将其应用于加载的代码,必须像配置任何其他转换器一样配置它。

转换加载器

源转换通过 Transform\ClassLoader\TransformingLoader 实例应用于代码。

包含单个文件

可以在加载时使用 TransformingLoader::includeFile 应用转换。如果需要转换,应使用此方法代替 includerequire

<?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没有关于先前源的概念,因此不会从缓存中删除旧键,这可能导致缓存变得很大,如果预转换的源频繁更改。遗憾的是,唯一的方法是清除缓存并重新开始。

另一方面,由于缓存使用源哈希值,这意味着只有当源实际上更改时才会重新计算转换,而不仅仅是源文件的修改时间更新(例如更改然后撤销更改)。