从命令行运行 Zend Framework 的操作

64

我想运行一个Zend框架的动作来生成一些文件,从命令行操作。这是否可能?如果是,我需要对我的现有Web项目进行多少更改才能使用ZF?

谢谢!

10个回答

68

更新

如果你愿意,你可以从https://github.com/akond/zf-cli获取适用于ZF 1.12的所有代码。

虽然解决方案#1是可行的,但有时您需要更加复杂的解决方案。 特别是当您希望拥有不止一个CLI脚本时。 如果您允许的话,我会提出另一种解决方案。

首先,在你的Bootstrap.php文件中实现以下内容:

protected function _initRouter ()
{
    if (PHP_SAPI == 'cli')
    {
        $this->bootstrap ('frontcontroller');
        $front = $this->getResource('frontcontroller');
        $front->setRouter (new Application_Router_Cli ());
        $front->setRequest (new Zend_Controller_Request_Simple ());
    }
}

这个方法将剥夺默认路由的调度控制权,以我们自己的路由Application_Router_Cli为代替。

顺带一提,如果你在_initRoutes中为你的web接口定义了自己的路由,那么在命令行模式下,你可能想要将它们中和掉。

protected function _initRoutes ()
{
    $router = Zend_Controller_Front::getInstance ()->getRouter ();
    if ($router instanceof Zend_Controller_Router_Rewrite)
    {
        // put your web-interface routes here, so they do not interfere
    }
}

假设你已经开启了Application前缀的自动加载,那么Application_Router_Cli类可能会像这样:

class Application_Router_Cli extends Zend_Controller_Router_Abstract
{
    public function route (Zend_Controller_Request_Abstract $dispatcher)
    {
        $getopt = new Zend_Console_Getopt (array ());
        $arguments = $getopt->getRemainingArgs ();
        if ($arguments)
        {
            $command = array_shift ($arguments);
            if (! preg_match ('~\W~', $command))
            {
                $dispatcher->setControllerName ($command);
                $dispatcher->setActionName ('cli');
                unset ($_SERVER ['argv'] [1]);

                return $dispatcher;
            }

            echo "Invalid command.\n", exit;

        }

        echo "No command given.\n", exit;
    }


    public function assemble ($userParams, $name = null, $reset = false, $encode = true)
    {
        echo "Not implemented\n", exit;
    }
}

现在,您只需执行以下命令即可运行应用程序

php index.php backup

在这种情况下,BackupController控制器中的cliAction方法将被调用。

class BackupController extends Zend_Controller_Action
{
    function cliAction ()
    {
        print "I'm here.\n";
    }
}

你甚至可以修改 Application_Router_Cli 类,以便每次不是使用 "cli" 操作,而是使用用户通过额外参数选择的操作。

最后一件事。为命令行界面定义自定义错误处理程序,这样你就不会在屏幕上看到任何 HTML 代码了。

在 Bootstrap.php 中。

protected function _initError ()
{
    $error = $frontcontroller->getPlugin ('Zend_Controller_Plugin_ErrorHandler');
    $error->setErrorHandlerController ('index');

    if (PHP_SAPI == 'cli')
    {
        $error->setErrorHandlerController ('error');
        $error->setErrorHandlerAction ('cli');
    }
}

在ErrorController.php中

function cliAction ()
{
    $this->_helper->viewRenderer->setNoRender (true);

    foreach ($this->_getParam ('error_handler') as $error)
    {
        if ($error instanceof Exception)
        {
            print $error->getMessage () . "\n";
        }
    }
}

1
这对我来说完美地运作了。我创建了一个单独的CLI引导程序,以便我可以仅加载必要的组件,而不需要任何通常的Web视图初始化。谢谢。 - Neil Aitken
我需要在/library中创建一个Application/router/Cli.php吗? - beingalex
是的,没错。别忘了通过autoloadernamespaces[] = Application在application.ini中为Application前缀打开自动加载。 - akond
通常我会在我的.htaccess文件中设置APPLICATION_ENV,但似乎被绕过了。使用这段代码,最好在哪里设置呢? - hogsolo

64

其实比您想象中的要简单得多。使用CLI脚本可以重复使用bootstrap/application组件和现有的配置,同时避免MVC堆栈和在HTTP请求中调用的不必要的权重。这是不使用wget的优势之一。

像公共的index.php一样启动您的脚本:

<?php

// Define path to application directory
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH',
              realpath(dirname(__FILE__) . '/../application'));

// Define application environment
defined('APPLICATION_ENV')
    || define('APPLICATION_ENV',
              (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV')
                                         : 'production'));

require_once 'Zend/Application.php';
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/config.php'
);

//only load resources we need for script, in this case db and mail
$application->getBootstrap()->bootstrap(array('db', 'mail'));

你可以像在MVC应用程序中一样使用ZF资源:

$db = $application->getBootstrap()->getResource('db');

$row = $db->fetchRow('SELECT * FROM something');

如果你想要在CLI脚本中添加可配置参数,请查看Zend_Console_Getopt

如果你发现有些常用代码同时在MVC应用程序中也被调用,那么可以将其封装成一个对象,并从MVC和命令行应用程序中调用该对象的方法。这是一般的良好实践。


感谢您的详细回答。您认为我应该如何在CLI应用程序中使用视图?例如,假设我想通过视图生成CSV或XML文件,我能否在这种情况下充分利用MCV模式? - Ariod
4
Zend_View是一个模板组件,可以单独使用。字面上,$view = new Zend_View(); $view->var = 'some data';,然后$view->render('/path/to/script.phtml');。您还可以像平常一样引导视图以设置路径/帮助程序/选项并从应用程序中检索它。如果您希望仅替换XML文档中的一些细节,则可以使用视图。对于生成高度动态的文档,我建议使用SimpleXML或XMLWriter而不使用视图。 - David Snabel-Caunt
谢谢 - 我现在已经发布了一篇博客文章,内容大致相同,网址为http://www.davidcaunt.co.uk/2010/02/25/easy-command-line-scripts-with-zend-application/。 - David Snabel-Caunt

8

我在我的CP中看到了这个标记。如果你正在使用ZF2,并且偶然发现了这篇文章,那么它已经变得非常容易。只需编辑您的module.config.php文件中的路由,如下所示:

/**
 * Router
 */

'router' => array(
    'routes' => array(

        // .. these are your normal web routes, look further down
    ),
),

/**
 * Console Routes
 */
'console' => array(
    'router' => array(
        'routes' => array(

            /* Sample Route */
            'do-cli' => array(
                'options' => array(
                    'route'    => 'do cli',
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'do-cli',
                    ),
                ),
            ),
        ),    
    ),
),

使用上述配置,您需要在应用程序模块下的IndexController.php中定义doCliAction。从命令行运行非常简单:

php index.php do cli

完成了!流程更加顺畅。


1
不错的发现,这里是文档链接:http://framework.zend.com/manual/2.0/en/modules/zend.console.routes.html - Eddie Jaoude

6

akond的解决方案不错,但是有些微妙之处可能会导致他的脚本在您的环境中无法工作。请考虑对他的答案进行以下调整:

Bootstrap.php

protected function _initRouter()
{
    if( PHP_SAPI == 'cli' )
    {
        $this->bootstrap( 'FrontController' );
        $front = $this->getResource( 'FrontController' );
        $front->setParam('disableOutputBuffering', true);
        $front->setRouter( new Application_Router_Cli() );
        $front->setRequest( new Zend_Controller_Request_Simple() );
    }
}

初始化错误可能会像上面所述一样抛出,错误处理程序可能尚未实例化,除非您已更改默认配置。

protected function _initError ()
{
    $this->bootstrap( 'FrontController' );
    $front = $this->getResource( 'FrontController' );
    $front->registerPlugin( new Zend_Controller_Plugin_ErrorHandler() );
    $error = $front->getPlugin ('Zend_Controller_Plugin_ErrorHandler');
    $error->setErrorHandlerController('index');

    if (PHP_SAPI == 'cli')
    {
        $error->setErrorHandlerController ('error');
        $error->setErrorHandlerAction ('cli');
    }
}

您可能还希望从命令行中提取多个参数,这里有一个基本示例:
class Application_Router_Cli extends Zend_Controller_Router_Abstract
{
    public function route (Zend_Controller_Request_Abstract $dispatcher)
    {
        $getopt     = new Zend_Console_Getopt (array ());
        $arguments  = $getopt->getRemainingArgs();

        if ($arguments)
        {
            $command = array_shift( $arguments );
            $action  = array_shift( $arguments );
            if(!preg_match ('~\W~', $command) )
            {
                $dispatcher->setControllerName( $command );
                $dispatcher->setActionName( $action );
                $dispatcher->setParams( $arguments );
                return $dispatcher;
            }

            echo "Invalid command.\n", exit;

        }

        echo "No command given.\n", exit;
    }


    public function assemble ($userParams, $name = null, $reset = false, $encode = true)
    {
        echo "Not implemented\n", exit;
    }
}

最后,在您的控制器中,您调用的操作使用了由CLI路由器删除的控制器和操作留下的参数:
public function echoAction()
{
    // disable rendering as required
    $database_name     = $this->getRequest()->getParam(0);        
    $udata             = array();

    if( ($udata = $this->getRequest()->getParam( 1 )) )
        $udata         = explode( ",", $udata );

    echo $database_name;
    var_dump( $udata );
}

您可以使用以下方式调用CLI命令:
php index.php Controller Action ....

例如,如上所示:
php index.php Controller echo database123 this,becomes,an,array

你需要实现更加强大的过滤/转义,但这是一个快速搭建的基础模块。希望这可以帮助你!


你能解释一下为什么要禁用输出缓冲吗?$front->setParam('disableOutputBuffering', true); - james

0

akond的想法很好,但错误异常没有被错误控制器呈现。

public function cliAction() {
  $this->_helper->layout->disableLayout();
  $this->_helper->viewRenderer->setNoRender(true);

  foreach ($this->_getParam('error_handler') as $error) {
    if ($error instanceof Exception) {
      print "cli-error: " . $error->getMessage() . "\n";
    }
  }
}

在 Application_Router_Cli 中,将 echo 和 die 语句注释掉。
public function assemble($userParams, $name = null, $reset = false, $encode = true) {
//echo "Not implemented\n";
}

0

一种选择是通过在调用所需操作的URL上执行wget来模拟它。


0

你不能使用wget的-O选项来保存输出。但是wget显然不是解决方案。最好使用CLI。


-1

您可以像平常一样从命令行中使用PHP。如果您从PHP调用脚本并在脚本中设置操作,那么您可以运行任何您想要的内容。

这其实非常简单。虽然这不是预期的使用方式,但如果您想要这样做,它就可以工作。

例如:

 php script.php 

请阅读这里:http://php.net/manual/zh/features.commandline.php


-1

如果您的操作系统是Linux,可以使用wget命令。例如:

wget http://example.com/controller/action

请参考http://linux.about.com/od/commands/l/blcmdl1_wget.htm

更新:

您可以编写一个简单的bash脚本,如下所示:

if wget http://example.com/controller/action
    echo "Hello World!" > /home/wasdownloaded.txt
else
    "crap, wget timed out, let's remove the file."
    rm /home/wasdownloaded.txt
fi

然后你可以在PHP中这样做:

if (true === file_exists('/home/wasdownloaded.txt') {
    // to check that the 
}

希望这能有所帮助。

wget是一种可能性,但是你的脚本容易受到超时的影响。 - David Snabel-Caunt
@David 你可以编写一个带有if else的bash脚本,将wget的返回值保存到文件中,并使用php检查该文件是否存在。但是我认为这似乎有点过于复杂了。尽管如此,我实际上会更新我的答案。也许它会有所帮助。 - Richard Knop

-2

我使用了wget命令

wget http://example.com/module/controller/action -O /dev/null

-O /dev/null 如果你不想保存输出


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接