ali1 / cakephp-json-tools
CakePHP 插件,用于从控制器配置 JSON 响应,无需模板
4.1.0
2020-02-15 22:15 UTC
Requires
- php: >=7.2
- cakephp/cakephp: ~4.0
- mockery/mockery: 0.9.*
Requires (Dev)
README
CakePHP 插件,用于从控制器创建 JSON 响应。
Json Tools 已创建,用于传统的基于浏览器的 CakePHP 项目,这些项目包含一些 AJAX 或 API 方法。Json Tools 组件使得创建这些变得轻而易举。
特性
- 一个组件,让您快速设置 AJAX 方法。
- 与 CakePHP 的 ResponseHandler 一起工作,您无需做其他操作
- 可以在方法中使用,该方法有时输出 Html,有时输出 Json,这取决于请求头,就像正常的 CakePHP 行为一样
要求
- Composer
- CakePHP 4.0+(查看版本以了解 3.7+ 版本的兼容性)
- PHP 7.2+
安装
在 CakePHP 根目录下:运行以下命令
composer require ali1/cakephp-json-tools
然后,在项目根目录的 Application.php 中,添加以下片段
// In project_root/Application.php: $this->addPlugin('JsonTools');
或者,您可以使用以下 shell 命令在 bootstrap.php 中自动启用插件
bin/cake plugin load JsonTools
现在将其添加到 src/AppController.php 或特定控制器中
class AppController extends Controller
{
public function initialize(): void
{
parent::initialize();
$this->loadComponent('JsonTools.Json');
}
}
用法(在可能需要输出 Json 的控制器方法中)
理解样板 JSON 输出
此组件初始化 ResponseHandler 以输出如下内容
[ 'error' => false, 'field_errors' => [], 'message' => '', '_redirect' => false, 'content' => null, ];
这对应于如下 JSON 输出
{"error": false, "field_errors": {}, "message": "OK", "_redirect": false, "content": false }
您的控制器方法可以使用此组件轻松覆盖这些键或添加新键。
初始化方法以使用样板 JSON 输出
// All Json actions where you want to use this component should have one of the following lines /** * The most basic priming. Will set the boiler-plate variables (see below) that can be processed by ResponseHandler * should there be a json request. If the request is not XHR/JSON, then this method would not have an effect. */ $this->Json->prepareVars(); /** * Will return true if is Json and is POST/PUT, otherwise false * Can replace something like $this->getRequest()->is(['post', 'put']) that is often used to check if form is submitted. * You don't need to run prepareVars() if you use this line */ if($this->Json->isJsonSubmit()){} /** * Will force the output to be Json regardless of HTTP request headers * You don't need to run prepareVars() if you use this line */ $this->Json->forceJson(); // will force the output to be Json regardless of HTTP request headers /** * Throw exception if request is not Json or not POST/PUT * You don't need to run prepareVars() if you use this line */ $this->Json->requireJsonSubmit(); // throw exception if request is not Json or not POST/PUT
设置 JSON 输出
样板输出键(见上方)可以在方法中使用以下方法之一在稍后重写
$this->Json->set('data', $data); // add a new key called data $this->Json->set('field_errors', $errors); // replace a key $this->Json->setMessage('Great'); // shortcut to replace message $this->Json->setError('Bad input'); // sets error to true and message to 'Bad Input' (can be configured to set error to string 'Bad Input' rather than bool true $this->Json->redirect(['action' => 'index']); // sets _redirect key to a URL (for javascript client to handle the redirect) $this->Json->entityErrorVars($user); // change the Json output to error: true, and message: a list of validation errors as a string (e.g. Username: Too long, Email: Incorrect email address)
示例控制器
// UsersController.php public function ajaxUpdateUser() { /* Json->requireJsonSubmit() will throw exception if not Json and a Post/Put request and also It will also prepare boiler plate variables that can be handled by RequestHandler 'error' => false, 'field_errors' => [], 'message' => '', '_redirect' => false, 'content' => null, In other words, the action output will be {"error": false, "field_errors": {}, "message": "OK", "_redirect": false, "content": false } All of these variables can be overridden in the action if errors do develop or example */ $this->Json->requireJsonSubmit(); if(!$user = $this->Users->save($this->getRequest()->getData()) { // Json->entityErrorVars($entity) will change the Json output to error: true, and message: a list of validation errors as a string (e.g. Username: Too long, Email: Incorrect email address) $this->Json->entityErrorVars($user); } else { // will make the Json output _redirect key into a URL. If you use this, your javascript needs to recognise this (see example javascript) $this->Flash->success("Saved"); $this->Json->redirect(['action' => 'view', $user->id]); } } public ajaxGetUser($user_id) { $user = $this->Users->get($user_id); $this->Json->forceJson(); // output will be Json. As of this line, the Json output will be the boilerplate output (error: false, message: OK etc.) $this->Json->set('data', $user); // the output will now have a data field containing the user object } public userCard($user_id) { $user = $this->Users->get($user_id); $this->Json->forceJson(); // output will be Json. As of this line, the Json output will be the boilerplate output (error: false, message: OK etc.) $this->set(compact('user')); // for use by the template. don't use $this->Json->set so that the user object does not get send in the output $this->Json->sendContent('element/Users/card'); // the Json output will have a 'content' key containing Html generated by the template } public otherExamples() { // Configuration $this->Json->setErrorMessageInErrorKey(true); // (default false) // true: if $this->Json->setError('error message') is called, the error key and the message key will contain the error message // false: if $this->Json->setError('error message') is called, the error message will be in the message key and the error key will be true and $this->Json->setHttpErrorStatusOnError(true); // (default false) // by default, the HTTP response is always 200 even in error situations $this->Json->setMessage('Great, all saved'); // shortcut to set the message key $this->Json->set('count', 5); // set any other json output keys you want to output }
示例 AJAX 表单
这是一个与上面的 ajaxUpdateUser 方法相对应的示例表单。
templates/Users/edit.php
<?php
$this->Html->script('ajax_form', ['block' => true]);
// optionally, also install BlockUI and include it, for a loading indicator while ajax is running (http://malsup.com/jquery/block/)
?>
<?= $this->Form->create($user, ['url' => ['controller' => 'Users', 'action' => 'ajaxUpdateUser'], 'onsubmit' => 'return dynamic_submit(this);']) ?>
<fieldset>
<legend><?= __('Update User') ?></legend>
<?php
echo $this->Form->control('username');
echo $this->Form->control('name');
echo $this->Form->control('email');
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
webroot/ajax_submit.js
/* Flexible Ajax Form Submission Function Usage: <form ... onsubmit="return ajax_submit(this);"> or <form ... onsubmit="return ajax_submit(this, {config});"> Will expect json response from server * If error: true, will alert error message and no further callbacks will occur * If success (error: false), what happens next depends on the success config * * (note if server return _redirect key in JSON, then this will take precedence and the page will be redirected) * * DEFAULT { success: true } By default, the page will just reload on success * * { success: false } If false is given, do nothing on success * * { success: function(data, form){} } If a function is given, the data will be passed to that callback function along with the form element. this callback function will be responsible for taking further action * * { success: $('.results') } If an object(element) is given, then HTML from the JSON content key will be loaded into the given element * * { success: '/url/to/success' } If a string is given, will redirect to this URL on success Other config: blockElement - if element given, only that element is blocked while loading, rather than the whole page */ window.ajax_submit = function(form, config){ config = config || {}; // config is optional config.success || (config.success = true); let mode; if (config.success === true) { // refresh mode mode = 'refresh'; } else if (typeof config.success == 'string') { // redirect mode mode = 'redirect'; } else if (typeof config.success === 'function') { // callback mode mode = 'callback'; } else if (typeof config.success === 'object') { // load HTML mode mode = 'html'; } else { // do nothing mode mode = ''; } config.blockElement || (config.blockElement = true); // true = whole page, false = none, element = block only element const ajaxOpts = { url: $(form).attr('action'), data: $(form).serialize(), context: form, method: 'post', headers: {}, dataType: 'json' }; if($(form).attr('method') && $(form).attr('method') === 'get') { ajaxOpts.method = 'get'; } if(typeof $.blockUI !== 'undefined') { if(config.blockElement === true){ $.blockUI({baseZ: 2000}); // modals are 1005 } else if (config.blockElement) { $(config.blockElement).block(); } } try{ $.ajax(ajaxOpts) .done(function(data, textStatus, jqXHR){ if(data.error) { $('.blockUI.blockOverlay').parent().unblock(); // take care of any blocked UI alert(data.message); } else { if (data._redirect) { window.location = data._redirect; } else if (mode === 'refresh') { location.reload(); } else if (mode === 'redirect') { window.location = config.success; } else if (mode === 'callback') { $('.blockUI.blockOverlay').parent().unblock(); // take care of any blocked UI config.success(data, this); // pass form back } else if (mode === 'html') { // load HTML mode $('.blockUI.blockOverlay').parent().unblock(); // take care of any blocked UI $(config.success).html(data); } else { // do nothing mode $('.blockUI.blockOverlay').parent().unblock(); // take care of any blocked UI } } }) .fail(function(jqXHR, textStatus, errorThrown) { $('.blockUI.blockOverlay').parent().unblock(); // take care of any blocked UI console.log(jqXHR); if (typeof jqXHR.responseJSON !== 'object' || typeof jqXHR.responseJSON.message !== 'string') { alert(errorThrown); } else { alert(errorThrown + ': ' + jqXHR.responseJSON.message); } }).always(function(data){ console.log(data); }); return false; }catch(err){ alert('An error occurred'); console.log(err); return false; } };