Limpieza de objetos externos

This commit is contained in:
2022-08-08 22:36:04 -04:00
parent a9968dec58
commit cdb4b382b7
57 changed files with 1210 additions and 339 deletions

View File

@ -0,0 +1,16 @@
<?php
namespace ProVM\Alias\Controller;
use Psr\Http\Message\ResponseInterface;
use function Safe\json_encode;
trait Json
{
public function withJson(ResponseInterface $response, $json_data, int $status_code = 200): ResponseInterface
{
$response->getBody()->write(json_encode($json_data));
return $response
->withStatus($status_code)
->withHeader('Content-Type', 'application/json');
}
}

View File

@ -1,7 +1,9 @@
<?php
namespace Common\Alias;
namespace ProVM\Alias;
use Common\Concept\Database as DatabaseInterface;
use Psr\Database\DatabaseInterface;
use Exception;
use PDO, PDOException, PDOStatement;
abstract class Database implements DatabaseInterface
{
@ -24,7 +26,7 @@ abstract class Database implements DatabaseInterface
return $this->port;
}
protected string $name;
public function setName(string $database_name): Database
public function setName(string $database_name): DatabaseInterface
{
$this->name = $database_name;
return $this;
@ -35,7 +37,7 @@ abstract class Database implements DatabaseInterface
}
protected string $username;
protected string $password;
public function setUser(string $username, string $password): Database
public function setUser(string $username, string $password): DatabaseInterface
{
$this->username = $username;
$this->password = $password;
@ -50,17 +52,17 @@ abstract class Database implements DatabaseInterface
return $this->password;
}
protected \PDO $connection;
public function connect(): Database
protected PDO $connection;
public function connect(): DatabaseInterface
{
if ($this->needsUser()) {
$this->connection = new \PDO($this->getDsn(), $this->getUser(), $this->getPassword());
$this->connection = new PDO($this->getDsn(), $this->getUser(), $this->getPassword());
return $this;
}
$this->connection = new \PDO($this->getDsn());
$this->connection = new PDO($this->getDsn());
return $this;
}
public function getConnection(): \PDO
public function getConnection(): PDO
{
if (!isset($this->connection)) {
return $this->connect()->connection;
@ -71,38 +73,38 @@ abstract class Database implements DatabaseInterface
{
$st = $this->getConnection()->query($query);
if (!$st) {
throw new \PDOException("Could not retrieve anything with '{$query}'.");
throw new PDOException("Could not retrieve anything with '{$query}'.");
}
$results = $st->fetchAll(\PDO::FETCH_ASSOC);
$results = $st->fetchAll(PDO::FETCH_ASSOC);
if (!$results) {
throw new \PDOException('Could not retrieve any results.');
throw new PDOException('Could not retrieve any results.');
}
return $results;
}
public function beginTransaction(): void
{
if (!$this->getConnection()->beginTransaction()) {
throw new \PDOException('Could not begin transaction.');
throw new PDOException('Could not begin transaction.');
}
}
public function commit(): void
{
if (!$this->getConnection()->commit()) {
throw new \PDOException('Could not commit');
throw new PDOException('Could not commit');
}
}
public function rollBack(): void
{
if (!$this->getConnection()->rollBack()) {
throw new \PDOException('Could not rollback.');
throw new PDOException('Could not rollback.');
}
}
protected \PDOStatement $prepared_statement;
public function prepare(string $query): Database
protected PDOStatement $prepared_statement;
public function prepare(string $query): DatabaseInterface
{
$st = $this->getConnection()->prepare($query);
if (!$st) {
throw new \PDOException("Could not prepare query '{$query}'.");
throw new PDOException("Could not prepare query '{$query}'.");
}
$this->prepared_statement = $st;
return $this;
@ -110,42 +112,42 @@ abstract class Database implements DatabaseInterface
public function execute(array $data): array
{
if (!isset($this->prepared_statement)) {
throw new \Exception('No prepared statement.');
throw new Exception('No prepared statement.');
}
if (!$this->prepared_statement->execute($data)) {
throw new \PDOException('Could not execute prepared statement.');
throw new PDOException('Could not execute prepared statement.');
}
$results = $this->prepared_statement->fetchAll(\PDO::FETCH_ASSOC);
$results = $this->prepared_statement->fetchAll(PDO::FETCH_ASSOC);
if (!$results) {
throw new \PDOException('Could not retrieve any results.');
throw new PDOException('Could not retrieve any results.');
}
return $results;
}
public function insert(array $values): void
{
if (!isset($this->prepared_statement)) {
throw new \Exception('No prepared statement.');
throw new Exception('No prepared statement.');
}
if (!$this->prepared_statement->execute($values)) {
throw new \PDOException('Could not insert.');
throw new PDOException('Could not insert.');
}
}
public function update(array $data): void
{
if (!isset($this->prepared_statement)) {
throw new \Exception('No prepared statement.');
throw new Exception('No prepared statement.');
}
if (!$this->prepared_statement->execute($data)) {
throw new \PDOException('Could not update.');
throw new PDOException('Could not update.');
}
}
public function delete(array $data): void
{
if (!isset($this->prepared_statement)) {
throw new \Exception('No prepared statement.');
throw new Exception('No prepared statement.');
}
if (!$this->prepared_statement->execute($data)) {
throw new \PDOException('Could not delete.');
throw new PDOException('Could not delete.');
}
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace ProVM\Alias\Database;
use ProVM\Concept\Database\Query as QueryInterface;
abstract class Query implements QueryInterface
{
public function __toString(): string
{
return $this->build();
}
}

View File

@ -1,10 +1,10 @@
<?php
namespace Common\Alias\Factory;
namespace ProVM\Alias\Factory;
use Common\Concept\Factory\Model as FactoryInterface;
use Common\Concept\Model as ModelInterface;
use Common\Concept\Repository;
use Psr\Container\ContainerInterface;
use ProVM\Concept\Factory\Model as FactoryInterface;
use ProVM\Concept\Model as ModelInterface;
use ProVM\Concept\Repository;
abstract class Model implements FactoryInterface
{

View File

@ -1,8 +1,8 @@
<?php
namespace Common\Alias;
namespace ProVM\Alias;
use function Safe\{fopen,fclose,fwrite};
use Common\Concept\File as FileInterface;
use ProVM\Concept\File as FileInterface;
abstract class File implements FileInterface
{

View File

@ -1,8 +1,8 @@
<?php
namespace Common\Alias;
namespace ProVM\Alias;
use function Safe\{touch,mkdir,unlink};
use Common\Concept\Filesystem as FilesystemInterface;
use ProVM\Concept\Filesystem as FilesystemInterface;
abstract class Filesystem implements FilesystemInterface
{

77
api/ProVM/Alias/Model.php Normal file
View File

@ -0,0 +1,77 @@
<?php
namespace ProVM\Alias;
use ReflectionObject;
use ProVM\Concept\Model as ModelInterface;
abstract class Model implements ModelInterface
{
protected bool $new = false;
public function setNew(): ModelInterface
{
$this->new = true;
return $this;
}
public function isNew(): bool
{
return $this->new;
}
protected array $edited;
public function setEdited(string $property): ModelInterface
{
$this->edited []= $property;
return $this;
}
public function getEdited(): array
{
return $this->edited;
}
public function isDirty(): bool
{
return isset($this->edited) and count($this->edited) > 0;
}
public function edit(array $data): void
{
$this->edited = [];
foreach ($data as $key => $val) {
if ($key === 'id') {
continue;
}
$getter = Model::parseGetter($key);
$setter = Model::parseSetter($key);
if (method_exists($this, $getter) and $this->{$getter}() !== $val) {
$this->{$setter}($val);
$this->setEdited($key);
}
}
}
public static function parseSetter(string $property): string
{
return 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
}
public static function parseGetter(string $property): string
{
return 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
}
public function jsonSerialize(): mixed
{
$ref = new ReflectionObject($this);
$properties = $ref->getProperties();
$output = [];
foreach ($properties as $property) {
if (get_called_class() !== $property->getDeclaringClass()->getName()) {
continue;
}
$key = $property->getName();
$method = Model::parseGetter($key);
if (method_exists($this, $method)) {
$output[$key] = $this->{$method}();
}
}
return $output;
}
}

View File

@ -0,0 +1,119 @@
<?php
namespace ProVM\Alias;
use Psr\Database\DatabaseInterface;
use Error;
use function Safe\error_log;
use ProVM\Concept\Model;
use ProVM\Concept\Repository as RepositoryInterface;
use ProVM\Implement\Database\QueryBuilder;
abstract class Repository implements RepositoryInterface
{
public function __construct(DatabaseInterface $database, QueryBuilder $builder)
{
$this->setDatabase($database);
$this->setQueryBuilder($builder);
}
protected QueryBuilder $query;
public function setQueryBuilder(QueryBuilder $builder): RepositoryInterface
{
$this->query = $builder;
return $this;
}
public function getQueryBuilder(): QueryBuilder
{
return $this->query;
}
protected string $table;
public function setTable(string $table): RepositoryInterface
{
$this->table = $table;
return $this;
}
public function getTable(): string
{
return $this->table;
}
protected array $properties;
public function addProperty(string $name): RepositoryInterface
{
$this->properties []= $name;
return $this;
}
public function setProperties(array $properties): RepositoryInterface
{
foreach ($properties as $property) {
$this->addProperty($property);
}
return $this;
}
public function getProperties(): array
{
return $this->properties;
}
protected DatabaseInterface $database;
public function setDatabase(DatabaseInterface $database): RepositoryInterface
{
$this->database = $database;
return $this;
}
public function getDatabase(): DatabaseInterface
{
return $this->database;
}
public function fetchAll(): array
{
$query = $this->getQueryBuilder()->select()->from($this->getTable())->build();
return array_map([$this, 'load'], $this->getDatabase()->query($query));
}
protected function extractProperties(Model $model): array
{
$properties = [];
foreach ($this->getProperties() as $p) {
$method = $model::parseGetter($p);
if (method_exists($model, $method)) {
try {
$properties[$p] = $model->{$method}();
} catch (Error $e) {
error_log($e);
}
}
}
return $properties;
}
public function save(Model $model): void
{
if ($model->isNew()) {
$this->saveNew($model);
return;
}
if ($model->isDirty()) {
$this->saveUpdate($model);
}
}
protected function saveNew(Model $model): void
{
$properties = $this->extractProperties($model);
$columns = array_keys($properties);
$ivalues = array_map(function($item) {return '?';}, array_keys($properties));
$values = array_values($properties);
$query = $this->getQueryBuilder()->insert()->into($this->getTable())->columns($columns)->values($ivalues)->build();
$this->getDatabase()->prepare($query)->insert($values);
}
protected function saveUpdate(Model $model): void
{
$edited = $model->getEdited();
$properties = array_intersect_key($this->extractProperties($model), array_combine($edited, $edited));
$sets = array_map(function($item) {
return "{$item} = ?";
}, array_keys($properties));
$values = array_values($properties);
$query = $this->getQueryBuilder()->update()->table($this->getTable())->set($sets)->where('id = ?')->build();
$this->getDatabase()->prepare($query)->update(array_merge($values, [$model->getId()]));
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace ProVM\Concept\Database;
interface Query
{
public function build(): string;
}

View File

@ -0,0 +1,10 @@
<?php
namespace ProVM\Concept\Factory;
use ProVM\Concept\Model as ModelInterface;
use ProVM\Concept\Repository;
interface Model
{
public function find(ModelInterface $model_name): Repository;
}

View File

@ -1,5 +1,5 @@
<?php
namespace Common\Concept;
namespace ProVM\Concept;
interface File
{

View File

@ -1,5 +1,5 @@
<?php
namespace Common\Concept;
namespace ProVM\Concept;
interface Filesystem
{

View File

@ -0,0 +1,16 @@
<?php
namespace ProVM\Concept;
use JsonSerializable;
interface Model extends JsonSerializable
{
public function setNew(): Model;
public function isNew(): bool;
public function setEdited(string $property): Model;
public function getEdited(): array;
public function isDirty(): bool;
public function edit(array $data): void;
public static function parseSetter(string $property): string;
public static function parseGetter(string $property): string;
}

View File

@ -1,11 +1,12 @@
<?php
namespace Common\Concept;
namespace ProVM\Concept;
interface Repository
{
public function fetchAll(): array;
public function fetchByKey($key): Model;
public function fetchById(int $id): Model;
public function load(array $data_row): Model;
public function create(array $data): Model;
public function save(Model $model): void;
public function delete(Model $model): void;
}

View File

@ -0,0 +1,117 @@
<?php
namespace ProVM\Implement;
use Psr\Collection\CollectionInterface;
class Collection implements CollectionInterface
{
public function __construct()
{
$this->set(CollectionInterface::class, $this);
}
protected array $data;
public function set(mixed $name, $value): CollectionInterface
{
$this->data[$name] = $value;
return $this;
}
public function has(mixed $name): bool
{
return isset($this->data[$name]);
}
public function get(mixed $name)
{
return $this->process($this->data[$name]) ?? null;
}
public function remove(mixed $name): CollectionInterface
{
unset($this->data[$name]);
return $this;
}
protected function process($value)
{
if (is_callable($value)) {
return $this->processCallable($value);
}
if (is_array($value)) {
return $this->processArray($value);
}
return $value;
}
protected function processCallable(Callable $value)
{
$ref = new \ReflectionFunction($value);
$param_array = [];
$params = $ref->getParameters();
foreach ($params as $p) {
$param_array[$p->getName()] = $this->get($p->getType()->getName());
}
return $ref->invokeArgs($param_array);
}
protected function processArray(array $value)
{
return $value;
}
public static function fromArray(array $source): CollectionInterface
{
$obj = new Collection();
foreach ($source as $key => $value) {
$obj->set($key, $value);
}
return $obj;
}
public static function fromObject(object $source): CollectionInterface
{
return Collection::fromArray((array) $source);
}
public function __get(mixed $name): mixed
{
return $this->get($name);
}
public function current(): mixed
{
return $this->data[$this->key()];
}
public function next(): void
{
next($this->data);
}
public function key(): mixed
{
return key($this->data);
}
public function valid(): bool
{
return isset($this->data);
}
public function rewind(): void
{
reset($this->data);
}
public function offsetExists(mixed $offset): bool
{
return $this->has($offset);
}
public function offsetGet(mixed $offset): mixed
{
return $this->get($offset);
}
public function offsetSet(mixed $offset, mixed $value): void
{
$this->set($offset, $value);
}
public function offsetUnset(mixed $offset): void
{
$this->remove($offset);
}
public function count(): int
{
return count($this->data);
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace Contabilidad\Implement\Database;
namespace ProVM\Implement\Database;
use Common\Alias\Database;

View File

@ -0,0 +1,47 @@
<?php
namespace ProVM\Implement\Database\Query\MySQL;
use Common\Alias\Database\Query;
class Delete extends Query
{
protected string $table;
public function from($table): Delete
{
$this->table = $table;
return $this;
}
public function getFrom(): string
{
return $this->table;
}
protected array $conditions;
public function where(array|string $conditions): Delete
{
if (is_string($conditions)) {
return $this->addCondition($conditions);
}
foreach ($conditions as $condition) {
$this->addCondition($condition);
}
return $this;
}
public function addCondition(string $condition): Delete
{
$this->conditions []= $condition;
return $this;
}
public function getWhere(): string
{
return implode(' AND ', $this->conditions);
}
public function build(): string
{
$output = ['DELETE FROM'];
$output []= $this->getFrom();
$output []= "WHERE {$this->getWhere()}";
return implode(' ', $output);
}
}

View File

@ -0,0 +1,103 @@
<?php
namespace ProVM\Implement\Database\Query\MySQL;
use Common\Alias\Database\Query;
class Insert extends Query
{
protected string $table;
public function into(string $table): Insert
{
$this->table = $table;
return $this;
}
public function getTable(): string
{
return $this->table;
}
protected Select $select;
public function select(Select $select): Insert
{
$this->select = $select;
return $this;
}
public function getSelect(): string
{
return $this->select->build();
}
protected array $columns;
public function columns(array $columns): Insert
{
foreach ($columns as $column) {
$this->addColumn($column);
}
return $this;
}
public function addColumn(string $column): Insert
{
$this->columns []= $column;
return $this;
}
public function getColumns(): string
{
return implode(', ', array_map(function($item) {return "`{$item}`";}, $this->columns));
}
protected array $values;
public function values(array $values): Insert
{
foreach ($values as $value) {
$this->addValue($value);
}
return $this;
}
public function addValue(string $value): Insert
{
$this->values []= $value;
return $this;
}
public function getValues(): string
{
return implode(', ', array_map(function($item) {
return $item;
}, $this->values));
}
protected array $updates;
public function update(array $updates): Insert
{
foreach ($updates as $update) {
$this->addUpdate($update);
}
return $this;
}
public function addUpdate(string $update_string): Insert
{
$this->updates []= $update_string;
return $this;
}
public function getUpdates(): string
{
return implode(', ', $this->updates);
}
public function build(): string
{
$output = ['INSERT INTO'];
$output []= $this->getTable();
try {
$output []= "({$this->getColumns()})";
} catch (\Error $e) {
\Safe\error_log($e);
}
try {
$output []= $this->getSelect();
} catch (\Error $e) {
$output []= "VALUES ({$this->getValues()})";
}
try {
$output []= "ON DUPLICATE KEY UPDATE {$this->getUpdates()}";
} catch (\Error $e) {
\Safe\error_log($e);
}
return implode(' ', $output);
}
}

View File

@ -0,0 +1,210 @@
<?php
namespace ProVM\Implement\Database\Query\MySQL;
use Common\Alias\Database\Query;
class Select extends Query
{
protected array $columns;
public function select(array|string $columns): Select
{
if (is_string($columns)) {
return $this->addColumn($columns);
}
foreach ($columns as $column) {
$this->addColumn($column);
}
return $this;
}
public function addColumn(string $column): Select
{
$this->columns []= $column;
return $this;
}
public function getSelect(): string
{
return implode(', ', array_map(function($item) {
return "`{$item}`";
}, $this->columns));
}
protected string $from;
public function from(string $table): Select
{
$this->from = $table;
return $this;
}
public function getFrom(): string
{
return $this->from;
}
protected array $joins;
public function join(array $joins): Select
{
if (is_string($joins[0])) {
return $this->addJoin($joins[0], $joins[1], $joins[2]);
}
foreach ($joins as $join) {
$this->addJoin($join[0], $join[1], $join[2]);
}
return $this;
}
public function addJoin(string $join_type, string $join_table, string $conditions): Select
{
$join_type = strtoupper($join_type);
$this->joins []= "{$join_type} {$join_table} {$conditions}";
return $this;
}
public function getJoins(): string
{
return implode('', $this->joins);
}
protected array $conditions;
public function where(array|string $conditions): Select
{
if (is_string($conditions)) {
return $this->addWhere($conditions);
}
foreach ($conditions as $condition) {
$this->addWhere($condition);
}
return $this;
}
public function addWhere($condition): Select
{
$this->conditions []= $condition;
return $this;
}
public function getWheres(): string
{
return implode(' AND ', $this->conditions);
}
protected array $orders;
public function order(array|string $orders): Select
{
if (is_string($orders)) {
return $this->addOrder($orders);
}
foreach ($orders as $order) {
if (is_array($order)) {
$this->addOrder($order[0], $order[1]);
continue;
}
$this->addOrder($order);
}
return $this;
}
public function addOrder(string $order, string $dir = 'desc'): Select
{
$dir = strtoupper($dir);
$this->orders []= "{$order} {$dir}";
return $this;
}
public function getOrders(): string
{
return implode(', ', $this->orders);
}
protected array $groups;
public function group(array|string $groups): Select
{
if (is_string($groups)) {
return $this->addGroup($groups);
}
foreach ($groups as $group) {
$this->addGroup($group);
}
return $this;
}
public function addGroup(string $group): Select
{
$this->groups []= $group;
return $this;
}
public function getGroups(): string
{
return implode(', ', $this->groups);
}
protected array $having;
public function having(array|string $havings): Select
{
if (is_string($havings)) {
return $this->addHaving($havings);
}
foreach ($havings as $having) {
$this->addHaving($having);
}
return $this;
}
public function addHaving(string $having): Select
{
$this->having []= $having;
return $this;
}
public function getHaving(): string
{
return implode(' AND ', $this->having);
}
protected int $limit;
public function limit(int $limit): Select
{
$this->limit = $limit;
return $this;
}
protected int $offset;
public function offset(int $offset): Select
{
$this->offset = $offset;
return $this;
}
public function getLimit(): string
{
$output = [$this->limit];
if (isset($this->offset)) {
$output []= 'OFFSET';
$output []= $this->offset;
}
return implode(' ', $output);
}
public function build(): string
{
$output = ['SELECT'];
try {
$output []= $this->getSelect();
} catch (\Error $e) {
$output []= '*';
}
$output []= 'FROM';
$output []= $this->getFrom();
try {
$output [] = $this->getJoins();
} catch (\Error $e) {
\Safe\error_log($e);
}
try {
$output []= "WHERE {$this->getWheres()}";
} catch (\Error $e) {
\Safe\error_log($e);
}
try {
$output []= "GROUP BY {$this->getGroups()}";
} catch (\Error $e) {
\Safe\error_log($e);
}
try {
$output []= "HAVING {$this->getHaving()}";
} catch (\Error $e) {
\Safe\error_log($e);
}
try {
$output []= "ORDER BY {$this->getOrders()}";
} catch (\Error $e) {
\Safe\error_log($e);
}
try {
$output []= "LIMIT {$this->getLimit()}";
} catch (\Error $e) {
\Safe\error_log($e);
}
return implode(' ', $output);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace ProVM\Implement\Database\Query\MySQL;
use Common\Alias\Database\Query;
class Update extends Query
{
protected string $table;
public function table(string $table): Update
{
$this->table = $table;
return $this;
}
public function getTable(): string
{
return $this->table;
}
protected array $sets;
public function set(array|string $set_strings): Update
{
if (is_string($set_strings)) {
return $this->addSet($set_strings);
}
foreach ($set_strings as $set_string) {
$this->addSet($set_string);
}
return $this;
}
public function addSet(string $set_string): Update
{
$this->sets []= $set_string;
return $this;
}
public function getSets(): string
{
return implode(', ', $this->sets);
}
protected array $conditions;
public function where(array|string $conditions): Update
{
if (is_string($conditions)) {
return $this->addCondition($conditions);
}
foreach ($conditions as $condition) {
$this->addCondition($condition);
}
return $this;
}
public function addCondition(string $condition): Update
{
$this->conditions []= $condition;
return $this;
}
public function getWhere(): string
{
return implode(' AND ', $this->conditions);
}
public function build(): string
{
$output = ['UPDATE'];
$output []= $this->getTable();
$output []= "SET {$this->getSets()}";
$output []= "WHERE {$this->getWhere()}";
return implode(' ', $output);
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace ProVM\Implement\Database;
use Common\Concept\Database\Query;
use Contabilidad\Implement\Database\Query\MySQL\Delete;
use Contabilidad\Implement\Database\Query\MySQL\Insert;
use Contabilidad\Implement\Database\Query\MySQL\Select;
use Contabilidad\Implement\Database\Query\MySQL\Update;
use Psr\Container\ContainerInterface;
class QueryBuilder
{
public function __construct(ContainerInterface $container)
{
$arr = [
'select',
'insert',
'update',
'delete'
];
foreach ($arr as $b) {
$builder = implode("\\", [
'Contabilidad',
'Implement',
'Database',
'Query',
'MySQL',
ucwords($b)
]);
$this->addBuilder($container->get($builder));
}
}
protected array $builders;
public function addBuilder(Query $builder): QueryBuilder
{
$name = explode("\\", get_class($builder));
$name = array_pop($name);
$this->builders[strtolower($name)] = $builder;
return $this;
}
public function start(string $name): Query
{
return $this->builders[strtolower($name)];
}
public function select(): Select
{
return $this->start('select');
}
public function insert(): Insert
{
return $this->start('insert');
}
public function update(): Update
{
return $this->start('update');
}
public function delete(): Delete
{
return $this->start('delete');
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace Contabilidad\Implement;
namespace ProVM\Implement;
use Common\Alias\File as BaseFile;

View File

@ -1,5 +1,5 @@
<?php
namespace Contabilidad\Implement;
namespace ProVM\Implement;
use Common\Concept\File as FileInterface;
use Common\Alias\Filesystem as BaseFilesystem;

View File

@ -0,0 +1,14 @@
<?php
namespace Psr\Collection;
use Iterator, ArrayAccess, Countable;
interface CollectionInterface extends Iterator, ArrayAccess, Countable
{
public function set(mixed $name, $value): CollectionInterface;
public function has(mixed $name): bool;
public function get(mixed $name);
public function remove(mixed $name): CollectionInterface;
public static function fromArray(array $source): CollectionInterface;
public static function fromObject(object $source): CollectionInterface;
}

View File

@ -1,25 +1,27 @@
<?php
namespace Common\Concept;
namespace Psr\Database;
interface Database
use PDO;
interface DatabaseInterface
{
public function setHost(string $host, ?int $port = null): Database;
public function setHost(string $host, ?int $port = null): DatabaseInterface;
public function getHost(): string;
public function getPort(): int;
public function setName(string $database_name): Database;
public function setName(string $database_name): DatabaseInterface;
public function getName(): string;
public function setUser(string $username, string $password): Database;
public function setUser(string $username, string $password): DatabaseInterface;
public function getUser(): string;
public function getPassword(): string;
public function getDsn(): string;
public function needsUser(): bool;
public function connect(): Database;
public function getConnection(): \PDO;
public function connect(): DatabaseInterface;
public function getConnection(): PDO;
public function beginTransaction(): void;
public function commit(): void;
public function rollBack(): void;
public function query(string $query): array;
public function prepare(string $query): Database;
public function prepare(string $query): DatabaseInterface;
public function execute(array $data): array;
public function insert(array $values): void;
public function update(array $data): void;

View File

@ -1,8 +0,0 @@
<?php
namespace Common\Alias;
use Common\Concept\Model as ModelInterface;
abstract class Model implements ModelInterface
{
}

View File

@ -1,62 +0,0 @@
<?php
namespace Common\Alias;
use Common\Concept\Database;
use Common\Concept\Model;
use Common\Concept\Repository as RepositoryInterface;
abstract class Repository implements RepositoryInterface
{
protected string $table;
public function setTable(string $table): RepositoryInterface
{
$this->table = $table;
return $this;
}
public function getTable(): string
{
return $this->table;
}
protected array $properties;
public function addProperty(string $name): RepositoryInterface
{
$this->properties []= $name;
return $this;
}
public function setProperties(array $properties): RepositoryInterface
{
foreach ($properties as $property) {
$this->addProperty($property);
}
return $this;
}
public function getProperties(): array
{
return $this->properties;
}
protected Database $database;
public function setDatabase(Database $database): RepositoryInterface
{
$this->database = $database;
return $this;
}
public function getDatabase(): Database
{
return $this->database;
}
public function fetchAll(): array
{
$query = "SELECT * FROM {$this->getTable()}";
return array_map([$this, 'load'], $this->getDatabase()->query($query));
}
public function save(Model $model): void
{
$columns = implode(', ', array_map(function($item) {return "'{$item}'";}, $this->getProperties()));
$values = implode(', ', array_map(function($item) {return '?';}, $this->getProperties()));
$query = "INSERT INTO {$this->getTable()} ({$columns}) VALUES ({$values})";
$values = array_map(function($item) use ($model) {$method = str_replace(' ', '', ucwords(str_replace('_', '', $item)));return $model->{"get{$method}"}();}, $this->getProperties());
$this->getDatabase()->prepare($query)->insert($values);
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace Common\Concept\Factory;
use Common\Concept\Model as ModelInterface;
use Common\Concept\Repository;
interface Model
{
public function find(ModelInterface $model_name): Repository;
}

View File

@ -1,6 +0,0 @@
<?php
namespace Common\Concept;
interface Model
{
}

View File

@ -0,0 +1,24 @@
<?php
namespace Common\Controller;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use function Safe\{json_decode, file_get_contents};
use ProVM\Alias\Controller\Json;
class Base
{
use Json;
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, ContainerInterface $container): ResponseInterface
{
$folder = $container->get('folders')->documentation;
$filename = implode(DIRECTORY_SEPARATOR, [
$folder,
'base.json'
]);
$documentation = json_decode(file_get_contents($filename));
return $this->withJson($response, $documentation);
}
}

View File

@ -0,0 +1,125 @@
<?php
namespace Common\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use PDOException;
use function Safe\{json_decode,error_log};
use ProVM\Alias\Controller\Json;
use Contabilidad\Repository\Cuenta;
class Cuentas
{
use Json;
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, Cuenta $repository): ResponseInterface
{
$cuentas = [];
try {
$cuentas = $repository->fetchAll();
} catch (PDOException $e) {
error_log($e);
}
return $this->withJson($response, compact('cuentas'));
}
public function get(ServerRequestInterface $request, ResponseInterface $response, Cuenta $repository, $cuenta_id): ResponseInterface
{
$cuenta = null;
try {
$cuenta = $repository->fetchById($cuenta_id);
} catch (PDOException $e) {
error_log($e);
}
return $this->withJson($response, compact('cuenta'));
}
public function add(ServerRequestInterface $request, ResponseInterface $response, Cuenta $repository): ResponseInterface
{
$body = $request->getBody();
$contents = $body->getContents();
$json = json_decode($contents);
if (!is_array($json)) {
$json = [$json];
}
$output = [
'input' => $json,
'cuentas' => []
];
foreach ($json as $data) {
$cuenta = $repository->create((array) $data);
$status = true;
$exists = true;
if ($cuenta->isNew()) {
$exists = false;
try {
$repository->save($cuenta);
} catch (PDOException $e) {
error_log($e);
$status = false;
}
}
$output['cuentas'] []= [
'cuenta' => $cuenta,
'exists' => $exists,
'added' => $status
];
}
return $this->withJson($response, $output);
}
public function edit(ServerRequestInterface $request, ResponseInterface $response, Cuenta $repository): ResponseInterface
{
$body = $request->getBody();
$contents = $body->getContents();
$json = json_decode($contents);
if (!is_array($json)) {
$json = [$json];
}
$output = [
'input' => $json,
'cuentas' => []
];
foreach ($json as $data) {
$cuenta = $repository->fetchById($data->id);
$old = clone $cuenta;
try {
$cuenta->edit((array) $data);
$status = $cuenta->isDirty();
if ($status) {
$repository->save($cuenta);
}
} catch (PDOException $e) {
error_log($e);
$status = false;
}
$output['cuentas'] []= [
'antes' => $old,
'cuenta' => $cuenta,
'edited' => $status
];
}
return $this->withJson($response, $output);
}
public function editOne(ServerRequestInterface $request, ResponseInterface $response, Cuenta $repository, $cuenta_id): ResponseInterface
{
$cuenta = $repository->fetchById($cuenta_id);
$body = $request->getBody();
$contents = $body->getContents();
$json = json_decode($contents, JSON_OBJECT_AS_ARRAY);
$output = [
'input' => $json,
'old' => clone $cuenta
];
try {
$cuenta->edit((array) $json);
$status = $cuenta->isDirty();
if ($status) {
$repository->save($cuenta);
}
} catch (PDOException $e) {
error_log($e);
$status = false;
}
$output['cuenta'] = $cuenta;
$output['edited'] = $status;
return $this->withJson($response, $output);
}
}

View File

@ -1,11 +1,11 @@
<?php
namespace Contabilidad\Common\Middleware;
namespace Common\Middleware;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as Handler;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ResponseFactoryInterface as Factory;
use Contabilidad\Common\Service\Auth as Service;
use Common\Service\Auth as Service;
class Auth {
protected Factory $factory;

View File

@ -1,5 +1,5 @@
<?php
namespace Contabilidad\Common\Service;
namespace Common\Service;
use Psr\Http\Message\ServerRequestInterface as Request;

View File

@ -28,6 +28,7 @@
"psr-4": {
"Common\\": "common/",
"Contabilidad\\": "src/",
"ProVM\\": "ProVM/",
"Psr\\": "Psr/"
}
},

View File

@ -0,0 +1,12 @@
{
"openapi": "3.0.0",
"info": {
"title": "Contabilidad",
"version": "1.0.0"
},
"paths": {
"/transactions": {
"description": "List transactions"
}
}
}

View File

@ -1,7 +1,4 @@
<?php
use Contabilidad\Common\Controller\Base;
use Common\Controller\Base;
$app->get('/key/generate[/]', [Base::class, 'generate_key']);
$app->get('/balance[/]', [Contabilidad\Common\Controller\TiposCategorias::class, 'balance']);
$app->get('/info', [Base::class, 'info']);
$app->get('/', Base::class);

View File

@ -1,13 +0,0 @@
<?php
use Contabilidad\Common\Controller\Categorias;
$app->group('/categorias', function($app) {
$app->post('/add[/]', [Categorias::class, 'add']);
$app->get('[/]', Categorias::class);
});
$app->group('/categoria/{categoria_id}', function($app) {
$app->get('/cuentas', [Categorias::class, 'cuentas']);
$app->put('/edit', [Categorias::class, 'edit']);
$app->delete('/delete', [Categorias::class, 'delete']);
$app->get('[/]', [Categorias::class, 'show']);
});

View File

@ -1,7 +0,0 @@
<?php
use Contabilidad\Common\Controller\Consolidados;
$app->group('/consolidar', function($app) {
$app->get('/update/{mes}/{cuenta_id}', [Consolidados::class, 'update']);
$app->get('[/]', [Consolidados::class, 'cli']);
});

View File

@ -1,20 +1,12 @@
<?php
use Contabilidad\Common\Controller\Cuentas;
use Common\Controller\Cuentas;
$app->group('/cuentas', function($app) {
$app->post('/add[/]', [Cuentas::class, 'add']);
$app->get('[/]', Cuentas::class);
$app->post('/add[/]', [Cuentas::class, 'add']);
$app->post('/edit[/]', [Cuentas::class, 'edit']);
$app->get('[/]', Cuentas::class);
});
$app->group('/cuenta/{cuenta_id}', function($app) {
$app->get('/entradas', [Cuentas::class, 'entradas']);
$app->group('/transacciones', function($app) {
$app->get('/amount', [Cuentas::class, 'transaccionesAmount']);
$app->get('/month/{month}', [Cuentas::class, 'transaccionesMonth']);
$app->get('/acum/{date}', [Cuentas::class, 'transaccionesAcumulation']);
$app->get('[/{limit:[0-9]+}[/{start:[0-9]+}]]', [Cuentas::class, 'transacciones']);
});
$app->get('/categoria', [Cuentas::class, 'categoria']);
$app->put('/edit', [Cuentas::class, 'edit']);
$app->delete('/delete', [Cuentas::class, 'delete']);
$app->get('[/]', [Cuentas::class, 'show']);
$app->post('/edit[/]', [Cuentas::class, 'editOne']);
$app->get('[/]', [Cuentas::class, 'get']);
});

View File

@ -1,5 +0,0 @@
<?php
use Contabilidad\Common\Controller\Import;
$app->post('/import', Import::class);
$app->get('/import/uploads', [Import::class, 'uploads']);

View File

@ -1,12 +0,0 @@
<?php
use Contabilidad\Common\Controller\Monedas;
$app->group('/monedas', function($app) {
$app->post('/add[/]', [Monedas::class, 'add']);
$app->get('[/]', Monedas::class);
});
$app->group('/moneda/{moneda_id}', function($app) {
$app->put('/edit', [Monedas::class, 'edit']);
$app->delete('/delete', [Monedas::class, 'delete']);
$app->get('[/]', [Monedas::class, 'show']);
});

View File

@ -1,8 +0,0 @@
<?php
use Contabilidad\Common\Controller\Queues;
$app->group('/queues', function($app) {
$app->get('/pending', [Queues::class, 'pending']);
$app->post('/processed', [Queues::class, 'processed']);
$app->get('[/]', Queues::class);
});

View File

@ -1,16 +0,0 @@
<?php
$folder = implode(DIRECTORY_SEPARATOR, [
__DIR__,
'tipos'
]);
if (file_exists($folder)) {
$app->group('/tipos', function($app) use ($folder) {
$files = new DirectoryIterator($folder);
foreach ($files as $file) {
if ($file->isDir() or $file->getExtension() != 'php') {
continue;
}
include_once $file->getRealPath();
}
});
}

View File

@ -1,13 +0,0 @@
<?php
use Contabilidad\Common\Controller\TiposCambios;
$app->group('/cambios', function($app) {
$app->post('/obtener[/]', [TiposCambios::class, 'obtain']);
$app->post('/add[/]', [TiposCambios::class, 'add']);
$app->get('[/]', TiposCambios::class);
});
$app->group('/cambio/{tipo_id}', function($app) {
$app->put('/edit[/]', [TiposCambios::class, 'edit']);
$app->delete('/delete[/]', [TiposCambios::class, 'delete']);
$app->get('[/]', [TiposCambios::class, 'show']);
});

View File

@ -1,13 +0,0 @@
<?php
use Contabilidad\Common\Controller\TiposCategorias;
$app->group('/categorias', function($app) {
$app->post('/add[/]', [TiposCategorias::class, 'add']);
$app->get('[/]', TiposCategorias::class);
});
$app->group('/categoria/{tipo_id}', function($app) {
$app->get('/categorias', [TiposCategorias::class, 'categorias']);
$app->put('/edit', [TiposCategorias::class, 'edit']);
$app->delete('/delete', [TiposCategorias::class, 'delete']);
$app->get('[/]', [TiposCategorias::class, 'show']);
});

View File

@ -1,12 +0,0 @@
<?php
use Contabilidad\Common\Controller\TiposCuentas;
$app->group('/cuentas', function($app) {
$app->post('/add[/]', [TiposCuentas::class, 'add']);
$app->get('[/]', TiposCuentas::class);
});
$app->group('/cuenta/{tipo_id}', function($app) {
$app->put('/edit', [TiposCuentas::class, 'edit']);
$app->delete('/delete', [TiposCuentas::class, 'delete']);
$app->get('[/]', [TiposCuentas::class, 'show']);
});

View File

@ -1,12 +0,0 @@
<?php
use Contabilidad\Common\Controller\Transacciones;
$app->group('/transacciones', function($app) {
$app->post('/add[/]', [Transacciones::class, 'add']);
$app->get('[/]', Transacciones::class);
});
$app->group('/transaccion/{transaccion_id}', function($app) {
$app->put('/edit', [Transacciones::class, 'edit']);
$app->delete('/delete', [Transacciones::class, 'delete']);
$app->get('[/]', [Transacciones::class, 'show']);
});

View File

@ -1,12 +0,0 @@
<?php
use Contabilidad\Common\Controller\Files;
$app->group('/uploads', function($app) {
$app->post('/add[/]', [Files::class, 'upload']);
$app->get('[/]', Files::class);
});
$app->group('/upload/{folder}/{filename}', function($app) {
$app->put('[/]', [Files::class, 'edit']);
$app->delete('[/]', [Files::class, 'delete']);
$app->get('[/]', [Files::class, 'get']);
});

View File

@ -1,6 +0,0 @@
<?php
use Contabilidad\Common\Controller\Workers;
$app->group('/workers', function($app) {
$app->post('/register', [Workers::class, 'register']);
});

View File

@ -1,2 +1,2 @@
<?php
$app->add($app->getContainer()->get(Contabilidad\Common\Middleware\Auth::class));
$app->add($app->getContainer()->get(Common\Middleware\Auth::class));

View File

@ -2,28 +2,39 @@
use Psr\Container\ContainerInterface as Container;
return [
'folders' => function(Container $c) {
$arr = [
'base' => dirname(__DIR__, 2)
];
$arr['resources'] = implode(DIRECTORY_SEPARATOR, [
$arr['base'],
'resources'
]);
$arr['routes'] = implode(DIRECTORY_SEPARATOR, [
$arr['resources'],
'routes'
]);
$arr['public'] = implode(DIRECTORY_SEPARATOR, [
$arr['base'],
'public'
]);
return (object) $arr;
},
'urls' => function(Container $c) {
$arr = [
'python' => 'http://python:5000'
];
return (object) $arr;
}
'folders' => function(Container $c) {
return \ProVM\Implement\Collection::fromArray([
'base' => dirname(__DIR__, 2),
'resources' => function(\Psr\Collection\CollectionInterface $collection) {
return implode(DIRECTORY_SEPARATOR, [
$collection['base'],
'resources'
]);
},
'documentation' => function(\Psr\Collection\CollectionInterface $collection) {
return implode(DIRECTORY_SEPARATOR, [
$collection['resources'],
'documentation'
]);
},
'routes' => function(\Psr\Collection\CollectionInterface $collection) {
return implode(DIRECTORY_SEPARATOR, [
$collection['resources'],
'routes'
]);
},
'public' => function(\Psr\Collection\CollectionInterface $collection) {
return implode(DIRECTORY_SEPARATOR, [
$collection['base'],
'public'
]);
}
]);
},
'urls' => function(Container $c) {
$arr = [
'python' => 'http://python:5000'
];
return (object) $arr;
}
];

View File

@ -14,15 +14,6 @@ return [
'name' => $_ENV['MYSQL_DATABASE']
]
];
function toObj($arr) {
$obj = (object) $arr;
foreach ($arr as $k => $v) {
if (is_array($v)) {
$obj->{$k} = toObj($v);
}
}
return $obj;
}
return (object) ['databases' => toObj($arr), 'short_names' => true];
return (object) ['databases' => \ProVM\Implement\Collection::fromArray($arr), 'short_names' => true];
}
];

View File

@ -0,0 +1,14 @@
<?php
use Psr\Container\ContainerInterface;
return [
\Common\Service\Auth::class => function(ContainerInterface $container) {
return new \Common\Service\Auth($container->get('api_key'));
},
\Common\Middleware\Auth::class => function(ContainerInterface $container) {
return new \Common\Middleware\Auth(
$container->get(\Nyholm\Psr7\Factory\Psr17Factory::class),
$container->get(\Common\Service\Auth::class)
);
}
];

View File

@ -2,12 +2,12 @@
use Psr\Container\ContainerInterface;
return [
\Common\Concept\Database::class => function(ContainerInterface $container) {
$settings = $container->get('databases')->default;
return new \Contabilidad\Implement\Database\MySQL(
$settings->host->name,
$settings->user->name,
$settings->user->password,
\Psr\Database\DatabaseInterface::class => function(ContainerInterface $container) {
$settings = (object) $container->get('databases')->databases->default;
return new \ProVM\Implement\Database\MySQL(
$settings->host['name'],
$settings->user['name'],
$settings->user['password'],
$settings->name
);
}

View File

@ -1,7 +1,7 @@
<?php
namespace Contabilidad\Model;
use Common\Alias\Model;
use ProVM\Alias\Model;
class Cuenta extends Model
{
@ -15,4 +15,14 @@ class Cuenta extends Model
{
return $this->id;
}
protected string $nombre;
public function setNombre(string $nombre): Cuenta
{
$this->nombre = $nombre;
return $this;
}
public function getNombre(): string
{
return $this->nombre;
}
}

View File

@ -1,14 +1,18 @@
<?php
namespace Contabilidad\Repository;
use Common\Alias\Repository;
use Common\Concept\Model;
use Psr\Database\DatabaseInterface;
use PDOException;
use ProVM\Alias\Repository;
use ProVM\Concept\Model;
use ProVM\Implement\Database\QueryBuilder;
use Contabilidad\Model\Cuenta as BaseModel;
class Cuenta extends Repository
{
public function __construct()
public function __construct(DatabaseInterface $database, QueryBuilder $builder)
{
parent::__construct($database, $builder);
$this->setTable('cuentas');
$this->setProperties(['id', 'nombre', 'tipo']);
}
@ -16,17 +20,33 @@ class Cuenta extends Repository
public function load(array $data_row): Model
{
return (new BaseModel())
->setId($data_row['id']);
->setId($data_row['id'])
->setNombre($data_row['nombre']);
}
public function create(array $data): Model
{
try {
return $this->fetchByNombre($data['nombre']);
} catch (PDOException $e) {
return (new BaseModel())
->setNew()
->setNombre($data['nombre']);
}
}
public function fetchByKey($key): Model
public function fetchById(int $id): Model
{
$query = "SELECT * FROM {$this->getTable()} WHERE `id` = ?";
return $this->load($this->getDatabase()->prepare($query)->execute($key)[0]);
$query = $this->getQueryBuilder()->select()->from($this->getTable())->where('id = ?')->build();
return $this->load($this->getDatabase()->prepare($query)->execute([$id])[0]);
}
public function fetchByNombre(string $nombre): Model
{
$query = $this->getQueryBuilder()->select()->from($this->getTable())->where('nombre = ?')->build();
return $this->load($this->getDatabase()->prepare($query)->execute([$nombre])[0]);
}
public function delete(Model $model): void
{
$query = "DELETE FROM {$this->getTable()} WHERE `id` = ?";
$query = $this->getQueryBuilder()->delete()->from($this->getTable())->where('id = ?')->build();
$this->getDatabase()->prepare($query)->delete([$model->getId()]);
}
}

View File

@ -7,6 +7,7 @@ services:
api:
profiles:
- api
container_name: api
<<: *restart
image: php
build:
@ -19,9 +20,10 @@ services:
- ./api/:/app/
- ./api/php.ini:/usr/local/etc/php/conf.d/php.ini
- ./logs/api/php/:/var/log/php/
api-proxy:
api_proxy:
profiles:
- api
container_name: api_proxy
<<: *restart
image: nginx
ports:
@ -33,6 +35,7 @@ services:
db:
profiles:
- api
container_name: db
<<: *restart
image: mariadb
env_file: .db.env
@ -41,6 +44,7 @@ services:
adminer:
profiles:
- api
container_name: adminer
<<: *restart
image: adminer
ports:
@ -49,6 +53,7 @@ services:
ui:
profiles:
- ui
container_name: ui
<<: *restart
image: php-ui
env_file:
@ -60,9 +65,10 @@ services:
- ./ui/:/app/
- ./ui/php.ini:/usr/local/etc/php/conf.d/php.ini
- ./logs/ui/php/:/var/log/php/
ui-proxy:
ui_proxy:
profiles:
- ui
container_name: ui_proxy
<<: *restart
image: nginx
ports:
@ -75,6 +81,7 @@ services:
python:
profiles:
- python
container_name: python
<<: *restart
build:
context: ./python
@ -91,6 +98,7 @@ services:
console:
profiles:
- console
container_name: console
<<: *restart
build:
context: ./console