move validations in the controllers, make controllers return HttpResponses and use HttpRequests
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
cccc68e3ce
commit
a1910d1167
@ -1,78 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Api;
|
|
||||||
|
|
||||||
use App\Model\TacticModel;
|
|
||||||
|
|
||||||
use App\HttpCodes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API endpoint related to tactics
|
|
||||||
*/
|
|
||||||
class TacticEndpoint {
|
|
||||||
private TacticModel $model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TacticModel $model
|
|
||||||
*/
|
|
||||||
public function __construct(TacticModel $model) {
|
|
||||||
$this->model = $model;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function updateName(int $tactic_id): void {
|
|
||||||
$request_body = file_get_contents('php://input');
|
|
||||||
$data = json_decode($request_body);
|
|
||||||
|
|
||||||
if (!isset($data->name)) {
|
|
||||||
http_response_code(HttpCodes::BAD_REQUEST);
|
|
||||||
echo "missing 'name'";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fails = [];
|
|
||||||
|
|
||||||
$this->model->updateName($fails, $tactic_id, $data->name);
|
|
||||||
if (!empty($fails)) {
|
|
||||||
http_response_code(HttpCodes::PRECONDITION_FAILED);
|
|
||||||
echo json_encode($fails);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function newTactic(): void {
|
|
||||||
$request_body = file_get_contents('php://input');
|
|
||||||
$data = json_decode($request_body);
|
|
||||||
|
|
||||||
$initial_name = $data->name;
|
|
||||||
|
|
||||||
if (!isset($data->name)) {
|
|
||||||
http_response_code(HttpCodes::BAD_REQUEST);
|
|
||||||
echo "missing 'name'";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fails = [];
|
|
||||||
$tactic = $this->model->makeNew($fails, $initial_name);
|
|
||||||
|
|
||||||
if (!empty($fails)) {
|
|
||||||
http_response_code(HttpCodes::PRECONDITION_FAILED);
|
|
||||||
echo json_encode($fails);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$id = $tactic->getId();
|
|
||||||
echo "{id: $id}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTacticInfo(int $id): void {
|
|
||||||
$tactic_info = $this->model->get($id);
|
|
||||||
|
|
||||||
if ($tactic_info == null) {
|
|
||||||
http_response_code(HttpCodes::NOT_FOUND);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode($tactic_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Api;
|
||||||
|
|
||||||
|
use App\Controller\Control;
|
||||||
|
use App\Http\HttpRequest;
|
||||||
|
use App\Http\HttpResponse;
|
||||||
|
use App\Http\JsonHttpResponse;
|
||||||
|
use App\HttpCodes;
|
||||||
|
use App\Model\TacticModel;
|
||||||
|
use App\Validation\Validators;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API endpoint related to tactics
|
||||||
|
*/
|
||||||
|
class APITacticController {
|
||||||
|
private TacticModel $model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TacticModel $model
|
||||||
|
*/
|
||||||
|
public function __construct(TacticModel $model) {
|
||||||
|
$this->model = $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateName(int $tactic_id): HttpResponse {
|
||||||
|
return Control::runChecked([
|
||||||
|
"name" => [Validators::userString(32)]
|
||||||
|
], function (HttpRequest $request) use ($tactic_id) {
|
||||||
|
$this->model->updateName($tactic_id, $request["name"]);
|
||||||
|
return HttpResponse::fromCode(HttpCodes::OK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newTactic(): HttpResponse {
|
||||||
|
return Control::runChecked([
|
||||||
|
"name" => [Validators::userString(32)]
|
||||||
|
], function (HttpRequest $request) {
|
||||||
|
$tactic = $this->model->makeNew($request["name"]);
|
||||||
|
$id = $tactic->getId();
|
||||||
|
return new JsonHttpResponse(["id" => $id]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTacticInfo(int $id): HttpResponse {
|
||||||
|
$tactic_info = $this->model->get($id);
|
||||||
|
|
||||||
|
if ($tactic_info == null) {
|
||||||
|
return new JsonHttpResponse("could not find tactic #$id", HttpCodes::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonHttpResponse($tactic_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Http\HttpRequest;
|
||||||
|
use App\Http\HttpResponse;
|
||||||
|
use App\Http\JsonHttpResponse;
|
||||||
|
use App\HttpCodes;
|
||||||
|
|
||||||
|
class Control {
|
||||||
|
|
||||||
|
public static function runChecked(array $schema, callable $run): HttpResponse {
|
||||||
|
$request_body = file_get_contents('php://input');
|
||||||
|
$payload = get_object_vars(json_decode($request_body));
|
||||||
|
|
||||||
|
return self::runCheckedFrom($payload, $schema, $run);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function runCheckedFrom(array $data, array $schema, callable $run): HttpResponse {
|
||||||
|
$fails = [];
|
||||||
|
$request = HttpRequest::from($data, $fails, $schema);
|
||||||
|
|
||||||
|
if (!empty($fails)) {
|
||||||
|
return new JsonHttpResponse($fails, HttpCodes::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return call_user_func_array($run, [$request]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use App\Validation\FieldValidationFail;
|
||||||
|
use App\Validation\Validation;
|
||||||
|
use ArrayAccess;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class HttpRequest implements ArrayAccess {
|
||||||
|
private array $data;
|
||||||
|
|
||||||
|
private function __construct(array $data) {
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new HttpRequest instance, and ensures that the given request data validates the given schema.
|
||||||
|
* This is a simple function that only supports flat schemas (non-composed, the data must only be a k/v array pair.)
|
||||||
|
* @param array $request the request's data
|
||||||
|
* @param array $fails a reference to a failure array, that will contain the reported validation failures.
|
||||||
|
* @param array $schema the schema to satisfy. a schema is a simple array with a string key (which is the top-level field name), and a set of validators
|
||||||
|
* @return HttpRequest|null the built HttpRequest instance, or null if a field is missing, or if any of the schema validator failed
|
||||||
|
*/
|
||||||
|
public static function from(array $request, array &$fails, array $schema): ?HttpRequest {
|
||||||
|
$failure = false;
|
||||||
|
foreach ($schema as $fieldName => $fieldValidators) {
|
||||||
|
if (!isset($request[$fieldName])) {
|
||||||
|
$fails[] = FieldValidationFail::missing($fieldName);
|
||||||
|
$failure = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$failure |= Validation::validate($request[$fieldName], $fieldName, $fails, ...$fieldValidators);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($failure) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new HttpRequest($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromPayload(array &$fails, array $schema): ?HttpRequest {
|
||||||
|
$request_body = file_get_contents('php://input');
|
||||||
|
$data = json_decode($request_body);
|
||||||
|
return self::from($data, $fails, $schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetExists($offset): bool {
|
||||||
|
return isset($this->data[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetGet($offset) {
|
||||||
|
return $this->data[$offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetSet($offset, $value) {
|
||||||
|
throw new Exception("requests are immutable objects.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetUnset($offset) {
|
||||||
|
throw new Exception("requests are immutable objects.");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
class HttpResponse {
|
||||||
|
|
||||||
|
private int $code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $code
|
||||||
|
*/
|
||||||
|
public function __construct(int $code) {
|
||||||
|
$this->code = $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): int {
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromCode(int $code): HttpResponse {
|
||||||
|
return new HttpResponse($code);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use App\HttpCodes;
|
||||||
|
|
||||||
|
class JsonHttpResponse extends HttpResponse {
|
||||||
|
|
||||||
|
private $payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $payload
|
||||||
|
*/
|
||||||
|
public function __construct($payload, int $code = HttpCodes::OK) {
|
||||||
|
parent::__construct($code);
|
||||||
|
$this->payload = $payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getJson() {
|
||||||
|
return json_encode($this->payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use App\HttpCodes;
|
||||||
|
|
||||||
|
class ViewHttpResponse extends HttpResponse {
|
||||||
|
|
||||||
|
public const TWIG_VIEW = 0;
|
||||||
|
public const REACT_VIEW = 1;
|
||||||
|
|
||||||
|
private string $file;
|
||||||
|
private array $arguments;
|
||||||
|
private int $kind;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $code
|
||||||
|
* @param int $kind
|
||||||
|
* @param string $file
|
||||||
|
* @param array $arguments
|
||||||
|
*/
|
||||||
|
private function __construct(int $kind, string $file, array $arguments, int $code = HttpCodes::OK) {
|
||||||
|
parent::__construct($code);
|
||||||
|
$this->kind = $kind;
|
||||||
|
$this->file = $file;
|
||||||
|
$this->arguments = $arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getViewKind(): int {
|
||||||
|
return $this->kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFile(): string {
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getArguments(): array {
|
||||||
|
return $this->arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function twig(string $file, array $arguments, int $code = HttpCodes::OK): ViewHttpResponse {
|
||||||
|
return new ViewHttpResponse(self::TWIG_VIEW, $file, $arguments, $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function react(string $file, array $arguments, int $code = HttpCodes::OK): ViewHttpResponse {
|
||||||
|
return new ViewHttpResponse(self::REACT_VIEW, $file, $arguments, $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validation;
|
||||||
|
|
||||||
|
class ComposedValidator extends Validator {
|
||||||
|
|
||||||
|
private Validator $first;
|
||||||
|
private Validator $then;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Validator $first
|
||||||
|
* @param Validator $then
|
||||||
|
*/
|
||||||
|
public function __construct(Validator $first, Validator $then) {
|
||||||
|
$this->first = $first;
|
||||||
|
$this->then = $then;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validate(string $name, $val): array {
|
||||||
|
$firstFailures = $this->first->validate($name, $val);
|
||||||
|
$thenFailures = $this->then->validate($name, $val);
|
||||||
|
return array_merge($firstFailures, $thenFailures);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue