限流 php接口限流 代码_有效地使用PHP流

限流 php接口限流 代码

In my previous article we've discovered the basics of PHP Streams and how powerful they were. In this tutorial we are going to use this power in the real world. First I'll show you how to build your custom filters and attach them to a stream, then we'll package our filters inside a document parser application.

上一篇文章中,我们发现了PHP Streams的基础知识以及它们的功能强大。 在本教程中,我们将在现实世界中使用此功能。 首先,我将向您展示如何构建自定义过滤器并将其附加到流,然后将过滤器打包到文档解析器应用程序中。

You are encouraged to read the previous article in case you haven't yet, as understanding the introduction will be essential for following along with this part.

如果您还没有阅读,请鼓励您阅读上一篇文章 ,因为理解介绍对于跟随本部分将是必不可少的。

The full source code to this article is available on Github.

Github上提供本文的完整源代码。

使用过滤器 (Using Filters)

As previously stated, filters are pieces of code that can be attached to a stream to perform operations on the data while reading or writing. PHP has a nice set of built-in filters such as string.toupper, string.tolower or string.strip_tags. Some PHP extensions also provide their own filters. For example, the mcrypt extension installs the mcrypt.* and mdecrypt.* filters. We can use the function stream_get_filters() to fetch the list of filters available on your machine.

如前所述,过滤器是一段代码,可以将其附加到流上,以在读取或写入时对数据执行操作。 PHP有一组不错的内置过滤器,例如string.toupperstring.tolowerstring.strip_tags 。 一些PHP扩展也提供了自己的过滤器。 例如, mcrypt扩展安装了mcrypt.*mdecrypt.*过滤器。 我们可以使用函数stream_get_filters()来获取您机器上可用的过滤器列表。

Once that we know which filters we can count on, we can append any number of filters to a stream resource using stream_filter_append():

一旦知道了我们可以依靠的过滤器,就可以使用stream_filter_append()将任意数量的过滤器附加到流资源中:

$h = fopen('lorem.txt', 'r');
    stream_filter_append($h, 'convert.base64-encode');
    fpassthru($h);
    fclose($h);

or open a stream using the php://filter meta wrapper:

或使用php://filter元包装器打开流:

$filter = 'convert.base64-encode';
    $file = 'lorem.txt';
    $h = fopen('php://filter/read=' . $filter . '/resource=' . $file,'r'); 
    fpassthru($h);
    fclose($h);

In the above samples the function fpassthru() will output the same encoded version of the sample file. Simple, isn't it? Let's see what we can do with the php_user_filter class.

在以上示例中,函数fpassthru()将输出示例文件的相同编码版本。 很简单,不是吗? 让我们看看如何使用php_user_filter类。

读取时过滤数据:降价过滤器 (Filtering data on read-time: the Markdown filter)

Our first custom filter will be appended to a reading stream in order to convert the markdown-formatted data from the source into HTML markup. PHP provides the base class php_user_filter that we're extending. This base class has two properties: filtername and params. filtername contains the label used to register our filter with stream_filter_register(), while params can be used by stream_filter_append() to pass data to filters.

我们的第一个自定义过滤器将附加到阅读流中,以将markdown格式的数据从源转换为HTML标记。 PHP提供了我们正在扩展的基类php_user_filter 。 该基类具有两个属性: filternameparamsfiltername包含用于注册我们与过滤器上的标签stream_filter_register()params可以通过使用stream_filter_append()将数据传递到过滤器。

The main worker method that we must override is filter(). This method is called by the parent stream and receives four parameters:

我们必须重写的主要worker方法是filter() 。 此方法由父流调用,并接收四个参数:

  • $in: a pointer to a group of buckets objects containing the data to be filtered.

    $in :指向一组存储桶对象的指针,其中包含要过滤的数据。

  • $out: a pointer to another group of buckets for storing the converted data.

    $out :指向另一组存储区的指针,用于存储转换后的数据。

  • $consumed: a counter passed by reference that must be incremented by the length of converted data.

    $consumed :通过引用传递的计数器,必须将其转换后的数据的长度增加。

  • $closing: a boolean flag that is set to TRUE if we are in the last cycle and the stream is about to close

    $closing :一个布尔标志,如果我们在最后一个周期中并且流将要关闭,则设置为TRUE

The other two optional methods, onCreate() and onClose(), are called respectively when our class is created and destroyed. They are useful if our filter needs to instantiate resources such as other streams or data buffers that must be released at the end of the conversion.

创建和销毁我们的类时,分别调用了另外两个可选方法onCreate()onClose() 。 如果我们的过滤器需要实例化必须在转换结束时释放的其他流或数据缓冲区之类的资源,它们将非常有用。

Our filter uses these methods to deal with a temporary data stream managed by a private property called $bufferHandle. The onCreate() method will fail returning false if the buffer stream is not available, while the onClose() method closes the resource. Our MarkdownFilter uses Michel Fortin's parser.

我们的过滤器使用这些方法来处理由称为$bufferHandle的私有属性管理的临时数据流。 如果缓冲区流不可用,则onCreate()方法将无法返回false ,而onClose()方法将关闭资源。 我们的MarkdownFilter使用Michel Fortin的解析器

<?php
namespace MarkdownFilter;

use \Michelf\MarkdownExtra as MarkdownExtra;

class MarkdownFilter extends \php_user_filter
{

    private $bufferHandle = '';

    public function filter($in, $out, &$consumed, $closing)
    {
        $data = '';

        while ($bucket = stream_bucket_make_writeable($in)) {
            $data .= $bucket->data;
            $consumed += $bucket->datalen;
        }

        $buck = stream_bucket_new($this->bufferHandle, '');

        if (false === $buck) {
            return PSFS_ERR_FATAL;
        }

        $parser = new MarkdownExtra;
        $html = $parser->transform($data);
        $buck->data = $html;

        stream_bucket_append($out, $buck);
        return PSFS_PASS_ON;
    }

    public function onCreate()
    {
        $this->bufferHandle = @fopen('php://temp', 'w+');
        if (false !== $this->bufferHandle) {
            return true;
        }
        return false;
    }

    public function onClose()
    {
        @fclose($this->bufferHandle);
    }
}

In the main filter() method I'm collecting all the content into a $data variable that will be converted later. The first loop cycles through the input stream, using stream_bucket_make_writeable(), to retrieve the current bucket of data. The content of each bucket ($bucket->data) is appended to our container and the $consumed parameter is incremented by the length of the retrieved data ($bucket->datalen).

在main filter()方法中,我将所有内容收集到$data变量中,稍后将对其进行转换。 第一个循环使用stream_bucket_make_writeable()在输入流中循环,以检索当前数据桶。 每个存储桶的内容( $bucket->data )都将附加到我们的容器中,并且$consumed参数将增加检索到的数据的长度( $bucket->datalen )。

When all of the data is collected, we need to create a new empty bucket that will be used to pass the converted content to the output stream. We use stream_bucket_new() to do this and if the operation fails we return the constant PSFS_ERR_FATAL that will trigger a filter error. Since we need a resource pointer to create a bucket, we use the $bufferHandle property, which has been initialized earlier using the php://temp built-in stream wrapper.

收集所有数据后,我们需要创建一个新的空存储桶,该存储桶将用于将转换后的内容传递到输出流。 我们使用stream_bucket_new()进行此操作,如果操作失败,我们将返回常量PSFS_ERR_FATAL ,它将触发过滤器错误。 由于我们需要资源指针来创建存储桶,因此我们使用$bufferHandle属性,该属性先前已使用php://temp内置流包装器进行了初始化。

Now that we have the data and the output bucket, we can instantiate a Markdown parser, convert all the data and store it in the bucket's data property. Finally the result is appended to the $out resource pointer with stream_bucket_append() and the function returns the constant PSFS_PASS_ON to communicate that the data was processed successfully.

现在我们有了数据和输出存储桶,我们可以实例化Markdown解析器,转换所有数据并将其存储在存储桶的data属性中。 最后,将结果通过stream_bucket_append()附加到$out资源指针,该函数返回常量PSFS_PASS_ON来传达数据已成功处理的信息。

We can now use the filter in this way:

现在,我们可以通过以下方式使用过滤器:

// Require the MarkdownFilter or autoload

    // Register the filter
    stream_filter_register("markdown", "\MarkdownFilter\MarkdownFilter")
        or die("Failed to register filter Markdown");

    // Apply the filter
    $content = file_get_contents(
        'php://filter/read=markdown/resource=file:///path/to/somefile.md'
    );

    // Check for success...
    if (false === $content) {
        echo "Unable to read from source\n";
        exit(1);
    }

    // ...and enjoy the results
    echo $content, "\n";

Please note that the use directive has no effect and the fully qualified class name must be provided when registering a custom filter.

请注意, use指令无效,注册自定义过滤器时必须提供完全限定的类名。

在写时过滤数据:模板过滤器 (Filtering data on write-time: the Template filter)

Once we have our content converted from Markdown to HTML, we need to pack it inside a page template. This can be anything from a basic HTML structure to a complex page layout with CSS styles. So, in the same way as we did a read-and-convert action with the input filter, we're going to write a convert-and-save action, embedding the template engine of our choice into the output stream. I chose the RainTPL parser for this tutorial, but you are free to adapt the code to the one you prefer.

将内容从Markdown转换为HTML后,我们需要将其打包在页面模板中。 从基本HTML结构到具有CSS样式的复杂页面布局,它可以是任何东西。 因此,与对输入过滤器执行读取和转换操作的方式相同,我们将编写一个转换并保存操作,将我们选择的模板引擎嵌入到输出流中。 在本教程中,我选择了RainTPL解析器 ,但是您可以自由地将代码修改为您喜欢的代码。

The structure of the template filter is similar to our input filter. First we'll register the filter in this way:

模板过滤器的结构类似于我们的输入过滤器。 首先,我们将以这种方式注册过滤器:

stream_filter_register("template.*", "\TemplateFilter\TemplateFilter")
    or die("Failed to register filter Template");

We use the format filtername.* as filter label, so that we can use that * to pass some data to our class. This is necessary because, as far as I know, there is no way to pass parameters to a filter applied using a php://filter wrapper. If you know of a way, please post it in the comments below.

我们使用filtername.*格式作为过滤器标签,以便我们可以使用*将一些数据传递给我们的类。 这是必要的,因为据我所知,无法将参数传递给使用php://filter包装器应用的php://filter器。 如果您知道一种方法,请在下面的评论中发布。

The filter is then applied in this way:

然后以这种方式应用过滤器:

$result = file_put_contents(
        'php://filter/write=template.' .
        base64_encode('Some Document Title') . '/resource=file:///path/to/destination.html',
        $content
    );

A title for the document is passed using the second part of the filter name and will be processed by the onCreate() method. Going further, we can use this trick to pass an array of serialized data with custom configuration settings for the template engine.

使用过滤器名称的第二部分传递文档的标题,并将由onCreate()方法处理。 更进一步,我们可以使用此技巧来传递带有模板引擎的自定义配置设置的序列化数据数组。

The TemplateFilter class:

TemplateFilter类:

<?php
namespace TemplateFilter;

use \Rain\Tpl as View;

class TemplateFilter extends \php_user_filter
{

    private $bufferHandle = '';
    private $docTitle = 'Untitled';

    public function filter($in, $out, &$consumed, $closing)
    {
        $data = '';

        while ($bucket = stream_bucket_make_writeable($in)) {
            $data .= $bucket->data;
            $consumed += $bucket->datalen;
        }

        $buck = stream_bucket_new($this->bufferHandle, '');

        if (false === $buck) {
            return PSFS_ERR_FATAL;
        }

        $config = array(
            "tpl_dir"       => dirname(__FILE__) . "/templates/",
            "cache_dir"     => sys_get_temp_dir() . "/",
            "auto_escape"   => false
        );
        View::configure($config);

        $view = new View();
        if (!$closing) {
            $matches = array();
            if (preg_match('/<h1>(.*)<\/h1>/i', $data, $matches)) {

                if (!empty($matches[1])) {
                    $this->docTitle = $matches[1];
                }
            }
            $view->assign('title', $this->docTitle);
            $view->assign('body', $data);
            $content = $view->draw('default', true);
            $buck->data = $content;
        }

        stream_bucket_append($out, $buck);
        return PSFS_PASS_ON;
    }

    public function onCreate()
    {
        $this->bufferHandle = @fopen('php://temp', 'w+');
        if (false !== $this->bufferHandle) {

            $info = explode('.', $this->filtername);
            if (is_array($info) && !empty($info[1])) {
                $this->docTitle = base64_decode($info[1]);
            }

            return true;
        }
        return false;
    }

    public function onClose()
    {
        @fclose($this->bufferHandle);
    }
}

We still have the $bufferHandle parameter pointing to the temporary stream, and we also have a parameter called $docTitle that will contain (by priority):

我们仍然有指向临时流的$bufferHandle参数,还有一个名为$docTitle的参数,它将包含(按优先级排列):

  1. the content of the first H1 tag (if exists) of the parsed document, or

    所解析文档的第一个H1标签(如果存在)的内容,或者

  2. the decoded content of the second part of the filter name, or

    过滤器名称第二部分的解码内容,或者
  3. the default fallback value 'Untitled'.

    默认后备值“无标题”。

Inside the onCreate() method, after the buffer stream is initialized, we're dealing with option number two:

onCreate()方法中,在缓冲流初始化之后,我们要处理第二个选项:

$info = explode('.', $this->filtername);
    if (is_array($info) && !empty($info[1])) {
        $this->docTitle = base64_decode($info[1]);
    }

The main filter() method can be divided in five steps here. The first two steps are identical to the Markdown filter: all the data is fetched from the input buckets and stored inside the variable $data, then an empty output bucket is created to store the processed content.

这里的main filter()方法可以分为五个步骤。 前两个步骤与Markdown过滤器相同:所有数据都从输入存储桶中提取并存储在变量$data ,然后创建一个空的输出存储桶来存储处理后的内容。

In the third step the template parser class is loaded and configured. I'm asking the system for a temporary directory to use for caching, disabling HTML tags escape feature and setting the templates directory.

在第三步中,将加载并配置模板解析器类。 我要求系统提供一个用于缓存的临时目录,禁用HTML标记转义功能并设置templates目录。

The default template used here is very simple, with the variables defined as {$VarName}:

此处使用的默认模板非常简单,变量定义为{$VarName}

<!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8">
            <title>{$title}</title>
        </head>
        <body>
            {$body}
        </body>
    </html>

The fourth step is where the actual parsing takes place. First I'm searching for a document title inside a H1 tag. Then I set the body and title variables defined in the template and finally process the document. The first parameter of the draw() method is the template name, the second tells to return the string instead of printing it.

第四步是实际解析的地方。 首先,我正在H1标签内搜索文档标题。 然后,设置模板中定义的bodytitle变量,最后处理文档。 draw()方法的第一个参数是模板名称,第二个告诉返回字符串而不是打印字符串。

The last step is placing the parsed content into the output bucket and appending it to the output resource, returning PSFS_PASS_ON.

最后一步是将已解析的内容放入输出存储桶中,并将其附加到输出资源中,返回PSFS_PASS_ON

放在一起:文档解析器 (Putting it all together: a document parser)

Now that we have the basic blocks in place it's time to build our document parser utility. The utility app lives in its own directory mddoc. Our custom filters live under the lib directory using a PSR-0 directory and namespace structure. I've used Composer to keep track of dependencies

现在我们已经有了基本的块,是时候构建我们​​的文档解析器实用程序了。 该实用程序应用程序位于其自己的目录mddoc 。 我们的自定义过滤器使用PSR-0目录和名称空间结构位于lib目录下。 我已经使用Composer跟踪依赖关系

"require": {
        "php": ">=5.3.0",
        "michelf/php-markdown": "*",
        "rain/raintpl": "3.*"
    },

and autoloading:

和自动加载:

"autoload": {
        "psr-0": { 
            "MarkdownFilter": "lib",
            "TemplateFilter": "lib"
        }
    }

The main application file is mddoc that can be executed like this:

主应用程序文件是mddoc ,可以这样执行:

$ /path/to/mddoc -i /path/to/sourcedir -o /path/to/destdir

The app file looks like:

该应用程序文件如下所示:

#!/usr/bin/env php
<?php
/**
 * Markdown Tree Converter
 * 
 * Recursive converts all markdown files from a source directory to HTML
 * and places them in the destination directory recreating the structure
 * of the source and applying a template parser.
 */

// Composer autoloader
require_once dirname(__FILE__) . '/vendor/autoload.php';


// Deals with command-line input arguments
function usage()
{
    printf(
        "Usage: %s -i %s -o %s\n",
        basename(__FILE__),
        '/path/to/sourcedir',
        '/path/to/destdir'
    );
}

if (5 > $argc) {
    usage();
    exit;
}

$in = array_search('-i', $argv);
$src = realpath($argv[$in+1]);
if (!is_dir($src) || !is_readable($src)) {
    echo "[ERROR] Invalild source directory.\n";
    usage();
    exit(1);
}

$out = array_search('-o', $argv);
$dest = realpath($argv[$out+1]);
if (!is_dir($dest) || !is_writeable($dest)) {
    echo "[ERROR] Invalild destination directory.\n";
    usage();
    exit(1);
}

// Register custom read-time MarkdownFilter
stream_filter_register("markdown", "\MarkdownFilter\MarkdownFilter")
    or die("Failed to register filter Markdown");

// Register custom write-time TemplateFilter
stream_filter_register("template.*", "\TemplateFilter\TemplateFilter")
    or die("Failed to register filter Template");

// Load directory iterator for source
$it = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($src),
    RecursiveIteratorIterator::SELF_FIRST
);

// For every valid item
while ($it->valid()) {

    // Exclude dot items (., ..)
    if (!$it->isDot()) {

        // If current item is a directory, the same empty directory
        // is created on destination
        if ($it->isDir()) {
            $path = $dest . '/' . $it->getFileName();
            if ((!@is_dir($path)) && !@mkdir($path, 0777, true)) {
                echo "Unable to create folder {$path}\n";
                exit(1);
            }
        }

        // If current item is a markdown (*.md) file it's processed and
        // saved at the coresponding destination path
        if ($it->isFile() && 'md' == $it->getExtension()) {
            $path = $it->key();
            if (!is_readable($path)) {
                echo "Unable to read file {$path}\n";
                exit(2);
            }

            $content = file_get_contents(
                'php://filter/read=markdown/resource=file://' . $path
            );
            if (false === $content) {
                echo "Unable to read from source '" . $path . "'\n";
                exit(3);
            }

            $pathinfo = pathinfo($dest . '/' . $it->getSubPathName());
            $target = $pathinfo['dirname']
                . '/' . $pathinfo['filename'] . '.html';

            $result = file_put_contents(
                'php://filter/write=template.' .
                base64_encode(basename($path)) . '/resource=file://' . $target,
                $content
            );
            if (false === $result) {
                echo "Unable to write file '" . $target . "'\n";
                exit(4);
            }

        }
    }

    $it->next();
}

exit(0);

First we include our autoloader, then we go ahead with argument validation:

首先,我们包括自动加载器,然后继续进行参数验证:

  • the command line must match the above example,

    命令行必须与上面的示例匹配,
  • the source directory must exist end be readable,

    源目录必须以可读的形式存在,
  • the destination directory must exist and be writeable.

    目标目录必须存在并且是可写的。

Then we register the custom filters (with full class path, remember) and instantiate a RecursiveIteratorIterator object to walk the source directory recursively. The main loop cycles through all valid elements fetched by the iterator. All the elements, excluding the dotfiles, are processed as follows:

然后,我们注册自定义过滤器(记住完整的类路径),并实例化RecursiveIteratorIterator对象以RecursiveIteratorIterator遍历源目录。 主循环循环遍历迭代器获取的所有有效元素。 除点文件外,所有元素的处理方式如下:

  • if the current element is a directory, try to re-create the relative path with the same name starting from the destination path.

    如果当前元素是目录,请尝试从目标路径开始以相同的名称重新创建相对路径。
  • if the current element is a markdown file (.md) the content of the file is read into a variable using the markdown read filter, then a new file with the .html extension is written at the same relative path starting from the destination directory with the `template.` filter applied.

    如果当前元素是markdown文件( .md),则使用markdown读取过滤器将文件内容读取到变量中,然后将具有.html扩展名的新文件写入相同的相对路径,从目标目录开始,模板。 `已应用过滤器。

The result is your documentation directory structure fully converted into HTML with one command. Not bad.

结果是您的文档目录结构可以通过一个命令完全转换为HTML。 不错。

摘要 (Summary)

We covered a lot of useful ground here and we also have a fully functional utility to… ehm, "append" to our tool chain. I'll leave it up to you to take it further and create other tools and component like this to enhance our projects. Happy coding!

我们在这里介绍了很多有用的知识,我们还有一个功能齐全的实用程序,可以……“附加”到我们的工具链中。 我将由您自己决定进一步发展并创建其他工具和组件来增强我们的项目。 祝您编码愉快!

翻译自: https://www.sitepoint.com/using-php-streams-effectively/

限流 php接口限流 代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值