corneltek/actionkit

3.1.1 2016-07-13 12:44 UTC

README

Coverage Status Build Status Latest Stable Version Total Downloads Latest Unstable Version License

ActionKit 是一个库,允许您在控制器、页面、Ajax 请求之间共享业务逻辑。

有时,您需要在控制器、页面、Ajax 请求之间重用代码,您可能需要坐下来编写一个共享的控制器类来共享可重用的通用代码。这种方法对于小型应用可能效果不错,然而,当您的应用越来越大时,共享通用代码将变得非常复杂,维护也会变得困难。

ActionKit 提供了一种方法来封装您的通用代码,并在应用中的任何地方重用这些通用代码。

除了在控制器之间共享逻辑之外,您还可以定义带有类型、验证器、表单小部件类型以及许多参数选项的参数,并将您的操作渲染为网页表单。

[Web Form] => [Input: paramters] => [ Parameter Validation ] 
        => [Execute the logic in Action]  
            => [Return result: Success or Failure, Data: processed data]
                => [Render result on the web page]

因此,您不需要处理 Ajax 机制、控制器处理程序、参数验证,ActionKit 的 Action 会自动为您处理所有这些工作,这样您就可以专注于您需要处理的唯一核心逻辑。

操作就像 API(应用程序编程接口),可以从 HTTP 请求、Ajax 请求或后端触发,以下是工作流程

ActionKit - PHP

基本操作

最小操作骨架

use ActionKit\Action;
class YourAction extends Action
{
    public function run() {
    }
}

要使用操作,您应该至少定义一个 run 方法,在 run 方法中,您编写您的逻辑和操作,然后返回结果。

要报告成功结果,您可以使用 success 方法

function run() {
    return $this->success('Success!!');
}

您也可以通过在数组中添加另一个参数将数据传递给操作结果

function run() {
    return $this->success('Success', ['user_id' => 1]);
}

要报告错误

function run() {
    return $this->error('Error', ['user_id' => 1]);
}

操作签名

要从前端触发操作,您可以在您的 HTML 表单中定义一个操作签名。

提交此表单时,ActionRunner 将使用此签名将您的操作调度到正确的地方。

约定规则如下

  • 具有类似 App\Action\CreateUser 命名空间的类将转换为签名 App::Action::CreateUser

简单的操作骨架

class YourAction extends \ActionKit\Action
{

    function schema() {
        $this->param('id')
            ->renderAs('HiddenInput');

        $this->param('password')
            ->renderAs('PasswordInput');

        $this->filterOut('hack','hack2','role');
    }

    function beforeRun() { 
        // do something
    }

    function run()
    {
        return $this->success( 'Success Helper (point to action result object)' );
        return $this->error( 'Error Helper (point to action result object)' );
    }

    function afterRun() 
    {
        // do something
    }
}

然后调用者

$act = new Action( $_REQUEST );
$act->invoke();
$rs = $a->getResult();

要执行操作,只需调用 invoke 方法来触发操作。

invoke 方法按顺序触发 runPreinitrunInitbeforeRunrunafterRun

操作架构

run 方法

方法

您将在 run 中使用的方法

  • $this->arg(string $key):通过键从操作获取参数。

  • $this->setArgs(array $arguments) 设置操作参数。

  • $this->success(string $message, $data = array())

    报告成功消息。

    您还可以将数据传递给操作结果对象。

    通过使用此方法,操作对象创建一个操作结果对象,并使用操作签名作为键将其注册到操作结果池中。

    @see ActionKit\Runner

    • 在 Ajax 模式下,操作结果将被转换为 JSON 格式,并且前端的 Action.js 将获取操作结果数据,然后显示结果消息(或将消息分发到 jGrowl 插件)

    • 在 HTTP POST/GET 模式下,操作结果也将保存在操作结果池中,并且通过调用简单的 twig 宏,您可以将这些操作结果对象渲染为 HTML 格式的字符串。

      @see bundles/CoreBundle/Templates/phifty/action_result.html

  • $this->error(string $message)

    报告错误消息。

属性

您需要在操作 run 方法中使用的属性

  • 请求:HttpRequest对象,您可以通过简单的API获取POST、GET、SESSION、SERVER。

      $this->request->param('user')  
      // is equal to isset($_REQUEST['user']) ? $_REQUEST['user'] : null
    
      $this->request->server->HTTP_HOST
      // is equal to isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null
    

动作参数方法

动作结果

执行动作后,动作在其内部创建一个动作结果对象,您可以通过动作对象的getResult()方法检索动作结果对象,以查看它是否成功执行或遇到了错误。

每个动作结果对象都保存在ActionRunner实例中。(动作结果池,是一个单例对象)

您还可以从ActionRunner中获取动作结果对象。

ActionResult对象包含一个标志(成功或错误)、一个消息字符串和一个数据存储。

以下是一个简单的示例,用于检查结果错误

if( $result->success ) {

} else {
    // error here

}

从动作对象中获取动作结果。

$rs = $action->getResult();
if( $rs->success ) {
    $msg = $rs->getMessage();
    $data = $rs->getData();
}

从ActionRunner中获取动作结果。

$runner = ActionKit\Runner::getInstance();
if( $result = $runner->getResult( 'Login::...' ) ) {
    // check the action result

}

RecordAction

Record动作对于连接ORM和前端表单非常有用,Record动作将参数传递给模型对象并验证来自HTTP请求的参数。

如果所有验证都通过,并且没有捕获到PDOExcetion,将生成动作结果并准备好发送到前端。

有3种类型的record动作,它们映射到CRUD操作

  1. 创建
  2. 更新
  3. 删除

映射的动作类如下

  1. CreateRecordAction
  2. UpdateRecordAction
  3. DeleteRecordAction

这3个record动作类继承自BaseRecordAction类。

BaseRecordAction类提供了将ORM接口方法和结果数据转换的多数方法。

RecordAction概述

namespace User\Action\UpdateAction;
use ActionKit\RecordAction\UpdateRecordAction;

class UpdateAction extends UpdateRecordAction {

    function schema() 
    {
        // For record actions, we can convert the record columns
        $this->useRecordSchema();

        $this->param( 'username' )
            ->label( _('Username') )
            ->useSuggestion();

        $this->param( 'password' )
            ->validator(function($value) {
                if ($value) {
                    return [false, "reason"];
                }
                return [true, "success!!"];
            });

        $this->filterOut(array('auth_token'));
    }

    function validatePassword( $value , $args ) 
    {
        return $this->valid( $message );

        # or
        return $this->invalid( $message );
    }

    function suggestUsername( $value , $args ) {
        return;  # not to suggest
        return $this->suggest( "$value is used. use: " , array( ... ) );
    }

    function completeCountry( $value , $args ) {

        ...
    }
}

消息

BaseRecordAction提供了默认的消息接口,要重写这些消息(无论是成功消息还是错误消息),您可以简单地重写这些方法

function successMessage(OperationResult $ret) {
    return _('Your Success Message');
}

function errorMessage(OperationResult $ret) {
    return _('Your Error Message');
}

RecordAction示例

创建新闻

namespace News\Action;
use ActionKit\RecordAction\CreateRecordAction;

class CreateNews extends CreateRecordAction
{
    public $recordClass = 'News\Model\News';
}

更新新闻

namespace News\Action;
use ActionKit\RecordAction\UpdateRecordAction;

class UpdateNews extends UpdateRecordAction
{
    public $recordClass = 'News\Model\News';
}

RecordAction API

$record = new User\Model\User( 3 ); // primary key = 3
$a = new User\Action\UpdateUser(array( 'nickname' => 'New Name' ) , $record );
$a->invoke();   // which calls $record->update(array( 'nickname' => 'New Name' ) );

RecordAction架构方法

  • useRecordSchema

RecordAction生成器

CRUD动作可以自动生成,或者可以手动创建。

从模型类名生成CreateRecordAction

$g = new ActionKit\ActionGenerator;
$code = $g->generateClassCode( 'App\Model\User' , 'Create' )->code;

从模型类名生成UpdateRecordAction

$g = new ActionKit\ActionGenerator;
$code = $g->generateClassCode( 'App\Model\User' , 'Update' )->code;

生成自定义动作

$g = new ActionKit\ActionGenerator;

$g->register('template name','...template path...');

$g->generate('SortImage', 'template name', array(
    "base_class" => "SortRecordAction",
    "record_class" => "ProductBundle\\Model\\ProductImage",
    ... template variable...
));

甚至更短(???)

use ActionKit\RecordAction\BaseRecordAction;
$class = BaseRecordAction::createCRUDClass( 'App\Model\Post' , 'Create' );

或者从记录对象创建记录动作

$post = new Post;
$update = $post->asUpdateAction();
$create = $post->asCreateAction();
$delete = $post->asDeleteAction();

动作小部件

动作小部件依赖于参数定义,默认的小部件类型是TextInput。

$post = new Post;
$update = $post->asUpdateAction();
$html = $update->widget('title')->render();
$html = $update->widget('title')->render( array( 'class' => '....' ));

$html = $update->render('title',array( /* attributes.... */ ));
$html = $update->render( null, array( /* attributes */ )  );

在动作架构中,您定义的参数可以自动生成表单小部件(使用FormKit)。

您只需在动作架构中为您的参数定义一个renderAs属性。

例如

class YourAction extends Action {
    function schema() {
        $this->param('name')
            ->renderAs('TextInput');
    }
}

然后,通过动作对象获取表单小部件,您可以这样做

$action = new YourAction;
$widget = $a->widget('name');

然后渲染它

$html = $widget->render(array(  
    'class' => 'extra-class'
    'id' => 'field-id'
));

对于其他类型的小部件,例如SelectInput,您可以指定options

$a->widget('user_type')->render(array( 
    'options' => array(
        'Option 1' => '1'
        'Option 2' => '2'
        'Group Option' => array(
            'Suboption 1' => '2.1'
            'Suboption 2' => '2.2'
        )
    )
));

您还可以通过小部件方法强制小部件类型,这将覆盖您之前定义的小部件类型

$a->widget('confirmed','RadioInput')->render(array(
    'false', 'true'
));

动作视图

动作视图可能包含一个formkit布局构建器,但动作视图为您构建了一切。

要创建一个动作视图,您只需调用createView方法

$view = $action->createView('+AdminUI\Action\StackView');
$view->render(array( ... render options ... ));

通过内置StackView进行动作渲染

通过使用ActionKit StackView,您不需要编写HTML,表单元素将自动生成。

以下是StackView的概述

$action = new SomeWhatAction;
$view = new ActionKit\View\StackView($action, array( ... options ... ));
$view->render();

用例

$action = new User\Action\ChangePassword;
$view = new ActionKit\View\StackView( $action );
echo $view->render();

您可以通过动作的asView方法渲染动作视图

    echo $action->asView('ActionKit\View\StackView')->render();
    echo $action->asView()->render();  // implies view class ActionKit\View\StackView

因此,如果您在Twig模板中,您可以这样做

{{ action.asView('ActionKit\\View\\StackView').render()|raw}}

您还可以向视图类传递额外的选项

echo $action->asView('ActionKit\View\StackView', array( ... view options ... ))->render();

动作渲染(通过纯HTML元素渲染)

您可以直接渲染一个HTML表单来触发相应的动作类,在这个例子中,我们触发了通过动态动作生成器自动生成的User\Action\UpdateUser动作。

<form method="post">
    <!-- action signature -->
    <input type="hidden" name="action"
        value="User::Action::UpdateUser"/>   

    <!-- action fields -->
    <input type="text" name="account" value="c9s"/>

    <input type="submit"/>
</form>

动作渲染和Action.js集成

<script>
    $(function() {
        Action.form( $('#profile')).setup({
            validation: "msgbox",
            status: true
        });
    });
</script>

{{ Web.render_result( update.signature ) |raw}}

{{ update.asView('ActionKit\\View\\StackView',{ 
        'form_id': 'profile' 
    }).render() |raw }}
</div>

动作渲染(逐字段渲染)

在控制器中,您可以初始化一个动作对象

function updateAction() {
    $changePasswordAction = new User\Action\ChangePassword( array( 
        ... values to override field values ... ) , $record );

    return $this->render('some_path.html',array( 
        'changePasswordAction' => $changePasswordAction
    ));
}

然后在模板中,您可以调用动作API并通过这些方法渲染这些字段,例如renderSignatureWidgetrenderWidgetrenderLabelrenderSubmitWidget

<form method="post">
    # This renders a field named "action" with action signature "User::Action::ChangePassword" 
    {{ changePasswordAction.renderSignatureWidget |raw}}

    {% if CRUD.Record.id %}
        {{ forms.hidden('id', CRUD.Record.id) }}
    {% endif %}

    <h5>Change/Setup password</h5>

    <div class="v-field">
        <div class="label">{% trans 'Password' %}</div>
        <div class="input">
            {{ changePasswordAction.widget('password1').render() |raw }}
        </div>
    </div>

    <div class="v-field">
        <div class="label">{% trans 'Password' %}</div>
        <div class="input">
            {{ changePasswordAction.widget('password1').render() |raw }}
        </div>
    </div>

    <div class="v-field">
        <div class="label">{% trans 'Password Confirm' %}</div>
        <div class="input">
            {{ changePasswordAction.widget('password2').render() |raw }}
        </div>
    </div>

    <div class="button-group">
        {% if CRUD.Record.id %}
            {{ changePasswordAction.renderSubmitWidget({ class: 'create button', value: _('Save') }) |raw }}
        {% else %}
            {{ changePasswordAction.renderSubmitWidget({ class: 'create button', value: _('Create') }) |raw }}
        {% endif %}
        {{ changePasswordAction.renderButtonWidget({ class: 'button', value: _('Close'), onclick: 'Region.of(this).fadeRemove();' }) |raw}}
    </div>
</form>

前端动作API

您可以从前端执行操作,这更像是一个API。要发送要执行的操作,您需要包含操作资源中的 action.js。

action.js 提供了一个名为 runAction 的简短助手,它可以帮助您执行操作,您可以通过以下形式调用 runAction 函数:

runAction( {Action Signature}, {Arguments});
runAction( {Action Signature}, {Arguments} , {Options});
runAction( {Action Signature}, {Arguments} , {Options}, {Callback} );
runAction( {Action Signature}, {Arguments} , {Callback} );
runAction( {Action Signature}, {Callback} );
runAction( {Action Signature} );

在下面的示例中,我们发送 Stock::Action::DeleteTransaction 到后端,并附带一个记录 ID 来删除交易记录,如果成功,则从 HTML 中淡出移除元素。

<div class="txn">
    <div class="txn-status txn-status-{{ txn.status }}">{{ txn.display('status') }}</div>
    <div class="txn-delete">
        <input type="button" 
            onclick=" runAction('Stock::Action::DeleteTransaction', { 
                            id: {{ txn.id }} 
                        }, { 
                            confirm: '確定刪除嗎? ', 
                            remove: $(this).parents('.txn')
                        });" value="刪除"/>
    </div>
</div>