Skip to content

Commit

Permalink
Merge pull request #3 from atk4/feature/rest-support-callable
Browse files Browse the repository at this point in the history
Feature/rest support callable
  • Loading branch information
DarkSide666 authored Jun 5, 2018
2 parents 56dab00 + 3cab3a1 commit 227f7b1
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 43 deletions.
148 changes: 106 additions & 42 deletions src/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,60 +55,70 @@ public function __construct($request = null)
}

/**
* Do pattern matching.
* Do pattern matching and save extracted variables.
*
* @param string $pattern
* @param callable $callable
* @param string $pattern
*
* @return mixed
* @return bool
*/
public function match($pattern, $callable = null)
protected $_vars;

public function match($pattern)
{
$path = explode('/', rtrim($this->path, '/'));
$pattern = explode('/', rtrim($pattern, '/'));

$vars = [];
$this->_vars = [];

while ($path || $pattern) {
$p = array_shift($path);
$r = array_shift($pattern);

// if path ends and there is nothing in pattern (used //) then continue
if ($p === null && $r === '') {
continue;
}

// must make sure both match
// if both match, then continue
if ($p === $r) {
continue;
}

// pattern 'r' accepts anything
// pattern '*' accepts anything
if ($r == '*' && strlen($p)) {
continue;
}

// if pattern ends, but there is still something in path, then don't match
if ($r === null || $r === '') {
return false;
}

// parameters always start with ':', save in $vars and continue
if ($r[0] == ':' && strlen($p)) {
$vars[] = $p;
$this->_vars[] = $p;
continue;
}

// good until the end
// pattern '**' = good until the end
if ($r == '**') {
break;
}

return false;
}

// if no callable function set - just say that it matches
if ($callable === null) {
return true;
}
return true;
}

/**
* Call callable and emit response.
*
* @param callable $callable
* @param array $vars
*/
public function call($callable, $vars = [])
{
// try to call callable function
try {
$ret = call_user_func_array($callable, $vars);
Expand All @@ -122,19 +132,25 @@ public function match($pattern, $callable = null)
$ret = $ret->export();
}

// no response, just step out
if ($ret === null) {
return;
}

// create response object
if ($ret !== null) {
if (!$this->response) {
$this->response =
new \Zend\Diactoros\Response\JsonResponse(
$ret,
200,
[],
JSON_PRETTY_PRINT | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
);
}
if (!$this->response) {
$this->response =
new \Zend\Diactoros\Response\JsonResponse(
$ret,
200,
[],
JSON_PRETTY_PRINT | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
);
}

// emit response and exit
// if there is emitter, then emit response and exit
// for testing purposes there can be situations when emitter is disabled. then do nothing.
if ($this->emitter) {
$this->emitter->emit($this->response);
exit;
}
Expand All @@ -153,8 +169,8 @@ public function match($pattern, $callable = null)
*/
public function get($pattern, $callable = null)
{
if ($this->request->getMethod() === 'GET') {
return $this->match($pattern, $callable);
if ($this->request->getMethod() === 'GET' && $this->match($pattern)) {
return $this->call($callable, $this->_vars);
}
}

Expand All @@ -168,8 +184,8 @@ public function get($pattern, $callable = null)
*/
public function post($pattern, $callable = null)
{
if ($this->request->getMethod() === 'POST') {
return $this->match($pattern, $callable);
if ($this->request->getMethod() === 'POST' && $this->match($pattern)) {
return $this->call($callable, $this->_vars);
}
}

Expand All @@ -183,8 +199,8 @@ public function post($pattern, $callable = null)
*/
public function patch($pattern, $callable = null)
{
if ($this->request->getMethod() === 'PATCH') {
return $this->match($pattern, $callable);
if ($this->request->getMethod() === 'PATCH' && $this->match($pattern)) {
return $this->call($callable, $this->_vars);
}
}

Expand All @@ -198,41 +214,89 @@ public function patch($pattern, $callable = null)
*/
public function delete($pattern, $callable = null)
{
if ($this->request->getMethod() === 'DELETE') {
return $this->match($pattern, $callable);
if ($this->request->getMethod() === 'DELETE' && $this->match($pattern)) {
return $this->call($callable, $this->_vars);
}
}

/**
* Implement REST pattern matching.
*
* @param string $pattern
* @param \atk4\data\Model $model
* @param string $pattern
* @param \atk4\data\Model|callable $model
*
* @return mixed
*/
public function rest($pattern, $model = null)
{
// GET all records
$this->get($pattern, function () use ($model) {
$args = func_get_args();

if (is_callable($model)) {
$model = call_user_func_array($model, $args);
}

return $model;
});

$this->get($pattern.'/:id', function ($id) use ($model) {
// GET :id - one record
$this->get($pattern.'/:id', function () use ($model) {
$args = func_get_args();
$id = array_pop($args); // pop last element of args array, it's :id

if (is_callable($model)) {
$model = call_user_func_array($model, $args);
}

return $model->load($id)->get();
});

$this->patch($pattern.'/:id', function ($id) use ($model) {
return $model->load($id)->set($this->requestData)->save()->get();
// PATCH :id - update one record (same as POST :id)
$this->patch($pattern.'/:id', function () use ($model) {
$args = func_get_args();
$id = array_pop($args); // pop last element of args array, it's :id

if (is_callable($model)) {
$model = call_user_func_array($model, $args);
}

return $model->load($id)->save($this->requestData)->get();
});
$this->post($pattern.'/:id', function ($id) use ($model) {
return $model->load($id)->set($this->requestData)->save()->get();

// POST :id - update one record
$this->post($pattern.'/:id', function () use ($model) {
$args = func_get_args();
$id = array_pop($args); // pop last element of args array, it's :id

if (is_callable($model)) {
$model = call_user_func_array($model, $args);
}

return $model->load($id)->save($this->requestData)->get();
});
$this->delete($pattern.'/:id', function ($id) use ($model) {

// DELETE :id - delete one record
$this->delete($pattern.'/:id', function () use ($model) {
$args = func_get_args();
$id = array_pop($args); // pop last element of args array, it's :id

if (is_callable($model)) {
$model = call_user_func_array($model, $args);
}

return !$model->load($id)->delete()->loaded();
});

// POST - insert new record
$this->post($pattern, function () use ($model) {
return $model->set($this->requestData)->save()->get();
$args = func_get_args();

if (is_callable($model)) {
$model = call_user_func_array($model, $args);
}

return $model->unload()->save($this->requestData)->get();
});
}

Expand Down
13 changes: 12 additions & 1 deletion tests/ApiTester.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public function assertRequest($response, $method, $uri = '/', $data = null)
* passed into an Api class. Execute callback $apiBuild afterwards allowing
* you to define your custom handlers. Match response from the API against
* the $response and if it is different - create assertion error.
*
* @param string $response
* @param callable $apiBuild
* @param string $uri
* @param string $method
* @param array $data
*/
public function assertApi($response, $apiBuild, $uri = '/ping', $method = 'GET', $data = null)
{
Expand Down Expand Up @@ -60,7 +66,12 @@ public function assertApi($response, $apiBuild, $uri = '/ping', $method = 'GET',
}

/**
* Simmulate a request and validate a response.
* Simulate a request and validate a response.
*
* @param string $response
* @param callable $handler
* @param string $method
* @param array $data
*/
public function assertReq($response, $handler, $method = 'GET', $data = null)
{
Expand Down
3 changes: 3 additions & 0 deletions tests/ApiTesterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ function ($api) {
$this->assertReq('pong', function () {
return 'pong';
}, 'POST');
$this->assertReq('pong', function () {
return 'pong';
}, 'PATCH');
}
}

0 comments on commit 227f7b1

Please sign in to comment.