[cakephp: API Component & Controller] Cake modules for ajax request and Server-Sent Event. #cakephp
<?php
namespace App\Controller\Component;
use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
/**
* Api Component.
* @access protected
* @category Ajax API
* @package Cake\Controller\Component
* @author hyano@ampware.jp
*/
class ApiComponent extends Component
{
/**
* Properties
* Set in initialize method.
*/
private $Controller;
private $code = 200;
public $message = 'API is working.';
public $debug = 'Printing on debug mode.';
/**
* Initialize method
* @param array $config
* @return void
*/
public function initialize(array $config) {
parent::initialize($config);
$this->Controller = $this->_registry->getController();
}
/**
* Startup method
* - Set controller properties (view / response).
* - Detection bad request without authority.
* @param void
* @return Cake\Http\Response (json) (on failure)
*/
public function startup() {
$this->Controller->viewBuilder()->autoLayout(false);
$this->Controller->autoRender = false;
$this->response = $this->response->withCharset(ENCODING);
if ($this->request->action == 'fetch') {
// Server-Sent Events API.
header("Content-Type: text/event-stream\n");
header("Cache-Control: no-cache\n");
} else {
// Ajax API.
$this->response = $this->response->withType('application/json');
if (!$this->request->is('ajax') || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest') {
return $this->printError(400, 'Failed to API request by Ajax. Please verify your request header.');
}
}
}
/**
* Print JSON Response
* Set & Print response as JSON
* @param array|null $results
* @param string|null $message
* @param int|null $code
* @return Cake\Http\Response (json)
*/
public function printJsonResponse(
array $results = null,
string $message = null,
int $code = null
) { /* Print response hash as JSON */
$status = !empty($results);
$code = $code ?? $this->code;
$message = $message ?? $this->message;
$results = $results ?? [];
$response = [
'status' => $status,
'code' => $code,
'message' => $message,
'results' => $results,
];
if (DEBUG_MODE) $response += ['debug' => $this->debug];
$this->response = $this->response->withStatus($code);
$this->response = $this->response->withStringBody(json_encode(compact('response')));
return $this->response;
}
/**
* Print error
* Print response as JSON for illegal access.
* @param int $code
* @param string $message
*/
public function printError(int $code=500, string $message=null) {
return $this->printJsonResponse(null, $message, $code);
}
/**
* Print event method for Server-Sent Events.
* @param mixed $data
* @param ?string $event
* @param ?int $sleep
* @param ?int $retry
* @see https://gist.github.com/a08b20aea47f509d8947e6bb63dcb9e3
*/
public function printFetchEvent($data, string $event='message', int $sleep=1, int $retry=0) {
$print = 'event: '.$event.LF;
if (is_array($data) || is_object($data)) {
$print .= 'data: '.json_encode($data).LF;
} else {
$print .= 'data: '.$data.LF;
}
if ($retry) {
$print .= 'retry: '.($retry * 1000).LF; // Milliseconds.
}
$print .= LF;
echo $print;
ob_flush();
flush();
session_write_close();
sleep($sleep);
}
/**
* Flush method for Server-Sent Events.
* @param int $sleep
* @return void
*/
public function printFetchFlush(int $sleep=1) {
ob_flush();
flush();
session_write_close();
sleep($sleep);
}
}
<?php
// ApiController
namespace App\Controller\Api;
use App\Controller\AppController;
use Cake\Datasource\ConnectionManager;
class ApiController extends AppController
{
/**
* Properties.
*/
public $connection = null; // Cake\Datasource\ConnectionInterface;
/**
* Initialize method
* Add loading components.
*/
public function initialize() {
parent::initialize();
$this->loadComponent('Api');
$this->connection = ConnectionManager::get('default');
}
/**
* Response status
* @param void
* @return Cake\Http\Response (json)
*/
public function status() {
return $this->Api->printJsonResponse([
'debug_mode' => DEBUG_MODE,
'request' => $this->request->url,
'authority' => $this->Auth->user(),
]);
}
}
// HogeController (extends ApisController)
namespace App\Controller\Api;
use App\Controller\Api\ApiController;
class QueuesController extends ApiController
{
/**
* Pull for GET access.
*/
public function pull() {
if (!$this->Auth->user()) $this->Api->printError(403);
if ($this->request->is('get')) {
$queues = $this->Queues->find('list');
$this->Api->message = $queues->count().' queues found.'
return $this->Api->printJsonResponse($queues->toArray());
}
}
/**
* Fetch for Server Sent Event.
*/
public function fetch() {
$started = new \DateTimeImmutable();
while (strtotime('now') <= $started->modify('+'.LOOP_TIME_LIMIT.' seconds')->format('U')) {
$queues = $this->Queues->find()->where(['is_fetched' => false]);
if (!$queues->count()) {
$this->Api->printFetchEvent('Polling is alive.', 'ping', WAIT);
continue;
}
try {
$this->connection->begin();
// Flagging to queues as fetched, then send event.
foreach ($queues as $queue) {
$tmpQueue = $this->Queues->patchEntity($queue, ['is_fetched' => true]);
if (!$this->Queues->save($tmpQueue)) throw new \Exception('Failed to save fetched records.');
}
$this->connection->commit();
$this->Api->printFetchEvent($queues, 'push', WAIT);
} catch (\Exception $e) {
$this->connection->rollback();
$this->Api->printFetchEvent($e->getMessage(), 'error', WAIT);
}
}
exit;
}
}