added static php-crud-api
This commit is contained in:
@@ -30,9 +30,7 @@ use Tqdev\PhpCrudApi\Middleware\PageLimitsMiddleware;
|
||||
use Tqdev\PhpCrudApi\Middleware\ReconnectMiddleware;
|
||||
use Tqdev\PhpCrudApi\Middleware\Router\SimpleRouter;
|
||||
use Tqdev\PhpCrudApi\Middleware\SanitationMiddleware;
|
||||
use Tqdev\PhpCrudApi\Middleware\SslRedirectMiddleware;
|
||||
use Tqdev\PhpCrudApi\Middleware\ValidationMiddleware;
|
||||
use Tqdev\PhpCrudApi\Middleware\XmlMiddleware;
|
||||
use Tqdev\PhpCrudApi\Middleware\XsrfMiddleware;
|
||||
use Tqdev\PhpCrudApi\OpenApi\OpenApiService;
|
||||
use Tqdev\PhpCrudApi\Record\ErrorCode;
|
||||
@@ -63,11 +61,8 @@ class Api implements RequestHandlerInterface
|
||||
$router = new SimpleRouter($config->getBasePath(), $responder, $cache, $config->getCacheTime(), $config->getDebug());
|
||||
foreach ($config->getMiddlewares() as $middleware => $properties) {
|
||||
switch ($middleware) {
|
||||
case 'sslRedirect':
|
||||
new SslRedirectMiddleware($router, $responder, $properties);
|
||||
break;
|
||||
case 'cors':
|
||||
new CorsMiddleware($router, $responder, $properties, $config->getDebug());
|
||||
new CorsMiddleware($router, $responder, $properties);
|
||||
break;
|
||||
case 'firewall':
|
||||
new FirewallMiddleware($router, $responder, $properties);
|
||||
@@ -111,9 +106,6 @@ class Api implements RequestHandlerInterface
|
||||
case 'customization':
|
||||
new CustomizationMiddleware($router, $responder, $properties, $reflection);
|
||||
break;
|
||||
case 'xml':
|
||||
new XmlMiddleware($router, $responder, $properties, $reflection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach ($config->getControllers() as $controller) {
|
||||
@@ -210,6 +202,15 @@ class Api implements RequestHandlerInterface
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return $this->router->route($this->addParsedBody($request));
|
||||
$response = null;
|
||||
try {
|
||||
$response = $this->router->route($this->addParsedBody($request));
|
||||
} catch (\Throwable $e) {
|
||||
$response = $this->responder->error(ErrorCode::ERROR_NOT_FOUND, $e->getMessage());
|
||||
if ($this->debug) {
|
||||
$response = ResponseUtils::addExceptionHeaders($response, $e);
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,9 +86,6 @@ class TempFileCache implements Cache
|
||||
if ($data === false) {
|
||||
return '';
|
||||
}
|
||||
if (strpos($data, '|') === false) {
|
||||
return '';
|
||||
}
|
||||
list($ttl, $string) = explode('|', $data, 2);
|
||||
if ($ttl > 0 && time() - filemtime($filename) > $ttl) {
|
||||
return '';
|
||||
@@ -121,18 +118,18 @@ class TempFileCache implements Cache
|
||||
if (strlen($entry) != $len) {
|
||||
continue;
|
||||
}
|
||||
if (file_exists($filename) && is_file($filename)) {
|
||||
if (is_file($filename)) {
|
||||
if ($all || $this->getString($filename) == null) {
|
||||
@unlink($filename);
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (strlen($entry) != $segments[0]) {
|
||||
continue;
|
||||
}
|
||||
if (file_exists($filename) && is_dir($filename)) {
|
||||
if (is_dir($filename)) {
|
||||
$this->clean($filename, array_slice($segments, 1), $len - $segments[0], $all);
|
||||
@rmdir($filename);
|
||||
rmdir($filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,8 +68,7 @@ class DefinitionService
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (
|
||||
$newColumn->getType() != $column->getType() ||
|
||||
if ($newColumn->getType() != $column->getType() ||
|
||||
$newColumn->getLength() != $column->getLength() ||
|
||||
$newColumn->getPrecision() != $column->getPrecision() ||
|
||||
$newColumn->getScale() != $column->getScale()
|
||||
|
||||
@@ -49,43 +49,23 @@ class ReflectedTable implements \JsonSerializable
|
||||
$columns[$column->getName()] = $column;
|
||||
}
|
||||
// set primary key
|
||||
$columnName = false;
|
||||
if ($type == 'view') {
|
||||
$columnName = 'id';
|
||||
} else {
|
||||
$columnNames = $reflection->getTablePrimaryKeys($name);
|
||||
if (count($columnNames) == 1) {
|
||||
$columnName = $columnNames[0];
|
||||
$columnNames = $reflection->getTablePrimaryKeys($name);
|
||||
if (count($columnNames) == 1) {
|
||||
$columnName = $columnNames[0];
|
||||
if (isset($columns[$columnName])) {
|
||||
$pk = $columns[$columnName];
|
||||
$pk->setPk(true);
|
||||
}
|
||||
}
|
||||
if ($columnName && isset($columns[$columnName])) {
|
||||
$pk = $columns[$columnName];
|
||||
$pk->setPk(true);
|
||||
}
|
||||
// set foreign keys
|
||||
if ($type == 'view') {
|
||||
$tables = $reflection->getTables();
|
||||
foreach ($columns as $columnName => $column) {
|
||||
if (substr($columnName, -3) == '_id') {
|
||||
foreach ($tables as $table) {
|
||||
$tableName = $table['TABLE_NAME'];
|
||||
$suffix = $tableName . '_id';
|
||||
if (substr($columnName, -1 * strlen($suffix)) == $suffix) {
|
||||
$column->setFk($tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$fks = $reflection->getTableForeignKeys($name);
|
||||
foreach ($fks as $columnName => $table) {
|
||||
$columns[$columnName]->setFk($table);
|
||||
}
|
||||
$fks = $reflection->getTableForeignKeys($name);
|
||||
foreach ($fks as $columnName => $table) {
|
||||
$columns[$columnName]->setFk($table);
|
||||
}
|
||||
return new ReflectedTable($name, $type, array_values($columns));
|
||||
}
|
||||
|
||||
public static function fromJson( /* object */$json): ReflectedTable
|
||||
public static function fromJson(/* object */$json): ReflectedTable
|
||||
{
|
||||
$name = $json->name;
|
||||
$type = isset($json->type) ? $json->type : 'table';
|
||||
@@ -137,7 +117,7 @@ class ReflectedTable implements \JsonSerializable
|
||||
{
|
||||
$columns = array();
|
||||
foreach ($this->fks as $columnName => $referencedTableName) {
|
||||
if ($tableName == $referencedTableName && !is_null($this->columns[$columnName])) {
|
||||
if ($tableName == $referencedTableName) {
|
||||
$columns[] = $this->columns[$columnName];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,11 @@ class ReflectionService
|
||||
return $this->database()->getTableNames();
|
||||
}
|
||||
|
||||
public function getDatabaseName(): string
|
||||
{
|
||||
return $this->database()->getName();
|
||||
}
|
||||
|
||||
public function removeTable(string $tableName): bool
|
||||
{
|
||||
unset($this->tables[$tableName]);
|
||||
|
||||
@@ -12,7 +12,7 @@ class Config
|
||||
'password' => null,
|
||||
'database' => null,
|
||||
'tables' => '',
|
||||
'middlewares' => 'cors,errors',
|
||||
'middlewares' => 'cors',
|
||||
'controllers' => 'records,geojson,openapi',
|
||||
'customControllers' => '',
|
||||
'customOpenApiBuilders' => '',
|
||||
@@ -41,8 +41,6 @@ class Config
|
||||
return 5432;
|
||||
case 'sqlsrv':
|
||||
return 1433;
|
||||
case 'sqlite':
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +53,6 @@ class Config
|
||||
return 'localhost';
|
||||
case 'sqlsrv':
|
||||
return 'localhost';
|
||||
case 'sqlite':
|
||||
return 'data.db';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,16 +65,6 @@ class Config
|
||||
];
|
||||
}
|
||||
|
||||
private function applyEnvironmentVariables(array $values): array
|
||||
{
|
||||
$newValues = array();
|
||||
foreach ($values as $key => $value) {
|
||||
$environmentKey = 'PHP_CRUD_API_' . strtoupper(preg_replace('/(?<!^)[A-Z]/', '_$0', str_replace('.', '_', $key)));
|
||||
$newValues[$key] = getenv($environmentKey, true) ?: $value;
|
||||
}
|
||||
return $newValues;
|
||||
}
|
||||
|
||||
public function __construct(array $values)
|
||||
{
|
||||
$driver = $this->getDefaultDriver($values);
|
||||
@@ -90,7 +76,6 @@ class Config
|
||||
$key = array_keys($diff)[0];
|
||||
throw new \Exception("Config has invalid value '$key'");
|
||||
}
|
||||
$newValues = $this->applyEnvironmentVariables($newValues);
|
||||
$this->values = $newValues;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ class RecordController
|
||||
if (!$this->service->hasTable($table)) {
|
||||
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
|
||||
}
|
||||
if ($this->service->getType($table) != 'table') {
|
||||
return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
|
||||
}
|
||||
$id = RequestUtils::getPathSegment($request, 3);
|
||||
$params = RequestUtils::getParams($request);
|
||||
if (strpos($id, ',') !== false) {
|
||||
|
||||
@@ -22,14 +22,9 @@ class ColumnsBuilder
|
||||
return '';
|
||||
}
|
||||
switch ($this->driver) {
|
||||
case 'mysql':
|
||||
return " LIMIT $offset, $limit";
|
||||
case 'pgsql':
|
||||
return " LIMIT $limit OFFSET $offset";
|
||||
case 'sqlsrv':
|
||||
return " OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY";
|
||||
case 'sqlite':
|
||||
return " LIMIT $limit OFFSET $offset";
|
||||
case 'mysql':return " LIMIT $offset, $limit";
|
||||
case 'pgsql':return " LIMIT $limit OFFSET $offset";
|
||||
case 'sqlsrv':return " OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,14 +74,9 @@ class ColumnsBuilder
|
||||
$valuesSql = '(' . implode(',', $values) . ')';
|
||||
$outputColumn = $this->quoteColumnName($table->getPk());
|
||||
switch ($this->driver) {
|
||||
case 'mysql':
|
||||
return "$columnsSql VALUES $valuesSql";
|
||||
case 'pgsql':
|
||||
return "$columnsSql VALUES $valuesSql RETURNING $outputColumn";
|
||||
case 'sqlsrv':
|
||||
return "$columnsSql OUTPUT INSERTED.$outputColumn VALUES $valuesSql";
|
||||
case 'sqlite':
|
||||
return "$columnsSql VALUES $valuesSql";
|
||||
case 'mysql':return "$columnsSql VALUES $valuesSql";
|
||||
case 'pgsql':return "$columnsSql VALUES $valuesSql RETURNING $outputColumn";
|
||||
case 'sqlsrv':return "$columnsSql OUTPUT INSERTED.$outputColumn VALUES $valuesSql";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,28 +146,17 @@ class ConditionsBuilder
|
||||
private function getSpatialFunctionName(string $operator): string
|
||||
{
|
||||
switch ($operator) {
|
||||
case 'co':
|
||||
return 'ST_Contains';
|
||||
case 'cr':
|
||||
return 'ST_Crosses';
|
||||
case 'di':
|
||||
return 'ST_Disjoint';
|
||||
case 'eq':
|
||||
return 'ST_Equals';
|
||||
case 'in':
|
||||
return 'ST_Intersects';
|
||||
case 'ov':
|
||||
return 'ST_Overlaps';
|
||||
case 'to':
|
||||
return 'ST_Touches';
|
||||
case 'wi':
|
||||
return 'ST_Within';
|
||||
case 'ic':
|
||||
return 'ST_IsClosed';
|
||||
case 'is':
|
||||
return 'ST_IsSimple';
|
||||
case 'iv':
|
||||
return 'ST_IsValid';
|
||||
case 'co':return 'ST_Contains';
|
||||
case 'cr':return 'ST_Crosses';
|
||||
case 'di':return 'ST_Disjoint';
|
||||
case 'eq':return 'ST_Equals';
|
||||
case 'in':return 'ST_Intersects';
|
||||
case 'ov':return 'ST_Overlaps';
|
||||
case 'to':return 'ST_Touches';
|
||||
case 'wi':return 'ST_Within';
|
||||
case 'ic':return 'ST_IsClosed';
|
||||
case 'is':return 'ST_IsSimple';
|
||||
case 'iv':return 'ST_IsValid';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,9 +176,6 @@ class ConditionsBuilder
|
||||
$functionName = str_replace('ST_', 'ST', $functionName);
|
||||
$argument = $hasArgument ? 'geometry::STGeomFromText(?,0)' : '';
|
||||
return "$column.$functionName($argument)=1";
|
||||
case 'sqlite':
|
||||
$argument = $hasArgument ? '?' : '0';
|
||||
return "$functionName($column, $argument)=1";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,35 +16,23 @@ class DataConverter
|
||||
|
||||
private function convertRecordValue($conversion, $value)
|
||||
{
|
||||
$args = explode('|', $conversion);
|
||||
$type = array_shift($args);
|
||||
switch ($type) {
|
||||
switch ($conversion) {
|
||||
case 'boolean':
|
||||
return $value ? true : false;
|
||||
case 'integer':
|
||||
return (int) $value;
|
||||
case 'float':
|
||||
return (float) $value;
|
||||
case 'decimal':
|
||||
return number_format($value, $args[0], '.', '');
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function getRecordValueConversion(ReflectedColumn $column): string
|
||||
{
|
||||
if (in_array($this->driver, ['mysql', 'sqlsrv', 'sqlite']) && $column->isBoolean()) {
|
||||
if (in_array($this->driver, ['mysql', 'sqlsrv']) && $column->isBoolean()) {
|
||||
return 'boolean';
|
||||
}
|
||||
if (in_array($this->driver, ['sqlsrv', 'sqlite']) && in_array($column->getType(), ['integer', 'bigint'])) {
|
||||
if ($this->driver == 'sqlsrv' && $column->getType() == 'bigint') {
|
||||
return 'integer';
|
||||
}
|
||||
if (in_array($this->driver, ['sqlite', 'pgsql']) && in_array($column->getType(), ['float', 'double'])) {
|
||||
return 'float';
|
||||
}
|
||||
if (in_array($this->driver, ['sqlite']) && in_array($column->getType(), ['decimal'])) {
|
||||
return 'decimal|' . $column->getScale();
|
||||
}
|
||||
return 'none';
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,6 @@ class GenericDB
|
||||
return "$this->driver:host=$this->address port=$this->port dbname=$this->database options='--client_encoding=UTF8'";
|
||||
case 'sqlsrv':
|
||||
return "$this->driver:Server=$this->address,$this->port;Database=$this->database";
|
||||
case 'sqlite':
|
||||
return "$this->driver:$this->address";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,10 +50,6 @@ class GenericDB
|
||||
];
|
||||
case 'sqlsrv':
|
||||
return [];
|
||||
case 'sqlite':
|
||||
return [
|
||||
'PRAGMA foreign_keys = on;',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +76,6 @@ class GenericDB
|
||||
\PDO::SQLSRV_ATTR_DIRECT_QUERY => false,
|
||||
\PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE => true,
|
||||
];
|
||||
case 'sqlite':
|
||||
return $options + [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,17 +183,11 @@ class GenericDB
|
||||
case 'mysql':
|
||||
$stmt = $this->query('SELECT LAST_INSERT_ID()', []);
|
||||
break;
|
||||
case 'sqlite':
|
||||
$stmt = $this->query('SELECT LAST_INSERT_ROWID()', []);
|
||||
break;
|
||||
}
|
||||
$pkValue = $stmt->fetchColumn(0);
|
||||
if ($this->driver == 'sqlsrv' && $table->getPk()->getType() == 'bigint') {
|
||||
return (int) $pkValue;
|
||||
}
|
||||
if ($this->driver == 'sqlite' && in_array($table->getPk()->getType(), ['integer', 'bigint'])) {
|
||||
return (int) $pkValue;
|
||||
}
|
||||
return $pkValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,9 +72,7 @@ class GenericDefinition
|
||||
return $column->getPk() ? ' AUTO_INCREMENT' : '';
|
||||
case 'pgsql':
|
||||
case 'sqlsrv':
|
||||
return $column->getPk() ? ' IDENTITY(1,1)' : '';
|
||||
case 'sqlite':
|
||||
return $column->getPk() ? ' AUTOINCREMENT' : '';
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,8 +96,6 @@ class GenericDefinition
|
||||
return "ALTER TABLE $p1 RENAME TO $p2";
|
||||
case 'sqlsrv':
|
||||
return "EXEC sp_rename $p1, $p2";
|
||||
case 'sqlite':
|
||||
return "ALTER TABLE $p1 RENAME TO $p2";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +114,6 @@ class GenericDefinition
|
||||
case 'sqlsrv':
|
||||
$p4 = $this->quote($tableName . '.' . $columnName);
|
||||
return "EXEC sp_rename $p4, $p3, 'COLUMN'";
|
||||
case 'sqlite':
|
||||
return "ALTER TABLE $p1 RENAME COLUMN $p2 TO $p3";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,22 +272,12 @@ class GenericDefinition
|
||||
$f4 = $this->quote($newColumn->getFk());
|
||||
$f5 = $this->quote($this->getPrimaryKey($newColumn->getFk()));
|
||||
$f6 = $this->quote($tableName . '_' . $pkColumn . '_pkey');
|
||||
if ($this->driver == 'sqlite') {
|
||||
if ($newColumn->getPk()) {
|
||||
$f2 = str_replace('NULL', 'NULL PRIMARY KEY', $f2);
|
||||
}
|
||||
$fields[] = "$f1 $f2";
|
||||
if ($newColumn->getFk()) {
|
||||
$constraints[] = "FOREIGN KEY ($f1) REFERENCES $f4 ($f5)";
|
||||
}
|
||||
} else {
|
||||
$fields[] = "$f1 $f2";
|
||||
if ($newColumn->getPk()) {
|
||||
$constraints[] = "CONSTRAINT $f6 PRIMARY KEY ($f1)";
|
||||
}
|
||||
if ($newColumn->getFk()) {
|
||||
$constraints[] = "CONSTRAINT $f3 FOREIGN KEY ($f1) REFERENCES $f4 ($f5)";
|
||||
}
|
||||
$fields[] = "$f1 $f2";
|
||||
if ($newColumn->getPk()) {
|
||||
$constraints[] = "CONSTRAINT $f6 PRIMARY KEY ($f1)";
|
||||
}
|
||||
if ($newColumn->getFk()) {
|
||||
$constraints[] = "CONSTRAINT $f3 FOREIGN KEY ($f1) REFERENCES $f4 ($f5)";
|
||||
}
|
||||
}
|
||||
$p2 = implode(',', array_merge($fields, $constraints));
|
||||
@@ -313,8 +297,6 @@ class GenericDefinition
|
||||
return "ALTER TABLE $p1 ADD COLUMN $p2 $p3";
|
||||
case 'sqlsrv':
|
||||
return "ALTER TABLE $p1 ADD $p2 $p3";
|
||||
case 'sqlite':
|
||||
return "ALTER TABLE $p1 ADD COLUMN $p2 $p3";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,8 +310,6 @@ class GenericDefinition
|
||||
return "DROP TABLE $p1 CASCADE;";
|
||||
case 'sqlsrv':
|
||||
return "DROP TABLE $p1;";
|
||||
case 'sqlite':
|
||||
return "DROP TABLE $p1;";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,46 +324,44 @@ class GenericDefinition
|
||||
return "ALTER TABLE $p1 DROP COLUMN $p2 CASCADE;";
|
||||
case 'sqlsrv':
|
||||
return "ALTER TABLE $p1 DROP COLUMN $p2;";
|
||||
case 'sqlite':
|
||||
return "ALTER TABLE $p1 DROP COLUMN $p2;";
|
||||
}
|
||||
}
|
||||
|
||||
public function renameTable(string $tableName, string $newTableName)
|
||||
{
|
||||
$sql = $this->getTableRenameSQL($tableName, $newTableName);
|
||||
return $this->query($sql, []);
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
public function renameColumn(string $tableName, string $columnName, ReflectedColumn $newColumn)
|
||||
{
|
||||
$sql = $this->getColumnRenameSQL($tableName, $columnName, $newColumn);
|
||||
return $this->query($sql, []);
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
public function retypeColumn(string $tableName, string $columnName, ReflectedColumn $newColumn)
|
||||
{
|
||||
$sql = $this->getColumnRetypeSQL($tableName, $columnName, $newColumn);
|
||||
return $this->query($sql, []);
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
public function setColumnNullable(string $tableName, string $columnName, ReflectedColumn $newColumn)
|
||||
{
|
||||
$sql = $this->getSetColumnNullableSQL($tableName, $columnName, $newColumn);
|
||||
return $this->query($sql, []);
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
public function addColumnPrimaryKey(string $tableName, string $columnName, ReflectedColumn $newColumn)
|
||||
{
|
||||
$sql = $this->getSetColumnPkConstraintSQL($tableName, $columnName, $newColumn);
|
||||
$this->query($sql, []);
|
||||
$this->query($sql);
|
||||
if ($this->canAutoIncrement($newColumn)) {
|
||||
$sql = $this->getSetColumnPkSequenceSQL($tableName, $columnName, $newColumn);
|
||||
$this->query($sql, []);
|
||||
$this->query($sql);
|
||||
$sql = $this->getSetColumnPkSequenceStartSQL($tableName, $columnName, $newColumn);
|
||||
$this->query($sql, []);
|
||||
$this->query($sql);
|
||||
$sql = $this->getSetColumnPkDefaultSQL($tableName, $columnName, $newColumn);
|
||||
$this->query($sql, []);
|
||||
$this->query($sql);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -392,55 +370,55 @@ class GenericDefinition
|
||||
{
|
||||
if ($this->canAutoIncrement($newColumn)) {
|
||||
$sql = $this->getSetColumnPkDefaultSQL($tableName, $columnName, $newColumn);
|
||||
$this->query($sql, []);
|
||||
$this->query($sql);
|
||||
$sql = $this->getSetColumnPkSequenceSQL($tableName, $columnName, $newColumn);
|
||||
$this->query($sql, []);
|
||||
$this->query($sql);
|
||||
}
|
||||
$sql = $this->getSetColumnPkConstraintSQL($tableName, $columnName, $newColumn);
|
||||
$this->query($sql, []);
|
||||
$this->query($sql);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addColumnForeignKey(string $tableName, string $columnName, ReflectedColumn $newColumn)
|
||||
{
|
||||
$sql = $this->getAddColumnFkConstraintSQL($tableName, $columnName, $newColumn);
|
||||
return $this->query($sql, []);
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
public function removeColumnForeignKey(string $tableName, string $columnName, ReflectedColumn $newColumn)
|
||||
{
|
||||
$sql = $this->getRemoveColumnFkConstraintSQL($tableName, $columnName, $newColumn);
|
||||
return $this->query($sql, []);
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
public function addTable(ReflectedTable $newTable)
|
||||
{
|
||||
$sql = $this->getAddTableSQL($newTable);
|
||||
return $this->query($sql, []);
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
public function addColumn(string $tableName, ReflectedColumn $newColumn)
|
||||
{
|
||||
$sql = $this->getAddColumnSQL($tableName, $newColumn);
|
||||
return $this->query($sql, []);
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
public function removeTable(string $tableName)
|
||||
{
|
||||
$sql = $this->getRemoveTableSQL($tableName);
|
||||
return $this->query($sql, []);
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
public function removeColumn(string $tableName, string $columnName)
|
||||
{
|
||||
$sql = $this->getRemoveColumnSQL($tableName, $columnName);
|
||||
return $this->query($sql, []);
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
private function query(string $sql, array $arguments): bool
|
||||
private function query(string $sql): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
// echo "- $sql -- " . json_encode($arguments) . "\n";
|
||||
return $stmt->execute($arguments);
|
||||
//echo "- $sql -- []\n";
|
||||
return $stmt->execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ class GenericReflection
|
||||
return ['spatial_ref_sys', 'raster_columns', 'raster_overviews', 'geography_columns', 'geometry_columns'];
|
||||
case 'sqlsrv':
|
||||
return [];
|
||||
case 'sqlite':
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +42,6 @@ class GenericReflection
|
||||
return 'SELECT c.relname as "TABLE_NAME", c.relkind as "TABLE_TYPE" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (\'r\', \'v\') AND n.nspname <> \'pg_catalog\' AND n.nspname <> \'information_schema\' AND n.nspname !~ \'^pg_toast\' AND pg_catalog.pg_table_is_visible(c.oid) AND \'\' <> ? ORDER BY "TABLE_NAME";';
|
||||
case 'sqlsrv':
|
||||
return 'SELECT o.name as "TABLE_NAME", o.xtype as "TABLE_TYPE" FROM sysobjects o WHERE o.xtype IN (\'U\', \'V\') ORDER BY "TABLE_NAME"';
|
||||
case 'sqlite':
|
||||
return 'SELECT t.name as "TABLE_NAME", t.type as "TABLE_TYPE" FROM sqlite_master t WHERE t.type IN (\'table\', \'view\') AND \'\' <> ? ORDER BY "TABLE_NAME"';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,13 +49,11 @@ class GenericReflection
|
||||
{
|
||||
switch ($this->driver) {
|
||||
case 'mysql':
|
||||
return 'SELECT "COLUMN_NAME", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" as "CHARACTER_MAXIMUM_LENGTH", "NUMERIC_PRECISION", "NUMERIC_SCALE", "COLUMN_TYPE" FROM "INFORMATION_SCHEMA"."COLUMNS" WHERE "TABLE_NAME" = ? AND "TABLE_SCHEMA" = ? ORDER BY "ORDINAL_POSITION"';
|
||||
return 'SELECT "COLUMN_NAME", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" as "CHARACTER_MAXIMUM_LENGTH", "NUMERIC_PRECISION", "NUMERIC_SCALE", "COLUMN_TYPE" FROM "INFORMATION_SCHEMA"."COLUMNS" WHERE "TABLE_NAME" = ? AND "TABLE_SCHEMA" = ?';
|
||||
case 'pgsql':
|
||||
return 'SELECT a.attname AS "COLUMN_NAME", case when a.attnotnull then \'NO\' else \'YES\' end as "IS_NULLABLE", pg_catalog.format_type(a.atttypid, -1) as "DATA_TYPE", case when a.atttypmod < 0 then NULL else a.atttypmod-4 end as "CHARACTER_MAXIMUM_LENGTH", case when a.atttypid != 1700 then NULL else ((a.atttypmod - 4) >> 16) & 65535 end as "NUMERIC_PRECISION", case when a.atttypid != 1700 then NULL else (a.atttypmod - 4) & 65535 end as "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM pg_attribute a JOIN pg_class pgc ON pgc.oid = a.attrelid WHERE pgc.relname = ? AND \'\' <> ? AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum;';
|
||||
return 'SELECT a.attname AS "COLUMN_NAME", case when a.attnotnull then \'NO\' else \'YES\' end as "IS_NULLABLE", pg_catalog.format_type(a.atttypid, -1) as "DATA_TYPE", case when a.atttypmod < 0 then NULL else a.atttypmod-4 end as "CHARACTER_MAXIMUM_LENGTH", case when a.atttypid != 1700 then NULL else ((a.atttypmod - 4) >> 16) & 65535 end as "NUMERIC_PRECISION", case when a.atttypid != 1700 then NULL else (a.atttypmod - 4) & 65535 end as "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM pg_attribute a JOIN pg_class pgc ON pgc.oid = a.attrelid WHERE pgc.relname = ? AND \'\' <> ? AND a.attnum > 0 AND NOT a.attisdropped;';
|
||||
case 'sqlsrv':
|
||||
return 'SELECT c.name AS "COLUMN_NAME", c.is_nullable AS "IS_NULLABLE", t.Name AS "DATA_TYPE", (c.max_length/2) AS "CHARACTER_MAXIMUM_LENGTH", c.precision AS "NUMERIC_PRECISION", c.scale AS "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM sys.columns c INNER JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE c.object_id = OBJECT_ID(?) AND \'\' <> ? ORDER BY c.column_id';
|
||||
case 'sqlite':
|
||||
return 'SELECT "name" AS "COLUMN_NAME", case when "notnull"==1 then \'no\' else \'yes\' end as "IS_NULLABLE", lower("type") AS "DATA_TYPE", 2147483647 AS "CHARACTER_MAXIMUM_LENGTH", 0 AS "NUMERIC_PRECISION", 0 AS "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM pragma_table_info(?) WHERE \'\' <> ? ORDER BY "cid"';
|
||||
return 'SELECT c.name AS "COLUMN_NAME", c.is_nullable AS "IS_NULLABLE", t.Name AS "DATA_TYPE", (c.max_length/2) AS "CHARACTER_MAXIMUM_LENGTH", c.precision AS "NUMERIC_PRECISION", c.scale AS "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM sys.columns c INNER JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE c.object_id = OBJECT_ID(?) AND \'\' <> ?';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +66,6 @@ class GenericReflection
|
||||
return 'SELECT a.attname AS "COLUMN_NAME" FROM pg_attribute a JOIN pg_constraint c ON (c.conrelid, c.conkey[1]) = (a.attrelid, a.attnum) JOIN pg_class pgc ON pgc.oid = a.attrelid WHERE pgc.relname = ? AND \'\' <> ? AND c.contype = \'p\'';
|
||||
case 'sqlsrv':
|
||||
return 'SELECT c.NAME as "COLUMN_NAME" FROM sys.key_constraints kc inner join sys.objects t on t.object_id = kc.parent_object_id INNER JOIN sys.index_columns ic ON kc.parent_object_id = ic.object_id and kc.unique_index_id = ic.index_id INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE kc.type = \'PK\' and t.object_id = OBJECT_ID(?) and \'\' <> ?';
|
||||
case 'sqlite':
|
||||
return 'SELECT "name" as "COLUMN_NAME" FROM pragma_table_info(?) WHERE "pk"=1 AND \'\' <> ?';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +78,6 @@ class GenericReflection
|
||||
return 'SELECT a.attname AS "COLUMN_NAME", c.confrelid::regclass::text AS "REFERENCED_TABLE_NAME" FROM pg_attribute a JOIN pg_constraint c ON (c.conrelid, c.conkey[1]) = (a.attrelid, a.attnum) JOIN pg_class pgc ON pgc.oid = a.attrelid WHERE pgc.relname = ? AND \'\' <> ? AND c.contype = \'f\'';
|
||||
case 'sqlsrv':
|
||||
return 'SELECT COL_NAME(fc.parent_object_id, fc.parent_column_id) AS "COLUMN_NAME", OBJECT_NAME (f.referenced_object_id) AS "REFERENCED_TABLE_NAME" FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc ON f.OBJECT_ID = fc.constraint_object_id WHERE f.parent_object_id = OBJECT_ID(?) and \'\' <> ?';
|
||||
case 'sqlite':
|
||||
return 'SELECT "from" AS "COLUMN_NAME", "table" AS "REFERENCED_TABLE_NAME" FROM pragma_foreign_key_list(?) WHERE \'\' <> ?';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,22 +95,20 @@ class GenericReflection
|
||||
return !$tables || in_array($v['TABLE_NAME'], $tables);
|
||||
});
|
||||
foreach ($results as &$result) {
|
||||
$map = [];
|
||||
switch ($this->driver) {
|
||||
case 'mysql':
|
||||
$map = ['BASE TABLE' => 'table', 'VIEW' => 'view'];
|
||||
$result['TABLE_TYPE'] = $map[$result['TABLE_TYPE']];
|
||||
break;
|
||||
case 'pgsql':
|
||||
$map = ['r' => 'table', 'v' => 'view'];
|
||||
$result['TABLE_TYPE'] = $map[$result['TABLE_TYPE']];
|
||||
break;
|
||||
case 'sqlsrv':
|
||||
$map = ['U' => 'table', 'V' => 'view'];
|
||||
break;
|
||||
case 'sqlite':
|
||||
$map = ['table' => 'table', 'view' => 'view'];
|
||||
$result['TABLE_TYPE'] = $map[trim($result['TABLE_TYPE'])];
|
||||
break;
|
||||
}
|
||||
$result['TABLE_TYPE'] = $map[trim($result['TABLE_TYPE'])];
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
@@ -149,23 +137,6 @@ class GenericReflection
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->driver == 'sqlite') {
|
||||
foreach ($results as &$result) {
|
||||
// sqlite does not properly reflect display width of types
|
||||
preg_match('|([a-z]+)(\(([0-9]+)(,([0-9]+))?\))?|', $result['DATA_TYPE'], $matches);
|
||||
if (isset($matches[1])) {
|
||||
$result['DATA_TYPE'] = $matches[1];
|
||||
} else {
|
||||
$result['DATA_TYPE'] = 'integer';
|
||||
}
|
||||
if (isset($matches[5])) {
|
||||
$result['NUMERIC_PRECISION'] = $matches[3];
|
||||
$result['NUMERIC_SCALE'] = $matches[5];
|
||||
} else if (isset($matches[3])) {
|
||||
$result['CHARACTER_MAXIMUM_LENGTH'] = $matches[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,26 +14,19 @@ class TypeConverter
|
||||
private $fromJdbc = [
|
||||
'mysql' => [
|
||||
'clob' => 'longtext',
|
||||
'boolean' => 'tinyint(1)',
|
||||
'boolean' => 'tinyint',
|
||||
'blob' => 'longblob',
|
||||
'timestamp' => 'datetime',
|
||||
],
|
||||
'pgsql' => [
|
||||
'clob' => 'text',
|
||||
'blob' => 'bytea',
|
||||
'float' => 'real',
|
||||
'double' => 'double precision',
|
||||
'varbinary' => 'bytea',
|
||||
],
|
||||
'sqlsrv' => [
|
||||
'boolean' => 'bit',
|
||||
'varchar' => 'nvarchar',
|
||||
'clob' => 'ntext',
|
||||
'blob' => 'image',
|
||||
'time' => 'time(0)',
|
||||
'timestamp' => 'datetime2(0)',
|
||||
'double' => 'float',
|
||||
'float' => 'real',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -128,20 +121,6 @@ class TypeConverter
|
||||
'uniqueidentifier' => 'char',
|
||||
'xml' => 'clob',
|
||||
],
|
||||
'sqlite' => [
|
||||
'tinytext' => 'clob',
|
||||
'text' => 'clob',
|
||||
'mediumtext' => 'clob',
|
||||
'longtext' => 'clob',
|
||||
'mediumint' => 'integer',
|
||||
'int' => 'integer',
|
||||
'bigint' => 'bigint',
|
||||
'int2' => 'smallint',
|
||||
'int4' => 'integer',
|
||||
'int8' => 'bigint',
|
||||
'double precision' => 'double',
|
||||
'datetime' => 'timestamp'
|
||||
],
|
||||
];
|
||||
|
||||
// source: https://docs.oracle.com/javase/9/docs/api/java/sql/Types.html
|
||||
@@ -202,8 +181,7 @@ class TypeConverter
|
||||
$jdbcType = $this->toJdbc['simplified'][$jdbcType];
|
||||
}
|
||||
if (!isset($this->valid[$jdbcType])) {
|
||||
//throw new \Exception("Unsupported type '$jdbcType' for driver '$this->driver'");
|
||||
$jdbcType = 'clob';
|
||||
throw new \Exception("Unsupported type '$jdbcType' for driver '$this->driver'");
|
||||
}
|
||||
return $jdbcType;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ class Geometry implements \JsonSerializable
|
||||
$coordinates = preg_replace('|([0-9\-\.]+ )+([0-9\-\.]+)|', '[\1\2]', $coordinates);
|
||||
}
|
||||
$coordinates = str_replace(['(', ')', ', ', ' '], ['[', ']', ',', ','], $coordinates);
|
||||
$json = $coordinates;
|
||||
$coordinates = json_decode($coordinates);
|
||||
if (!$coordinates) {
|
||||
throw new \Exception('Could not decode WKT: ' . $wkt);
|
||||
|
||||
@@ -10,7 +10,6 @@ use Tqdev\PhpCrudApi\Controller\Responder;
|
||||
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
|
||||
use Tqdev\PhpCrudApi\Middleware\Communication\VariableStore;
|
||||
use Tqdev\PhpCrudApi\Middleware\Router\Router;
|
||||
use Tqdev\PhpCrudApi\Record\ErrorCode;
|
||||
use Tqdev\PhpCrudApi\Record\FilterInfo;
|
||||
use Tqdev\PhpCrudApi\RequestUtils;
|
||||
|
||||
@@ -72,20 +71,9 @@ class AuthorizationMiddleware extends Middleware
|
||||
}
|
||||
}
|
||||
|
||||
private function pathHandler(string $path) /*: bool*/
|
||||
{
|
||||
$pathHandler = $this->getProperty('pathHandler', '');
|
||||
return $pathHandler ? call_user_func($pathHandler, $path) : true;
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
|
||||
{
|
||||
$path = RequestUtils::getPathSegment($request, 1);
|
||||
|
||||
if (!$this->pathHandler($path)) {
|
||||
return $this->responder->error(ErrorCode::ROUTE_NOT_FOUND, $request->getUri()->getPath());
|
||||
}
|
||||
|
||||
$operation = RequestUtils::getOperation($request);
|
||||
$tableNames = RequestUtils::getTableNames($request, $this->reflection);
|
||||
foreach ($tableNames as $tableName) {
|
||||
|
||||
@@ -7,21 +7,11 @@ use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Tqdev\PhpCrudApi\Controller\Responder;
|
||||
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
|
||||
use Tqdev\PhpCrudApi\Middleware\Router\Router;
|
||||
use Tqdev\PhpCrudApi\Record\ErrorCode;
|
||||
use Tqdev\PhpCrudApi\ResponseFactory;
|
||||
use Tqdev\PhpCrudApi\ResponseUtils;
|
||||
|
||||
class CorsMiddleware extends Middleware
|
||||
{
|
||||
private $debug;
|
||||
|
||||
public function __construct(Router $router, Responder $responder, array $properties, bool $debug)
|
||||
{
|
||||
parent::__construct($router, $responder, $properties);
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
private function isOriginAllowed(string $origin, string $allowedOrigins): bool
|
||||
{
|
||||
$found = false;
|
||||
@@ -46,9 +36,6 @@ class CorsMiddleware extends Middleware
|
||||
} elseif ($method == 'OPTIONS') {
|
||||
$response = ResponseFactory::fromStatus(ResponseFactory::OK);
|
||||
$allowHeaders = $this->getProperty('allowHeaders', 'Content-Type, X-XSRF-TOKEN, X-Authorization');
|
||||
if ($this->debug) {
|
||||
$allowHeaders = implode(', ', array_filter([$allowHeaders, 'X-Exception-Name, X-Exception-Message, X-Exception-File']));
|
||||
}
|
||||
if ($allowHeaders) {
|
||||
$response = $response->withHeader('Access-Control-Allow-Headers', $allowHeaders);
|
||||
}
|
||||
@@ -65,22 +52,11 @@ class CorsMiddleware extends Middleware
|
||||
$response = $response->withHeader('Access-Control-Max-Age', $maxAge);
|
||||
}
|
||||
$exposeHeaders = $this->getProperty('exposeHeaders', '');
|
||||
if ($this->debug) {
|
||||
$exposeHeaders = implode(', ', array_filter([$exposeHeaders, 'X-Exception-Name, X-Exception-Message, X-Exception-File']));
|
||||
}
|
||||
if ($exposeHeaders) {
|
||||
$response = $response->withHeader('Access-Control-Expose-Headers', $exposeHeaders);
|
||||
}
|
||||
} else {
|
||||
$response = null;
|
||||
try {
|
||||
$response = $next->handle($request);
|
||||
} catch (\Throwable $e) {
|
||||
$response = $this->responder->error(ErrorCode::ERROR_NOT_FOUND, $e->getMessage());
|
||||
if ($this->debug) {
|
||||
$response = ResponseUtils::addExceptionHeaders($response, $e);
|
||||
}
|
||||
}
|
||||
$response = $next->handle($request);
|
||||
}
|
||||
if ($origin) {
|
||||
$allowCredentials = $this->getProperty('allowCredentials', 'true');
|
||||
|
||||
@@ -42,19 +42,16 @@ class DbAuthMiddleware extends Middleware
|
||||
}
|
||||
$path = RequestUtils::getPathSegment($request, 1);
|
||||
$method = $request->getMethod();
|
||||
if ($method == 'POST' && in_array($path, ['login', 'register', 'password'])) {
|
||||
if ($method == 'POST' && $path == 'login') {
|
||||
$body = $request->getParsedBody();
|
||||
$username = isset($body->username) ? $body->username : '';
|
||||
$password = isset($body->password) ? $body->password : '';
|
||||
$newPassword = isset($body->newPassword) ? $body->newPassword : '';
|
||||
$tableName = $this->getProperty('usersTable', 'users');
|
||||
$table = $this->reflection->getTable($tableName);
|
||||
$usernameColumnName = $this->getProperty('usernameColumn', 'username');
|
||||
$usernameColumn = $table->getColumn($usernameColumnName);
|
||||
$passwordColumnName = $this->getProperty('passwordColumn', 'password');
|
||||
$passwordLength = $this->getProperty('passwordLength', '12');
|
||||
$pkName = $table->getPk()->getName();
|
||||
$registerUser = $this->getProperty('registerUser', '');
|
||||
$passwordColumn = $table->getColumn($passwordColumnName);
|
||||
$condition = new ColumnCondition($usernameColumn, 'eq', $username);
|
||||
$returnedColumns = $this->getProperty('returnedColumns', '');
|
||||
if (!$returnedColumns) {
|
||||
@@ -62,67 +59,20 @@ class DbAuthMiddleware extends Middleware
|
||||
} else {
|
||||
$columnNames = array_map('trim', explode(',', $returnedColumns));
|
||||
$columnNames[] = $passwordColumnName;
|
||||
$columnNames[] = $pkName;
|
||||
}
|
||||
$columnOrdering = $this->ordering->getDefaultColumnOrdering($table);
|
||||
if ($path == 'register') {
|
||||
if (!$registerUser) {
|
||||
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
|
||||
}
|
||||
if (strlen($password) < $passwordLength) {
|
||||
return $this->responder->error(ErrorCode::PASSWORD_TOO_SHORT, $passwordLength);
|
||||
}
|
||||
$users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
|
||||
if (!empty($users)) {
|
||||
return $this->responder->error(ErrorCode::USER_ALREADY_EXIST, $username);
|
||||
}
|
||||
$data = json_decode($registerUser, true);
|
||||
$data = is_array($data) ? $data : [];
|
||||
$data[$usernameColumnName] = $username;
|
||||
$data[$passwordColumnName] = password_hash($password, PASSWORD_DEFAULT);
|
||||
$this->db->createSingle($table, $data);
|
||||
$users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
|
||||
foreach ($users as $user) {
|
||||
$users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
|
||||
foreach ($users as $user) {
|
||||
if (password_verify($password, $user[$passwordColumnName]) == 1) {
|
||||
if (!headers_sent()) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
unset($user[$passwordColumnName]);
|
||||
$_SESSION['user'] = $user;
|
||||
return $this->responder->success($user);
|
||||
}
|
||||
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
|
||||
}
|
||||
if ($path == 'login') {
|
||||
$users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
|
||||
foreach ($users as $user) {
|
||||
if (password_verify($password, $user[$passwordColumnName]) == 1) {
|
||||
if (!headers_sent()) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
unset($user[$passwordColumnName]);
|
||||
$_SESSION['user'] = $user;
|
||||
return $this->responder->success($user);
|
||||
}
|
||||
}
|
||||
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
|
||||
}
|
||||
if ($path == 'password') {
|
||||
if ($username != ($_SESSION['user'][$usernameColumnName] ?? '')) {
|
||||
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
|
||||
}
|
||||
if (strlen($newPassword) < $passwordLength) {
|
||||
return $this->responder->error(ErrorCode::PASSWORD_TOO_SHORT, $passwordLength);
|
||||
}
|
||||
$users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
|
||||
foreach ($users as $user) {
|
||||
if (password_verify($password, $user[$passwordColumnName]) == 1) {
|
||||
if (!headers_sent()) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
$data = [$passwordColumnName => password_hash($newPassword, PASSWORD_DEFAULT)];
|
||||
$this->db->updateSingle($table, $data, $user[$pkName]);
|
||||
unset($user[$passwordColumnName]);
|
||||
return $this->responder->success($user);
|
||||
}
|
||||
}
|
||||
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
|
||||
}
|
||||
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
|
||||
}
|
||||
if ($method == 'POST' && $path == 'logout') {
|
||||
if (isset($_SESSION['user'])) {
|
||||
@@ -135,12 +85,6 @@ class DbAuthMiddleware extends Middleware
|
||||
}
|
||||
return $this->responder->error(ErrorCode::AUTHENTICATION_REQUIRED, '');
|
||||
}
|
||||
if ($method == 'GET' && $path == 'me') {
|
||||
if (isset($_SESSION['user'])) {
|
||||
return $this->responder->success($_SESSION['user']);
|
||||
}
|
||||
return $this->responder->error(ErrorCode::AUTHENTICATION_REQUIRED, '');
|
||||
}
|
||||
if (!isset($_SESSION['user']) || !$_SESSION['user']) {
|
||||
$authenticationMode = $this->getProperty('mode', 'required');
|
||||
if ($authenticationMode == 'required') {
|
||||
|
||||
@@ -38,7 +38,7 @@ class MultiTenancyMiddleware extends Middleware
|
||||
private function getPairs($handler, string $operation, string $tableName): array
|
||||
{
|
||||
$result = array();
|
||||
$pairs = call_user_func($handler, $operation, $tableName) ?: [];
|
||||
$pairs = call_user_func($handler, $operation, $tableName);
|
||||
$table = $this->reflection->getTable($tableName);
|
||||
foreach ($pairs as $k => $v) {
|
||||
if ($table->hasColumn($k)) {
|
||||
|
||||
@@ -50,9 +50,7 @@ class SimpleRouter implements Router
|
||||
return substr($fullPath, 0, -1 * strlen($path));
|
||||
}
|
||||
}
|
||||
if ('/' . basename(__FILE__) == $fullPath) {
|
||||
return $fullPath;
|
||||
}
|
||||
return $fullPath;
|
||||
}
|
||||
return '/';
|
||||
}
|
||||
@@ -95,7 +93,6 @@ class SimpleRouter implements Router
|
||||
$data = gzcompress(json_encode($this->routes, JSON_UNESCAPED_UNICODE));
|
||||
$this->cache->set('PathTree', $data, $this->ttl);
|
||||
}
|
||||
|
||||
return $this->handle($request);
|
||||
}
|
||||
|
||||
@@ -104,7 +101,7 @@ class SimpleRouter implements Router
|
||||
$method = strtoupper($request->getMethod());
|
||||
$path = array();
|
||||
$segment = $method;
|
||||
for ($i = 1; strlen($segment) > 0; $i++) {
|
||||
for ($i = 1; $segment; $i++) {
|
||||
array_push($path, $segment);
|
||||
$segment = RequestUtils::getPathSegment($request, $i);
|
||||
}
|
||||
@@ -144,8 +141,6 @@ class SimpleRouter implements Router
|
||||
} catch (\PDOException $e) {
|
||||
if (strpos(strtolower($e->getMessage()), 'duplicate') !== false) {
|
||||
$response = $this->responder->error(ErrorCode::DUPLICATE_KEY_EXCEPTION, '');
|
||||
} elseif (strpos(strtolower($e->getMessage()), 'unique constraint') !== false) {
|
||||
$response = $this->responder->error(ErrorCode::DUPLICATE_KEY_EXCEPTION, '');
|
||||
} elseif (strpos(strtolower($e->getMessage()), 'default value') !== false) {
|
||||
$response = $this->responder->error(ErrorCode::DATA_INTEGRITY_VIOLATION, '');
|
||||
} elseif (strpos(strtolower($e->getMessage()), 'allow nulls') !== false) {
|
||||
|
||||
@@ -6,7 +6,6 @@ use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
|
||||
use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
|
||||
use Tqdev\PhpCrudApi\Column\ReflectionService;
|
||||
use Tqdev\PhpCrudApi\Controller\Responder;
|
||||
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
|
||||
@@ -31,97 +30,11 @@ class SanitationMiddleware extends Middleware
|
||||
if ($table->hasColumn($columnName)) {
|
||||
$column = $table->getColumn($columnName);
|
||||
$value = call_user_func($handler, $operation, $tableName, $column->serialize(), $value);
|
||||
$value = $this->sanitizeType($table, $column, $value);
|
||||
}
|
||||
}
|
||||
return (object) $context;
|
||||
}
|
||||
|
||||
private function sanitizeType(ReflectedTable $table, ReflectedColumn $column, $value)
|
||||
{
|
||||
$tables = $this->getArrayProperty('tables', 'all');
|
||||
$types = $this->getArrayProperty('types', 'all');
|
||||
if (
|
||||
(in_array('all', $tables) || in_array($table->getName(), $tables)) &&
|
||||
(in_array('all', $types) || in_array($column->getType(), $types))
|
||||
) {
|
||||
if (is_null($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$newValue = null;
|
||||
switch ($column->getType()) {
|
||||
case 'integer':
|
||||
case 'bigint':
|
||||
$newValue = filter_var(trim($value), FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
|
||||
break;
|
||||
case 'decimal':
|
||||
$newValue = filter_var(trim($value), FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
|
||||
if (is_float($newValue)) {
|
||||
$newValue = number_format($newValue, $column->getScale(), '.', '');
|
||||
}
|
||||
break;
|
||||
case 'float':
|
||||
case 'double':
|
||||
$newValue = filter_var(trim($value), FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
|
||||
break;
|
||||
case 'boolean':
|
||||
$newValue = filter_var(trim($value), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
break;
|
||||
case 'date':
|
||||
$time = strtotime(trim($value));
|
||||
if ($time !== false) {
|
||||
$newValue = date('Y-m-d', $time);
|
||||
}
|
||||
break;
|
||||
case 'time':
|
||||
$time = strtotime(trim($value));
|
||||
if ($time !== false) {
|
||||
$newValue = date('H:i:s', $time);
|
||||
}
|
||||
break;
|
||||
case 'timestamp':
|
||||
$time = strtotime(trim($value));
|
||||
if ($time !== false) {
|
||||
$newValue = date('Y-m-d H:i:s', $time);
|
||||
}
|
||||
break;
|
||||
case 'blob':
|
||||
case 'varbinary':
|
||||
// allow base64url format
|
||||
$newValue = strtr(trim($value), '-_', '+/');
|
||||
break;
|
||||
case 'clob':
|
||||
case 'varchar':
|
||||
$newValue = $value;
|
||||
break;
|
||||
case 'geometry':
|
||||
$newValue = trim($value);
|
||||
break;
|
||||
}
|
||||
if (!is_null($newValue)) {
|
||||
$value = $newValue;
|
||||
}
|
||||
} else {
|
||||
switch ($column->getType()) {
|
||||
case 'integer':
|
||||
case 'bigint':
|
||||
if (is_float($value)) {
|
||||
$value = (int) round($value);
|
||||
}
|
||||
break;
|
||||
case 'decimal':
|
||||
if (is_float($value) || is_int($value)) {
|
||||
$value = number_format((float) $value, $column->getScale(), '.', '');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// post process
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
|
||||
{
|
||||
$operation = RequestUtils::getOperation($request);
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tqdev\PhpCrudApi\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
|
||||
use Tqdev\PhpCrudApi\ResponseFactory;
|
||||
|
||||
class SslRedirectMiddleware extends Middleware
|
||||
{
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
$scheme = $uri->getScheme();
|
||||
if ($scheme == 'http') {
|
||||
$uri = $request->getUri();
|
||||
$uri = $uri->withScheme('https');
|
||||
$response = ResponseFactory::fromStatus(301);
|
||||
$response = $response->withHeader('Location', $uri->__toString());
|
||||
} else {
|
||||
$response = $next->handle($request);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,8 @@ namespace Tqdev\PhpCrudApi\Middleware;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Tqdev\PhpCrudApi\Column\ReflectionService;
|
||||
use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
|
||||
use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
|
||||
use Tqdev\PhpCrudApi\Column\ReflectionService;
|
||||
use Tqdev\PhpCrudApi\Controller\Responder;
|
||||
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
|
||||
use Tqdev\PhpCrudApi\Middleware\Router\Router;
|
||||
@@ -16,202 +15,62 @@ use Tqdev\PhpCrudApi\RequestUtils;
|
||||
|
||||
class ValidationMiddleware extends Middleware
|
||||
{
|
||||
private $reflection;
|
||||
private $reflection;
|
||||
|
||||
public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
|
||||
{
|
||||
parent::__construct($router, $responder, $properties);
|
||||
$this->reflection = $reflection;
|
||||
}
|
||||
public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
|
||||
{
|
||||
parent::__construct($router, $responder, $properties);
|
||||
$this->reflection = $reflection;
|
||||
}
|
||||
|
||||
private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: ResponseInterface?*/
|
||||
{
|
||||
$context = (array) $record;
|
||||
$details = array();
|
||||
$tableName = $table->getName();
|
||||
foreach ($context as $columnName => $value) {
|
||||
if ($table->hasColumn($columnName)) {
|
||||
$column = $table->getColumn($columnName);
|
||||
$valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
|
||||
if ($valid === true || $valid === '') {
|
||||
$valid = $this->validateType($table, $column, $value);
|
||||
}
|
||||
if ($valid !== true && $valid !== '') {
|
||||
$details[$columnName] = $valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count($details) > 0) {
|
||||
return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: ResponseInterface?*/
|
||||
{
|
||||
$context = (array) $record;
|
||||
$details = array();
|
||||
$tableName = $table->getName();
|
||||
foreach ($context as $columnName => $value) {
|
||||
if ($table->hasColumn($columnName)) {
|
||||
$column = $table->getColumn($columnName);
|
||||
$valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
|
||||
if ($valid !== true && $valid !== '') {
|
||||
$details[$columnName] = $valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count($details) > 0) {
|
||||
return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function validateType(ReflectedTable $table, ReflectedColumn $column, $value)
|
||||
{
|
||||
$tables = $this->getArrayProperty('tables', 'all');
|
||||
$types = $this->getArrayProperty('types', 'all');
|
||||
if (
|
||||
(in_array('all', $tables) || in_array($table->getName(), $tables)) &&
|
||||
(in_array('all', $types) || in_array($column->getType(), $types))
|
||||
) {
|
||||
if (is_null($value)) {
|
||||
return ($column->getNullable() ? true : "cannot be null");
|
||||
}
|
||||
if (is_string($value)) {
|
||||
// check for whitespace
|
||||
switch ($column->getType()) {
|
||||
case 'varchar':
|
||||
case 'clob':
|
||||
break;
|
||||
default:
|
||||
if (strlen(trim($value)) != strlen($value)) {
|
||||
return 'illegal whitespace';
|
||||
}
|
||||
break;
|
||||
}
|
||||
// try to parse
|
||||
switch ($column->getType()) {
|
||||
case 'integer':
|
||||
case 'bigint':
|
||||
if (
|
||||
filter_var($value, FILTER_SANITIZE_NUMBER_INT) !== $value ||
|
||||
filter_var($value, FILTER_VALIDATE_INT) === false
|
||||
) {
|
||||
return 'invalid integer';
|
||||
}
|
||||
break;
|
||||
case 'decimal':
|
||||
if (strpos($value, '.') !== false) {
|
||||
list($whole, $decimals) = explode('.', ltrim($value, '-'), 2);
|
||||
} else {
|
||||
list($whole, $decimals) = array(ltrim($value, '-'), '');
|
||||
}
|
||||
if (strlen($whole) > 0 && !ctype_digit($whole)) {
|
||||
return 'invalid decimal';
|
||||
}
|
||||
if (strlen($decimals) > 0 && !ctype_digit($decimals)) {
|
||||
return 'invalid decimal';
|
||||
}
|
||||
if (strlen($whole) > $column->getPrecision() - $column->getScale()) {
|
||||
return 'decimal too large';
|
||||
}
|
||||
if (strlen($decimals) > $column->getScale()) {
|
||||
return 'decimal too precise';
|
||||
}
|
||||
break;
|
||||
case 'float':
|
||||
case 'double':
|
||||
if (
|
||||
filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT) !== $value ||
|
||||
filter_var($value, FILTER_VALIDATE_FLOAT) === false
|
||||
) {
|
||||
return 'invalid float';
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
if (!in_array(strtolower($value), array('true', 'false'))) {
|
||||
return 'invalid boolean';
|
||||
}
|
||||
break;
|
||||
case 'date':
|
||||
if (date_create_from_format('Y-m-d', $value) === false) {
|
||||
return 'invalid date';
|
||||
}
|
||||
break;
|
||||
case 'time':
|
||||
if (date_create_from_format('H:i:s', $value) === false) {
|
||||
return 'invalid time';
|
||||
}
|
||||
break;
|
||||
case 'timestamp':
|
||||
if (date_create_from_format('Y-m-d H:i:s', $value) === false) {
|
||||
return 'invalid timestamp';
|
||||
}
|
||||
break;
|
||||
case 'clob':
|
||||
case 'varchar':
|
||||
if ($column->hasLength() && mb_strlen($value, 'UTF-8') > $column->getLength()) {
|
||||
return 'string too long';
|
||||
}
|
||||
break;
|
||||
case 'blob':
|
||||
case 'varbinary':
|
||||
if (base64_decode($value, true) === false) {
|
||||
return 'invalid base64';
|
||||
}
|
||||
if ($column->hasLength() && strlen(base64_decode($value)) > $column->getLength()) {
|
||||
return 'string too long';
|
||||
}
|
||||
break;
|
||||
case 'geometry':
|
||||
// no checks yet
|
||||
break;
|
||||
}
|
||||
} else { // check non-string types
|
||||
switch ($column->getType()) {
|
||||
case 'integer':
|
||||
case 'bigint':
|
||||
if (!is_int($value)) {
|
||||
return 'invalid integer';
|
||||
}
|
||||
break;
|
||||
case 'float':
|
||||
case 'double':
|
||||
if (!is_float($value) && !is_int($value)) {
|
||||
return 'invalid float';
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
if (!is_bool($value) && ($value !== 0) && ($value !== 1)) {
|
||||
return 'invalid boolean';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 'invalid ' . $column->getType();
|
||||
}
|
||||
}
|
||||
// extra checks
|
||||
switch ($column->getType()) {
|
||||
case 'integer': // 4 byte signed
|
||||
$value = filter_var($value, FILTER_VALIDATE_INT);
|
||||
if ($value > 2147483647 || $value < -2147483648) {
|
||||
return 'invalid integer';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (true);
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
|
||||
{
|
||||
$operation = RequestUtils::getOperation($request);
|
||||
if (in_array($operation, ['create', 'update', 'increment'])) {
|
||||
$tableName = RequestUtils::getPathSegment($request, 2);
|
||||
if ($this->reflection->hasTable($tableName)) {
|
||||
$record = $request->getParsedBody();
|
||||
if ($record !== null) {
|
||||
$handler = $this->getProperty('handler', '');
|
||||
if ($handler !== '') {
|
||||
$table = $this->reflection->getTable($tableName);
|
||||
if (is_array($record)) {
|
||||
foreach ($record as $r) {
|
||||
$response = $this->callHandler($handler, $r, $operation, $table);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$response = $this->callHandler($handler, $record, $operation, $table);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $next->handle($request);
|
||||
}
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
|
||||
{
|
||||
$operation = RequestUtils::getOperation($request);
|
||||
if (in_array($operation, ['create', 'update', 'increment'])) {
|
||||
$tableName = RequestUtils::getPathSegment($request, 2);
|
||||
if ($this->reflection->hasTable($tableName)) {
|
||||
$record = $request->getParsedBody();
|
||||
if ($record !== null) {
|
||||
$handler = $this->getProperty('handler', '');
|
||||
if ($handler !== '') {
|
||||
$table = $this->reflection->getTable($tableName);
|
||||
if (is_array($record)) {
|
||||
foreach ($record as $r) {
|
||||
$response = $this->callHandler($handler, $r, $operation, $table);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$response = $this->callHandler($handler, $record, $operation, $table);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $next->handle($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tqdev\PhpCrudApi\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Tqdev\PhpCrudApi\Column\ReflectionService;
|
||||
use Tqdev\PhpCrudApi\Controller\Responder;
|
||||
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
|
||||
use Tqdev\PhpCrudApi\Middleware\Router\Router;
|
||||
use Tqdev\PhpCrudApi\RequestUtils;
|
||||
use Tqdev\PhpCrudApi\ResponseFactory;
|
||||
|
||||
class XmlMiddleware extends Middleware
|
||||
{
|
||||
private $reflection;
|
||||
|
||||
public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
|
||||
{
|
||||
parent::__construct($router, $responder, $properties);
|
||||
$this->reflection = $reflection;
|
||||
}
|
||||
|
||||
private function json2xml($json, $types = 'null,boolean,number,string,object,array')
|
||||
{
|
||||
$a = json_decode($json);
|
||||
$d = new \DOMDocument();
|
||||
$c = $d->createElement("root");
|
||||
$d->appendChild($c);
|
||||
$t = function ($v) {
|
||||
$type = gettype($v);
|
||||
switch ($type) {
|
||||
case 'integer':
|
||||
return 'number';
|
||||
case 'double':
|
||||
return 'number';
|
||||
default:
|
||||
return strtolower($type);
|
||||
}
|
||||
};
|
||||
$ts = explode(',', $types);
|
||||
$f = function ($f, $c, $a, $s = false) use ($t, $d, $ts) {
|
||||
if (in_array($t($a), $ts)) {
|
||||
$c->setAttribute('type', $t($a));
|
||||
}
|
||||
if ($t($a) != 'array' && $t($a) != 'object') {
|
||||
if ($t($a) == 'boolean') {
|
||||
$c->appendChild($d->createTextNode($a ? 'true' : 'false'));
|
||||
} else {
|
||||
$c->appendChild($d->createTextNode($a));
|
||||
}
|
||||
} else {
|
||||
foreach ($a as $k => $v) {
|
||||
if ($k == '__type' && $t($a) == 'object') {
|
||||
$c->setAttribute('__type', $v);
|
||||
} else {
|
||||
if ($t($v) == 'object') {
|
||||
$ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k));
|
||||
$f($f, $ch, $v);
|
||||
} else if ($t($v) == 'array') {
|
||||
$ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k));
|
||||
$f($f, $ch, $v, true);
|
||||
} else {
|
||||
$va = $d->createElementNS(null, $s ? 'item' : $k);
|
||||
if ($t($v) == 'boolean') {
|
||||
$va->appendChild($d->createTextNode($v ? 'true' : 'false'));
|
||||
} else {
|
||||
$va->appendChild($d->createTextNode($v));
|
||||
}
|
||||
$ch = $c->appendChild($va);
|
||||
if (in_array($t($v), $ts)) {
|
||||
$ch->setAttribute('type', $t($v));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
$f($f, $c, $a, $t($a) == 'array');
|
||||
return $d->saveXML($d->documentElement);
|
||||
}
|
||||
|
||||
private function xml2json($xml)
|
||||
{
|
||||
$a = @dom_import_simplexml(simplexml_load_string($xml));
|
||||
if (!$a) {
|
||||
return null;
|
||||
}
|
||||
$t = function ($v) {
|
||||
$t = $v->getAttribute('type');
|
||||
$txt = $v->firstChild->nodeType == XML_TEXT_NODE;
|
||||
return $t ?: ($txt ? 'string' : 'object');
|
||||
};
|
||||
$f = function ($f, $a) use ($t) {
|
||||
$c = null;
|
||||
if ($t($a) == 'null') {
|
||||
$c = null;
|
||||
} else if ($t($a) == 'boolean') {
|
||||
$b = substr(strtolower($a->textContent), 0, 1);
|
||||
$c = in_array($b, array('1', 't'));
|
||||
} else if ($t($a) == 'number') {
|
||||
$c = $a->textContent + 0;
|
||||
} else if ($t($a) == 'string') {
|
||||
$c = $a->textContent;
|
||||
} else if ($t($a) == 'object') {
|
||||
$c = array();
|
||||
if ($a->getAttribute('__type')) {
|
||||
$c['__type'] = $a->getAttribute('__type');
|
||||
}
|
||||
for ($i = 0; $i < $a->childNodes->length; $i++) {
|
||||
$v = $a->childNodes[$i];
|
||||
$c[$v->nodeName] = $f($f, $v);
|
||||
}
|
||||
$c = (object) $c;
|
||||
} else if ($t($a) == 'array') {
|
||||
$c = array();
|
||||
for ($i = 0; $i < $a->childNodes->length; $i++) {
|
||||
$v = $a->childNodes[$i];
|
||||
$c[$i] = $f($f, $v);
|
||||
}
|
||||
}
|
||||
return $c;
|
||||
};
|
||||
$c = $f($f, $a);
|
||||
return json_encode($c);
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
|
||||
{
|
||||
parse_str($request->getUri()->getQuery(), $params);
|
||||
$isXml = isset($params['format']) && $params['format'] == 'xml';
|
||||
if ($isXml) {
|
||||
$body = $request->getBody()->getContents();
|
||||
if ($body) {
|
||||
$json = $this->xml2json($body);
|
||||
$request = $request->withParsedBody(json_decode($json));
|
||||
}
|
||||
}
|
||||
$response = $next->handle($request);
|
||||
if ($isXml) {
|
||||
$body = $response->getBody()->getContents();
|
||||
if ($body) {
|
||||
$types = implode(',', $this->getArrayProperty('types', 'null,array'));
|
||||
if ($types == '' || $types == 'all') {
|
||||
$xml = $this->json2xml($body);
|
||||
} else {
|
||||
$xml = $this->json2xml($body, $types);
|
||||
}
|
||||
$response = ResponseFactory::fromXml(ResponseFactory::OK, $xml);
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Tqdev\PhpCrudApi\OpenApi;
|
||||
|
||||
use Tqdev\PhpCrudApi\Column\ReflectionService;
|
||||
use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
|
||||
use Tqdev\PhpCrudApi\Middleware\Communication\VariableStore;
|
||||
use Tqdev\PhpCrudApi\OpenApi\OpenApiDefinition;
|
||||
|
||||
@@ -23,16 +22,16 @@ class OpenApiRecordsBuilder
|
||||
'integer' => ['type' => 'integer', 'format' => 'int32'],
|
||||
'bigint' => ['type' => 'integer', 'format' => 'int64'],
|
||||
'varchar' => ['type' => 'string'],
|
||||
'clob' => ['type' => 'string', 'format' => 'large-string'], //custom format
|
||||
'clob' => ['type' => 'string'],
|
||||
'varbinary' => ['type' => 'string', 'format' => 'byte'],
|
||||
'blob' => ['type' => 'string', 'format' => 'large-byte'], //custom format
|
||||
'decimal' => ['type' => 'string', 'format' => 'decimal'], //custom format
|
||||
'blob' => ['type' => 'string', 'format' => 'byte'],
|
||||
'decimal' => ['type' => 'string'],
|
||||
'float' => ['type' => 'number', 'format' => 'float'],
|
||||
'double' => ['type' => 'number', 'format' => 'double'],
|
||||
'date' => ['type' => 'string', 'format' => 'date'],
|
||||
'time' => ['type' => 'string', 'format' => 'time'], //custom format
|
||||
'time' => ['type' => 'string', 'format' => 'date-time'],
|
||||
'timestamp' => ['type' => 'string', 'format' => 'date-time'],
|
||||
'geometry' => ['type' => 'string', 'format' => 'geometry'], //custom format
|
||||
'geometry' => ['type' => 'string'],
|
||||
'boolean' => ['type' => 'boolean'],
|
||||
];
|
||||
|
||||
@@ -169,49 +168,6 @@ class OpenApiRecordsBuilder
|
||||
}
|
||||
}
|
||||
|
||||
private function getPattern(ReflectedColumn $column): string
|
||||
{
|
||||
switch ($column->getType()) {
|
||||
case 'integer':
|
||||
$n = strlen(pow(2, 31));
|
||||
return '^-?[0-9]{1,' . $n . '}$';
|
||||
case 'bigint':
|
||||
$n = strlen(pow(2, 63));
|
||||
return '^-?[0-9]{1,' . $n . '}$';
|
||||
case 'varchar':
|
||||
$l = $column->getLength();
|
||||
return '^.{0,' . $l . '}$';
|
||||
case 'clob':
|
||||
return '^.*$';
|
||||
case 'varbinary':
|
||||
$l = $column->getLength();
|
||||
$b = (int) 4 * ceil($l / 3);
|
||||
return '^[A-Za-z0-9+/]{0,' . $b . '}=*$';
|
||||
case 'blob':
|
||||
return '^[A-Za-z0-9+/]*=*$';
|
||||
case 'decimal':
|
||||
$p = $column->getPrecision();
|
||||
$s = $column->getScale();
|
||||
return '^-?[0-9]{1,' . ($p - $s) . '}(\.[0-9]{1,' . $s . '})?$';
|
||||
case 'float':
|
||||
return '^-?[0-9]+(\.[0-9]+)?([eE]-?[0-9]+)?$';
|
||||
case 'double':
|
||||
return '^-?[0-9]+(\.[0-9]+)?([eE]-?[0-9]+)?$';
|
||||
case 'date':
|
||||
return '^[0-9]{4}-[0-9]{2}-[0-9]{2}$';
|
||||
case 'time':
|
||||
return '^[0-9]{2}:[0-9]{2}:[0-9]{2}$';
|
||||
case 'timestamp':
|
||||
return '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$';
|
||||
return '';
|
||||
case 'geometry':
|
||||
return '^(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON)\s*\(.*$';
|
||||
case 'boolean':
|
||||
return '^(true|false)$';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private function setComponentSchema(string $tableName, array $references) /*: void*/
|
||||
{
|
||||
$table = $this->reflection->getTable($tableName);
|
||||
@@ -222,10 +178,7 @@ class OpenApiRecordsBuilder
|
||||
if (!$pkName && $operation != 'list') {
|
||||
continue;
|
||||
}
|
||||
if ($type == 'view' && !in_array($operation, array('read', 'list'))) {
|
||||
continue;
|
||||
}
|
||||
if ($type == 'view' && !$pkName && $operation == 'read') {
|
||||
if ($type != 'table' && $operation != 'list') {
|
||||
continue;
|
||||
}
|
||||
if ($operation == 'delete') {
|
||||
@@ -250,13 +203,8 @@ class OpenApiRecordsBuilder
|
||||
}
|
||||
$column = $table->getColumn($columnName);
|
||||
$properties = $this->types[$column->getType()];
|
||||
$properties['maxLength'] = $column->hasLength() ? $column->getLength() : 0;
|
||||
$properties['nullable'] = $column->getNullable();
|
||||
$properties['pattern'] = $this->getPattern($column);
|
||||
foreach ($properties as $key => $value) {
|
||||
if ($value) {
|
||||
$this->openapi->set("$prefix|properties|$columnName|$key", $value);
|
||||
}
|
||||
$this->openapi->set("$prefix|properties|$columnName|$key", $value);
|
||||
}
|
||||
if ($column->getPk()) {
|
||||
$this->openapi->set("$prefix|properties|$columnName|x-primary-key", true);
|
||||
|
||||
@@ -31,8 +31,6 @@ class ErrorCode
|
||||
const BAD_OR_MISSING_XSRF_TOKEN = 1017;
|
||||
const ONLY_AJAX_REQUESTS_ALLOWED = 1018;
|
||||
const PAGINATION_FORBIDDEN = 1019;
|
||||
const USER_ALREADY_EXIST = 1020;
|
||||
const PASSWORD_TOO_SHORT = 1021;
|
||||
|
||||
private $values = [
|
||||
9999 => ["%s", ResponseFactory::INTERNAL_SERVER_ERROR],
|
||||
@@ -56,8 +54,6 @@ class ErrorCode
|
||||
1017 => ["Bad or missing XSRF token", ResponseFactory::FORBIDDEN],
|
||||
1018 => ["Only AJAX requests allowed for '%s'", ResponseFactory::FORBIDDEN],
|
||||
1019 => ["Pagination forbidden", ResponseFactory::FORBIDDEN],
|
||||
1020 => ["User '%s' already exists", ResponseFactory::CONFLICT],
|
||||
1021 => ["Password too short (<%d characters)", ResponseFactory::UNPROCESSABLE_ENTITY],
|
||||
];
|
||||
|
||||
public function __construct(int $code)
|
||||
|
||||
@@ -10,19 +10,27 @@ use Tqdev\PhpCrudApi\Record\Condition\OrCondition;
|
||||
|
||||
class FilterInfo
|
||||
{
|
||||
private function addConditionFromFilterPath(PathTree $conditions, array $path, ReflectedTable $table, array $params)
|
||||
{
|
||||
$key = 'filter' . implode('', $path);
|
||||
if (isset($params[$key])) {
|
||||
foreach ($params[$key] as $filter) {
|
||||
$condition = Condition::fromString($table, $filter);
|
||||
if (($condition instanceof NoCondition) == false) {
|
||||
$conditions->put($path, $condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getConditionsAsPathTree(ReflectedTable $table, array $params): PathTree
|
||||
{
|
||||
$conditions = new PathTree();
|
||||
foreach ($params as $key => $filters) {
|
||||
if (substr($key, 0, 6) == 'filter') {
|
||||
preg_match_all('/\d+|\D+/', substr($key, 6), $matches);
|
||||
$path = $matches[0];
|
||||
foreach ($filters as $filter) {
|
||||
$condition = Condition::fromString($table, $filter);
|
||||
if (($condition instanceof NoCondition) == false) {
|
||||
$conditions->put($path, $condition);
|
||||
}
|
||||
}
|
||||
$this->addConditionFromFilterPath($conditions, [], $table, $params);
|
||||
for ($n = ord('0'); $n <= ord('9'); $n++) {
|
||||
$this->addConditionFromFilterPath($conditions, [chr($n)], $table, $params);
|
||||
for ($l = ord('a'); $l <= ord('f'); $l++) {
|
||||
$this->addConditionFromFilterPath($conditions, [chr($n), chr($l)], $table, $params);
|
||||
}
|
||||
}
|
||||
return $conditions;
|
||||
|
||||
@@ -62,9 +62,6 @@ class RelationJoiner
|
||||
foreach ($params['join'] as $tableNames) {
|
||||
$path = array();
|
||||
foreach (explode(',', $tableNames) as $tableName) {
|
||||
if (!$this->reflection->hasTable($tableName)) {
|
||||
continue;
|
||||
}
|
||||
$t = $this->reflection->getTable($tableName);
|
||||
if ($t != null) {
|
||||
$path[] = $t->getName();
|
||||
|
||||
@@ -23,8 +23,7 @@ class RequestUtils
|
||||
{
|
||||
$params = array();
|
||||
$query = $request->getUri()->getQuery();
|
||||
//$query = str_replace('][]=', ']=', str_replace('=', '[]=', $query));
|
||||
$query = str_replace('%5D%5B%5D=', '%5D=', str_replace('=', '%5B%5D=', $query));
|
||||
$query = str_replace('][]=', ']=', str_replace('=', '[]=', $query));
|
||||
parse_str($query, $params);
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ use Psr\Http\Message\ResponseInterface;
|
||||
class ResponseFactory
|
||||
{
|
||||
const OK = 200;
|
||||
const MOVED_PERMANENTLY = 301;
|
||||
const FOUND = 302;
|
||||
const UNAUTHORIZED = 401;
|
||||
const FORBIDDEN = 403;
|
||||
const NOT_FOUND = 404;
|
||||
@@ -18,14 +16,10 @@ class ResponseFactory
|
||||
const UNPROCESSABLE_ENTITY = 422;
|
||||
const INTERNAL_SERVER_ERROR = 500;
|
||||
|
||||
public static function fromXml(int $status, string $xml): ResponseInterface
|
||||
{
|
||||
return self::from($status, 'text/xml', $xml);
|
||||
}
|
||||
|
||||
public static function fromCsv(int $status, string $csv): ResponseInterface
|
||||
{
|
||||
return self::from($status, 'text/csv', $csv);
|
||||
$response = self::from($status, 'text/csv', $csv);
|
||||
return $response->withHeader('Content-Type', 'text/csv');
|
||||
}
|
||||
|
||||
public static function fromHtml(int $status, string $html): ResponseInterface
|
||||
@@ -39,14 +33,14 @@ class ResponseFactory
|
||||
return self::from($status, 'application/json', $content);
|
||||
}
|
||||
|
||||
public static function from(int $status, string $contentType, string $content): ResponseInterface
|
||||
private static function from(int $status, string $contentType, string $content): ResponseInterface
|
||||
{
|
||||
$psr17Factory = new Psr17Factory();
|
||||
$response = $psr17Factory->createResponse($status);
|
||||
$stream = $psr17Factory->createStream($content);
|
||||
$stream->rewind();
|
||||
$response = $response->withBody($stream);
|
||||
$response = $response->withHeader('Content-Type', $contentType . '; charset=utf-8');
|
||||
$response = $response->withHeader('Content-Type', $contentType);
|
||||
$response = $response->withHeader('Content-Length', strlen($content));
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -10,13 +10,9 @@ use Tqdev\PhpCrudApi\ResponseUtils;
|
||||
require '../vendor/autoload.php';
|
||||
|
||||
$config = new Config([
|
||||
// 'driver' => 'mysql',
|
||||
// 'address' => 'localhost',
|
||||
// 'port' => '3306',
|
||||
'username' => 'php-crud-api',
|
||||
'password' => 'php-crud-api',
|
||||
'database' => 'php-crud-api',
|
||||
// 'debug' => false
|
||||
'database' => 'php-crud-api'
|
||||
]);
|
||||
$request = RequestFactory::fromGlobals();
|
||||
$api = new Api($config);
|
||||
|
||||
Reference in New Issue
Block a user