这是我正在使用的代码,因为我正在努力解决方案。
public function indexAction()
{
//id3 options
$options = array("version" => 3.0, "encoding" => Zend_Media_Id3_Encoding::ISO88591, "compat" => true);
//path to collection
$path = APPLICATION_PATH . '/../public/Media/Music/';//Currently Approx 2000 files
//inner iterator
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
//iterator
$iterator = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $file) {
if (!$file->isDir() && $file->getExtension() === 'mp3') {
//real path to mp3 file
$filePath = $file->getRealPath();
Zend_Debug::dump($filePath);//current results: accepted path no errors
$id3 = new Zend_Media_Id3v2($filePath, $options);
foreach ($id3->getFramesByIdentifier("T*") as $frame) {
$data[$frame->identifier] = $frame->text;
}
Zend_Debug::dump($data);//currently can scan the whole collection without timing out, but APIC data not being processed.
}
}
}
问题: 在多个目录中处理mp3文件的文件系统。将id3标记数据提取到数据库(3个表),并将标记中的封面图像提取到单独的文件中。
我可以处理实际的提取和数据处理。我的问题是输出。
使用Zend Framework 1.x处理输出缓冲的方式,输出正在处理文件的指示器很困难。在没有输出缓冲的旧式PHP脚本中,您可以在循环的每次迭代中打印出一些html,并有一些进度指示。
我希望能够处理每个专辑的目录,输出结果然后继续到下一个专辑的目录。只需要用户干预某些错误。
任何帮助都将不胜感激。
Javascript不是我正在寻找的解决方案。我认为这应该可以在PHP和ZF 1 MVC的构造中实现。
我这样做主要是为了自己的启蒙,这似乎是学习一些重要概念的好方法。
[编辑]
好吧,关于如何将其分解为更小的块的一些想法怎么样。处理一个块,提交,处理下一个块,一种事物。进出ZF。
[编辑]
我开始看到我正在努力实现的问题。似乎输出缓冲不仅仅发生在ZF中,它从ZF一直到浏览器都在发生。 Hmmmmm ...
答案 0 :(得分:7)
这是你不该做的的一个典型例子,因为
您正在尝试使用PHP解析ID3 tag
这很慢并且尝试同时拥有多个解析文件肯定会让它更慢
RecursiveDirectoryIterator
会加载文件夹和子文件夹中的所有文件,我认为没有限制..第二天可以是2,000
100,000
?总处理时间是不可预测的,在某些情况下这肯定需要几个小时
高度依赖单个文件系统,使用当前架构,文件存储在本地系统中,因此很难拆分文件并进行适当的负载平衡
您没有检查之前是否已提取过文件信息,结果为Loop and extraction Duplication
No locking system
..这意味着此过程可以同时启动,导致服务器上的性能普遍降低
我的建议是不要使用loop
或RecursiveDirectoryIterator
批量处理文件。
将文件上传或传输后立即定位到服务器。这样你一次只能处理一个文件这样可以分散处理时间。
您的问题正是作业队列的目的您也不仅限于使用PHP
实施解析..您可以利用C
或{{ 1}}表现
优势
C++
PHP
服务器中的多语言客户端
示例已经过测试
预期流程客户
预期的流程服务器
最后,这个处理可以在多个服务器上并行完成
答案 1 :(得分:2)
一种解决方案是使用作业队列,例如Gearman。 Gearman是解决此类问题的绝佳解决方案,可与Zend Framework轻松集成(http://blog.digitalstruct.com/2010/10/17/integrating-gearman-into-zend-framework/)
它将允许您创建一个工作人员来处理每个“chuck”,允许您的进程在处理作业时继续解锁,非常方便长时间运行的过程,如音乐/图像处理等http://gearman.org/index.php?id=getting_started
答案 2 :(得分:1)
我不熟悉Zend Framework的工作原理。我会给你一个一般的建议。当处理那么多次迭代并且可能长时间处理的进程时,通常建议将长进程转移到后台进程中。或者,在网络相关中,转入cron工作。
如果你想要使用的进程是针对单个站点的,你可以在你的cronjob中实现这样的东西(注意:粗糙的伪代码):
<?php
$targetdir = "/path/to/mp3";
$logdir = "/path/to/log/";
//check if current state is exists. If it does, then previous cronjob is still running
//we should stop this process so that it doesn't do duplicated process which might have introduced random bugs
if(file_exists($logdir."current-state")){
exit;
}
//start process, write state to logdir
file_put_contents($logdir."current-log", "process started at ".date("Y-m-d H:i:s"));
file_put_contents($logdir."current-state", "started\t".date("Y-m-d H:i:s"));
$dirh = opendir($targetdir);
while($file = readdir($dirh)){
//lets ignore current and parent dir
if(in_array($file, array('.', '..'))) continue;
//do whatever process you want to do here:
//you might want to write another log, too:
file_put_contents($logdir."current-log", "processing file {$file}", FILE_APPEND);
}
closedir($dirh);
file_put_contents($logdir."current-log", "process finished at ".date("Y-m-d H:i:s"));
//process is finished, delete current-state:
unlink($logdir."current-state");
接下来,在您的php for web文件中,您可以添加片段,管理页面或页脚,或任何您想要的页面,以查看进度:
<?php
if(file_exists($logdir."current-state")){
echo "<strong>there are background process running.</strong>";
} else {
echo "<strong>no background process running.</strong>";
}
答案 3 :(得分:1)
我应该建议使用插件。
class Postpone extends Zend_Controller_Plugin_Abstract
{
private $tail;
private $callback;
function __construct ($callback = array())
{
$this->callback = $callback;
}
public function setRequest (Zend_Controller_Request_Abstract $request)
{
/*
* We use layout, which essentially contains some html and a placeholder for action output.
* We put the marker into this placeholder in order to figure out "the tail" -- the part of layout that goes after placeholder.
*/
$mark = '---cut-here--';
$layout = $this->getLayout ();
$layout->content = $mark;
/*
* Now we have it.
*/
$this->tail = preg_replace ("/.*$mark/s", '', $layout->render ());
}
public function postDispatch (Zend_Controller_Request_Abstract $request)
{
$response = $this->getResponse ();
$response->sendHeaders ();
/*
* The layout generates its output to the default section of the response.
* This output inludes "the tail".
* We don't need this tail shown right now, because we have callback to do.
* So we remove it here for a while, but we'll show it later.
*/
echo substr ($this->getResponse ()
->getBody ('default'), 0, - strlen ($this->tail));
/*
* Since we have just echoed the result, we don't need it in the response. Do we?
*/
Zend_Controller_Front::getInstance ()->returnResponse(true);
$response->clearBody ();
/*
* Now to business.
* We execute that calculation intensive callback.
*/
if (! empty ($this->callback) && is_callable ($this->callback))
{
call_user_func ($this->callback);
}
/*
* We sure don't want to leave behind the tail.
* Output it so html looks consistent.
*/
echo $this->tail;
}
/**
* Returns layout object
*/
function getLayout ()
{
$layout_plugin = Zend_Controller_Front::getInstance ()->getPlugin ('Zend_Layout_Controller_Plugin_Layout');
return $layout = $layout_plugin->getLayout ();
}
}
class IndexController extends Zend_Controller_Action
{
/*
* This is a calculation intensive action
*/
public function indexAction ()
{
/*
* Zend_Layout in its current implementation accumulates whole action output inside itself.
* This fact hampers out intention to gradually output the result.
* What we do here is we defer execution of our intensive calculation in form of callback into the Postpone plugin.
* The scenario is:
* 1. Application started
* 2. Layout is started
* 3. Action gets executed (except callback) and its output is collected by layout.
* 4. Layout output goes to response.
* 5. Postpone::postDispatch outputs first part of the response (without the tail).
* 6. Postpone::postDispatch calls the callback. Its output goes stright to browser.
* 7. Postpone::postDispatch prints the tail.
*/
$this->getFrontController ()
->registerPlugin (new Postpone (function ()
{
/*
* A calculation immigration
* Put your actual calculations here.
*/
echo str_repeat(" ", 5000);
foreach (range (1, 500) as $x)
{
echo "<p>$x</p><br />\n";
usleep(61500);
flush();
}
}), 1000);
}
}