first commit
This commit is contained in:
35
vendor/claudecio/axiumphp/src/AxiumPHP.php
vendored
Executable file
35
vendor/claudecio/axiumphp/src/AxiumPHP.php
vendored
Executable file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace AxiumPHP;
|
||||
|
||||
use Exception;
|
||||
|
||||
class AxiumPHP {
|
||||
|
||||
private array $requiredConstants = [
|
||||
'INI_SYSTEM_PATH',
|
||||
'STORAGE_FOLDER_PATH'
|
||||
];
|
||||
|
||||
/**
|
||||
* Construtor que vai garantir que as constantes necessárias estejam definidas antes de
|
||||
* instanciar o AxiumPHP.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Verificar as constantes no momento da criação da instância
|
||||
$this->checkRequiredConstants();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se todas as constantes necessárias estão definidas.
|
||||
*
|
||||
* @throws Exception Se alguma constante necessária não estiver definida.
|
||||
*/
|
||||
private function checkRequiredConstants(): void {
|
||||
foreach ($this->requiredConstants as $constant) {
|
||||
if (!defined(constant_name: $constant)) {
|
||||
throw new Exception(message: "Constante '{$constant}' não definida.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
120
vendor/claudecio/axiumphp/src/Core/Database.php
vendored
Executable file
120
vendor/claudecio/axiumphp/src/Core/Database.php
vendored
Executable file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
namespace AxiumPHP\Core;
|
||||
|
||||
use PDO;
|
||||
use Dotenv\Dotenv;
|
||||
use RuntimeException;
|
||||
use AxiumPHP\Core\Database\Drivers\MySQL;
|
||||
use AxiumPHP\Core\Database\Drivers\Postgres;
|
||||
|
||||
class Database {
|
||||
private static array $connections = [];
|
||||
private static bool $envLoaded = false;
|
||||
|
||||
private static function loadEnv(): void {
|
||||
if (!self::$envLoaded) {
|
||||
if (file_exists(filename: ROOT_SYSTEM_PATH . '/.env')) {
|
||||
$dotenv = Dotenv::createImmutable(paths: ROOT_SYSTEM_PATH);
|
||||
$dotenv->load();
|
||||
}
|
||||
self::$envLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna conexão por nome + schema
|
||||
*/
|
||||
public static function getConnection(string $connectionName = 'DEFAULT', ?string $schema = null): mixed {
|
||||
self::loadEnv();
|
||||
$connectionName = strtoupper(string: $connectionName);
|
||||
$key = $connectionName . ($schema ? "_$schema" : '');
|
||||
|
||||
if (!isset(self::$connections[$key])) {
|
||||
$driver = $_ENV["{$connectionName}_DATABASE_DRIVER"];
|
||||
|
||||
switch (strtolower(string: $driver)) {
|
||||
case 'mysql':
|
||||
$conn = new MySQL(envName: $connectionName);
|
||||
break;
|
||||
case 'pgsql':
|
||||
$conn = new Postgres(envName: $connectionName, schema: $schema);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException(message: "Driver desconhecido: {$driver}");
|
||||
}
|
||||
|
||||
self::$connections[$key] = $conn;
|
||||
}
|
||||
|
||||
return self::$connections[$key];
|
||||
}
|
||||
|
||||
public static function disconnect(string $connectionName = 'DEFAULT', ?string $schema = null): void {
|
||||
$connectionName = strtoupper(string: $connectionName);
|
||||
$key = $connectionName . ($schema ? "_$schema" : '');
|
||||
self::$connections[$key] = null;
|
||||
}
|
||||
|
||||
/** Métodos auxiliares (execute, fetchAll, fetchOne, etc.) */
|
||||
public static function execute(string $sql, array $params = [], string $connectionName = 'DEFAULT', ?string $schema = null): bool {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if (method_exists(object_or_class: $conn, method: 'execute')) {
|
||||
return $conn->execute($sql, $params);
|
||||
}
|
||||
throw new RuntimeException(message: "O driver '{$connectionName}' não suporta execute()");
|
||||
}
|
||||
|
||||
public static function fetchAll(string $sql, array $params = [], string $connectionName = 'DEFAULT', ?string $schema = null): array {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if (method_exists(object_or_class: $conn, method: 'fetchAll')) {
|
||||
return $conn->fetchAll($sql, $params);
|
||||
}
|
||||
throw new RuntimeException("O driver '{$connectionName}' não suporta fetchAll()");
|
||||
}
|
||||
|
||||
public static function fetchOne(string $sql, array $params = [], string $connectionName = 'DEFAULT', ?string $schema = null): ?array {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if (method_exists(object_or_class: $conn, method: 'fetchOne')) {
|
||||
return $conn->fetchOne($sql, $params);
|
||||
}
|
||||
throw new RuntimeException(message: "O driver '{$connectionName}' não suporta fetchOne()");
|
||||
}
|
||||
|
||||
// Transações
|
||||
public static function beginTransaction(string $connectionName = 'DEFAULT', ?string $schema = null): void {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if (method_exists(object_or_class: $conn, method: 'beginTransaction')) $conn->beginTransaction();
|
||||
}
|
||||
|
||||
public static function commit(string $connectionName = 'DEFAULT', ?string $schema = null): void {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
|
||||
if ($conn instanceof PDO && $conn->inTransaction()) {
|
||||
$conn->commit();
|
||||
} elseif (method_exists(object_or_class: $conn, method: 'getPDO')) {
|
||||
$pdo = $conn->getPDO();
|
||||
if ($pdo instanceof PDO && $pdo->inTransaction()) {
|
||||
$pdo->commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function rollback(string $connectionName = 'DEFAULT', ?string $schema = null): void {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
|
||||
if ($conn instanceof PDO && $conn->inTransaction()) {
|
||||
$conn->rollback();
|
||||
} elseif (method_exists(object_or_class: $conn, method: 'getPDO')) {
|
||||
$pdo = $conn->getPDO();
|
||||
if ($pdo instanceof PDO && $pdo->inTransaction()) {
|
||||
$pdo->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function lastInsertId(string $connectionName = 'DEFAULT', ?string $schema = null): string {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if (method_exists(object_or_class: $conn, method: 'getPDO')) return $conn->getPDO()->lastInsertId();
|
||||
throw new RuntimeException(message: "O driver '{$connectionName}' não suporta lastInsertId()");
|
||||
}
|
||||
}
|
||||
32
vendor/claudecio/axiumphp/src/Core/Database/Drivers/MySQL.php
vendored
Executable file
32
vendor/claudecio/axiumphp/src/Core/Database/Drivers/MySQL.php
vendored
Executable file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace AxiumPHP\Core\Database\Drivers;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use RuntimeException;
|
||||
|
||||
class MySQL extends PDOAbstract {
|
||||
protected function connect(string $envName): void {
|
||||
$host = $_ENV["{$envName}_DATABASE_HOST"];
|
||||
$port = $_ENV["{$envName}_DATABASE_PORT"] ?? 3306;
|
||||
$dbname = $_ENV["{$envName}_DATABASE_NAME"];
|
||||
$user = $_ENV["{$envName}_DATABASE_USERNAME"];
|
||||
$password = $_ENV["{$envName}_DATABASE_PASSWORD"];
|
||||
$charset = $_ENV["{$envName}_DATABASE_CHARSET"] ?? 'utf8mb4';
|
||||
|
||||
try {
|
||||
$this->connection = new PDO(
|
||||
dsn: "mysql:host={$host};port={$port};dbname={$dbname};charset={$charset}",
|
||||
username: $user,
|
||||
password: $password,
|
||||
options: [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_PERSISTENT => true,
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
throw new RuntimeException(message: "Erro ao conectar no MySQL: {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
55
vendor/claudecio/axiumphp/src/Core/Database/Drivers/PDOAbstract.php
vendored
Executable file
55
vendor/claudecio/axiumphp/src/Core/Database/Drivers/PDOAbstract.php
vendored
Executable file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace AxiumPHP\Core\Database\Drivers;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class PDOAbstract {
|
||||
protected PDO $connection;
|
||||
|
||||
public function __construct(string $envName) {
|
||||
$this->connect(envName: $envName);
|
||||
}
|
||||
|
||||
abstract protected function connect(string $envName): void;
|
||||
|
||||
public function execute(string $sql, array $params = []): bool {
|
||||
$stmt = $this->connection->prepare(query: $sql);
|
||||
return $stmt->execute(params: $params);
|
||||
}
|
||||
|
||||
public function fetchAll(string $sql, array $params = []): array {
|
||||
$stmt = $this->connection->prepare(query: $sql);
|
||||
$stmt->execute(params: $params);
|
||||
return $stmt->fetchAll(mode: PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function fetchOne(string $sql, array $params = []): ?array {
|
||||
$stmt = $this->connection->prepare(query: $sql);
|
||||
$stmt->execute(params: $params);
|
||||
$result = $stmt->fetch(mode: PDO::FETCH_ASSOC);
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
public function beginTransaction(): void {
|
||||
if (!$this->connection->inTransaction()) {
|
||||
$this->connection->beginTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public function commit(): void {
|
||||
if ($this->connection->inTransaction()) {
|
||||
$this->connection->commit();
|
||||
}
|
||||
}
|
||||
|
||||
public function rollback(): void {
|
||||
if ($this->connection->inTransaction()) {
|
||||
$this->connection->rollBack();
|
||||
}
|
||||
}
|
||||
|
||||
public function getPDO(): PDO {
|
||||
return $this->connection;
|
||||
}
|
||||
}
|
||||
55
vendor/claudecio/axiumphp/src/Core/Database/Drivers/Postgres.php
vendored
Executable file
55
vendor/claudecio/axiumphp/src/Core/Database/Drivers/Postgres.php
vendored
Executable file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace AxiumPHP\Core\Database\Drivers;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use RuntimeException;
|
||||
|
||||
class Postgres extends PDOAbstract {
|
||||
|
||||
public function __construct(string $envName, ?string $schema = null) {
|
||||
$this->connect(envName: $envName, schema: $schema);
|
||||
}
|
||||
|
||||
protected function connect(string $envName, ?string $schema = null): void {
|
||||
$host = $_ENV["{$envName}_DATABASE_HOST"];
|
||||
$port = $_ENV["{$envName}_DATABASE_PORT"] ?? 5432;
|
||||
$dbname = $_ENV["{$envName}_DATABASE_NAME"];
|
||||
$user = $_ENV["{$envName}_DATABASE_USERNAME"];
|
||||
$pass = $_ENV["{$envName}_DATABASE_PASSWORD"];
|
||||
$schema = $schema ?? ($_ENV["{$envName}_DATABASE_SCHEMA"] ?? 'public');
|
||||
$systemTimeZone = $_ENV["SYSTEM_TIMEZONE"] ?? 'UTC';
|
||||
$sslMode = $_ENV["{$envName}_DATABASE_SSLMODE"] ?? 'disable'; // disable, require, verify-ca, verify-full
|
||||
|
||||
$dsn = "pgsql:host={$host};port={$port};dbname={$dbname};sslmode={$sslMode}";
|
||||
|
||||
try {
|
||||
$this->connection = new PDO(
|
||||
dsn: $dsn,
|
||||
username: $user,
|
||||
password: $pass,
|
||||
options: [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_PERSISTENT => false
|
||||
]
|
||||
);
|
||||
|
||||
// Define o fuso horário da sessão
|
||||
$this->connection->exec(statement: "SET TIME ZONE '{$systemTimeZone}'");
|
||||
|
||||
// Define o schema padrão
|
||||
$this->connection->exec(statement: "SET search_path TO {$schema}, public");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
throw new RuntimeException(
|
||||
message: "Erro ao conectar no PostgreSQL: {$e->getMessage()}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Método auxiliar para alterar schema dinamicamente
|
||||
public function setSchema(string $schema): void {
|
||||
$this->connection->exec(statement: "SET search_path TO {$schema}, public");
|
||||
}
|
||||
}
|
||||
118
vendor/claudecio/axiumphp/src/Core/LoggerService.php
vendored
Executable file
118
vendor/claudecio/axiumphp/src/Core/LoggerService.php
vendored
Executable file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
namespace AxiumPHP\Core;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use AxiumPHP\Core\Database;
|
||||
|
||||
class LoggerService {
|
||||
public const DRIVER_FILE = 'FILE';
|
||||
public const DRIVER_DATABASE = 'DATABASE';
|
||||
|
||||
private static string $logDir;
|
||||
private static bool $initialized = false;
|
||||
private static string $driver = self::DRIVER_FILE;
|
||||
private static string $connectionName = 'DEFAULT'; // conexão do Database
|
||||
|
||||
/**
|
||||
* Inicializa o Logger
|
||||
*/
|
||||
public static function init(string $driver = self::DRIVER_FILE,?string $logDir = null,string $dbConnectionName = 'DEFAULT'): void {
|
||||
self::$driver = strtoupper(string: $driver);
|
||||
self::$connectionName = $dbConnectionName;
|
||||
|
||||
if (!defined(constant_name: 'STORAGE_FOLDER_PATH')) {
|
||||
throw new Exception(message: "Constante 'STORAGE_FOLDER_PATH' não foi definida.");
|
||||
}
|
||||
|
||||
self::$logDir = $logDir ?? STORAGE_FOLDER_PATH . '/logs';
|
||||
|
||||
if (self::$driver === self::DRIVER_DATABASE) {
|
||||
// Garante que a conexão exista
|
||||
Database::getConnection(connectionName: self::$connectionName);
|
||||
}
|
||||
|
||||
if (self::$driver === self::DRIVER_FILE && !is_dir(filename: self::$logDir)) {
|
||||
if (!mkdir(directory: self::$logDir, permissions: 0775, recursive: true) && !is_dir(filename: self::$logDir)) {
|
||||
throw new Exception(message: "Não foi possível criar o diretório de logs: " . self::$logDir);
|
||||
}
|
||||
}
|
||||
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log genérico
|
||||
*/
|
||||
public static function log(string $message, string $level = 'INFO', array $context = []): void {
|
||||
if (!self::$initialized) {
|
||||
throw new Exception(message: "LoggerService não foi inicializado. Chame LoggerService::init() antes.");
|
||||
}
|
||||
|
||||
switch (self::$driver) {
|
||||
case self::DRIVER_FILE:
|
||||
self::logToFile(message: $message, level: $level);
|
||||
break;
|
||||
case self::DRIVER_DATABASE:
|
||||
self::logToDatabase(message: $message, level: $level, context: $context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Métodos auxiliares por nível
|
||||
public static function info(string $message, array $context = []): void {
|
||||
self::log(message: $message, level: 'INFO', context: $context);
|
||||
}
|
||||
|
||||
public static function warning(string $message, array $context = []): void {
|
||||
self::log(message: $message, level: 'WARNING', context: $context);
|
||||
}
|
||||
|
||||
public static function error(string $message, array $context = []): void {
|
||||
self::log(message: $message, level: 'ERROR', context: $context);
|
||||
}
|
||||
|
||||
public static function debug(string $message, array $context = []): void {
|
||||
self::log(message: $message, level: 'DEBUG', context: $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log para arquivo
|
||||
*/
|
||||
private static function logToFile(string $message, string $level): void {
|
||||
$date = (new DateTime())->format(format: 'Y-m-d');
|
||||
$now = (new DateTime())->format(format: 'Y-m-d H:i:s');
|
||||
$filename = self::$logDir . "/app-{$date}.log";
|
||||
$logMessage = "[$now][$level] $message" . PHP_EOL;
|
||||
file_put_contents(filename: $filename, data: $logMessage, flags: FILE_APPEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log para database usando o Database multi-driver
|
||||
*/
|
||||
private static function logToDatabase(string $message, string $level, array $context = []): void {
|
||||
$sql =
|
||||
"INSERT INTO logs (
|
||||
level,
|
||||
message,
|
||||
context,
|
||||
created_at
|
||||
) VALUES (
|
||||
:level,
|
||||
:message,
|
||||
:context,
|
||||
:created_at
|
||||
)";
|
||||
|
||||
Database::execute(
|
||||
sql: $sql,
|
||||
params: [
|
||||
'level' => $level,
|
||||
'message' => $message,
|
||||
'context' => json_encode(value: $context),
|
||||
'created_at' => (new DateTime())->format(format: 'Y-m-d H:i:s')
|
||||
],
|
||||
connectionName: self::$connectionName
|
||||
);
|
||||
}
|
||||
}
|
||||
267
vendor/claudecio/axiumphp/src/Core/Module/Loader.php
vendored
Executable file
267
vendor/claudecio/axiumphp/src/Core/Module/Loader.php
vendored
Executable file
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
namespace AxiumPHP\Core\Module;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Loader {
|
||||
private $configFilePath;
|
||||
private $configData;
|
||||
private $startedModules = [];
|
||||
private static array $loadedShortcuts = [];
|
||||
private static array $loadedModules = [];
|
||||
private array $requiredConstants = [
|
||||
'MODULE_PATH',
|
||||
];
|
||||
private $modulePath = MODULE_PATH;
|
||||
private $iniSystemPath = INI_SYSTEM_PATH;
|
||||
|
||||
/**
|
||||
* Construtor da classe.
|
||||
*
|
||||
* Inicializa o objeto com o caminho do arquivo de configuração e carrega as configurações.
|
||||
*
|
||||
* @param string $configFileName O caminho do arquivo de configuração (opcional).
|
||||
*/
|
||||
public function __construct(?string $configFileName = "system-ini.json") {
|
||||
$this->configFilePath = "{$this->iniSystemPath}/{$configFileName}";
|
||||
$this->loadConfig();
|
||||
// Verificar as constantes no momento da criação da instância
|
||||
$this->checkRequiredConstants();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se todas as constantes necessárias estão definidas.
|
||||
*
|
||||
* @throws Exception Se alguma constante necessária não estiver definida.
|
||||
*/
|
||||
private function checkRequiredConstants(): void {
|
||||
foreach ($this->requiredConstants as $constant) {
|
||||
if (!defined(constant_name: $constant)) {
|
||||
throw new Exception(message: "Constante '{$constant}' não definida.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Carrega as configurações do arquivo JSON.
|
||||
*
|
||||
* Este método lê o conteúdo do arquivo de configuração especificado em
|
||||
* `$this->configFilePath`, decodifica-o de JSON e armazena os dados
|
||||
* no atributo `$this->configData`.
|
||||
*
|
||||
* @throws Exception Se o arquivo de configuração não for encontrado.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadConfig():void {
|
||||
$configPath = $this->configFilePath;
|
||||
|
||||
// Verifica se o arquivo de configuração existe
|
||||
if (file_exists(filename: $configPath)) {
|
||||
$jsonContent = file_get_contents(filename: $configPath);
|
||||
$this->configData = json_decode(json: $jsonContent, associative: true);
|
||||
} else {
|
||||
throw new Exception(message: "Arquivo de inicialização não encontrado: {$configPath}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Carrega os módulos do núcleo do sistema definidos na configuração.
|
||||
*
|
||||
* Este método carrega os módulos listados na seção "Core" do arquivo
|
||||
* de configuração, utilizando o método `startModule`.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loadCoreModules():void {
|
||||
$this->startModule(modules: $this->configData["Modules"]["Core"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Carrega e inicializa todos os módulos ativos da aplicação, conforme
|
||||
* definido na configuração.
|
||||
*
|
||||
* Este método acessa a propriedade `$configData`, especificamente a seção
|
||||
* ["Modules"]["active"], que se espera ser um array contendo os
|
||||
* identificadores (nomes ou objetos) dos módulos que devem ser carregados
|
||||
* e inicializados. Em seguida, chama o método `startModule`, passando este
|
||||
* array de módulos ativos para iniciar o processo de carregamento e
|
||||
* inicialização de cada um deles.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @see startModule()
|
||||
* @property array $configData Propriedade da classe que contém os dados de
|
||||
* configuração da aplicação. Espera-se que possua uma chave "Modules" com
|
||||
* uma sub-chave "active" contendo um array de módulos.
|
||||
*/
|
||||
public function loadActiveModules():void {
|
||||
$this->startModule(modules: $this->configData["Modules"]["Active"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Carrega e inicializa um único módulo da aplicação.
|
||||
*
|
||||
* Este método recebe um identificador de módulo (que pode ser uma string
|
||||
* com o nome do módulo ou um objeto representando o módulo) e o passa para
|
||||
* o método `startModule` para iniciar o processo de carregamento e
|
||||
* inicialização desse módulo específico.
|
||||
*
|
||||
* @param mixed $module O identificador do módulo a ser carregado. Pode ser
|
||||
* uma string contendo o nome do módulo ou um objeto de módulo já instanciado.
|
||||
* O tipo exato depende da implementação do sistema de módulos.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @see startModule()
|
||||
*/
|
||||
public function loadModule(mixed $module) {
|
||||
$this->startModule(modules: [$module]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna os atalhos carregados para um slug de módulo específico.
|
||||
*
|
||||
* Este método estático permite acessar atalhos que foram previamente carregados
|
||||
* e armazenados na propriedade estática `self::$loadedShortcuts`. Ele espera
|
||||
* o slug (identificador amigável) de um módulo como entrada e retorna o array
|
||||
* de atalhos associado a ele.
|
||||
*
|
||||
* @param string $moduleSlug O slug do módulo para o qual os atalhos devem ser retornados.
|
||||
* @return array|null Um array contendo os atalhos para o módulo especificado, ou `null`
|
||||
* se nenhum atalho tiver sido carregado para aquele slug.
|
||||
*/
|
||||
public static function getShortcuts(string $moduleSlug): ?array {
|
||||
if(isset(self::$loadedShortcuts[$moduleSlug]['shortcuts'])) {
|
||||
return self::$loadedShortcuts[$moduleSlug]['shortcuts'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna os slugs dos módulos que foram carregados.
|
||||
*
|
||||
* Este método estático fornece acesso à lista de identificadores (slugs)
|
||||
* de todos os módulos que foram previamente carregados e armazenados na
|
||||
* propriedade estática `self::$loadedModules`. É útil para verificar
|
||||
* quais módulos estão ativos ou disponíveis no contexto atual da aplicação.
|
||||
*
|
||||
* @return array|null Um array contendo os slugs dos módulos carregados, ou `null`
|
||||
* se nenhum módulo tiver sido carregado ou se a propriedade estiver vazia.
|
||||
*/
|
||||
public static function getSlugLoadedModules(): ?array {
|
||||
return self::$loadedModules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca o nome real de uma subpasta dentro de um diretório base,
|
||||
* ignorando a diferença entre maiúsculas e minúsculas.
|
||||
*
|
||||
* Este método privado recebe um `$basePath` (o diretório onde procurar)
|
||||
* e um `$targetName` (o nome da pasta desejada). Primeiro, verifica se o
|
||||
* `$basePath` é um diretório válido. Se não for, retorna null. Em seguida,
|
||||
* lê todos os arquivos e pastas dentro do `$basePath`. Para cada entrada,
|
||||
* compara o nome da entrada com o `$targetName` de forma case-insensitive.
|
||||
* Se encontrar uma entrada que corresponda ao `$targetName` (ignorando o case)
|
||||
* e que seja um diretório, retorna o nome da entrada com o seu case real.
|
||||
* Se após verificar todas as entradas nenhuma pasta correspondente for
|
||||
* encontrada, retorna null.
|
||||
*
|
||||
* @param string $basePath O caminho para o diretório base onde a subpasta será procurada.
|
||||
* @param string $targetName O nome da subpasta a ser encontrada (a comparação é case-insensitive).
|
||||
* @return string|null O nome real da pasta (com o case correto) se encontrada, ou null caso contrário.
|
||||
*/
|
||||
private function getRealFolderName(string $basePath, string $targetName): ?string {
|
||||
if (!is_dir(filename: $basePath)) return null;
|
||||
|
||||
$entries = scandir(directory: $basePath);
|
||||
foreach ($entries as $entry) {
|
||||
if (strcasecmp(string1: $entry, string2: $targetName) === 0 && is_dir(filename: "{$basePath}/{$entry}")) {
|
||||
return $entry; // Nome com o case real
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicia os módulos especificados.
|
||||
*
|
||||
* Este método itera sobre a lista de módulos fornecida, carrega seus manifestos,
|
||||
* verifica a compatibilidade da versão, carrega as dependências e inclui o
|
||||
* arquivo de bootstrap do módulo.
|
||||
*
|
||||
* @param array $modules Um array de strings representando os módulos a serem iniciados.
|
||||
* Cada string deve estar no formato "nome_do_modulo@versao".
|
||||
*
|
||||
* @throws Exception Se o manifesto do módulo não for encontrado, se houver um erro ao decodificar
|
||||
* o manifesto, se a versão do módulo for incompatível ou se o bootstrap
|
||||
* do módulo não for encontrado.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function startModule(array $modules): void {
|
||||
foreach ($modules as $module) {
|
||||
// Identifica o módulo requisitado e sua versão
|
||||
[$moduleName, $version] = explode(separator: '@', string: $module);
|
||||
|
||||
// Pega o nome real da pasta do módulo
|
||||
$realModuleFolder = $this->getRealFolderName(basePath: $this->modulePath, targetName: $moduleName);
|
||||
if (!$realModuleFolder) {
|
||||
throw new Exception(message: "Pasta do módulo '{$moduleName}' não encontrada.");
|
||||
}
|
||||
|
||||
// Caminho do manifesto usando o nome real
|
||||
$manifestPath = "{$this->modulePath}/{$realModuleFolder}/manifest.json";
|
||||
if (!file_exists(filename: $manifestPath)) {
|
||||
throw new Exception(message: "Manifesto do módulo '{$moduleName}' não encontrado.");
|
||||
}
|
||||
|
||||
$moduleManifest = json_decode(json: file_get_contents(filename: $manifestPath), associative: true);
|
||||
if (!$moduleManifest) {
|
||||
throw new Exception(message: "Erro ao decodificar o manifesto do módulo '{$moduleName}'.");
|
||||
}
|
||||
|
||||
// Evita carregar o mesmo módulo mais de uma vez
|
||||
if (in_array(needle: $moduleManifest["uuid"], haystack: $this->startedModules)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verifica se a versão é compatível
|
||||
if ($moduleManifest['version'] !== $version) {
|
||||
throw new Exception(message: "Versão do módulo '{$moduleName}' é incompatível. Versão requerida: {$version}. Versão instalada: {$moduleManifest['version']}");
|
||||
}
|
||||
|
||||
// Inicia Bootstrap do Módulo
|
||||
$bootstrapModuleFile = "{$this->modulePath}/{$realModuleFolder}/bootstrap.php";
|
||||
if (file_exists(filename: $bootstrapModuleFile)) {
|
||||
require_once $bootstrapModuleFile;
|
||||
}
|
||||
|
||||
// Procura a pasta Routes com o case correto
|
||||
$realRoutesFolder = $this->getRealFolderName(basePath: "{$this->modulePath}/{$realModuleFolder}", targetName: 'Routes');
|
||||
if ($realRoutesFolder) {
|
||||
// Procura arquivo com atalhos de rotas
|
||||
$shortcutsFile = "{$this->modulePath}/{$realModuleFolder}/{$realRoutesFolder}/shortcuts.json";
|
||||
if (file_exists(filename: $shortcutsFile)) {
|
||||
$shortcuts = json_decode(
|
||||
json: file_get_contents(filename: $shortcutsFile),
|
||||
associative: true
|
||||
);
|
||||
|
||||
self::$loadedShortcuts[$moduleManifest["slug"]] = $shortcuts;
|
||||
}
|
||||
}
|
||||
|
||||
// Marca como carregado
|
||||
$this->startedModules[] = $moduleManifest["uuid"];
|
||||
self::$loadedModules[] = $moduleManifest["slug"];
|
||||
|
||||
// Carrega dependências, se existirem
|
||||
if (!empty($moduleManifest['dependencies'])) {
|
||||
$this->startModule(modules: $moduleManifest['dependencies']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
635
vendor/claudecio/axiumphp/src/Core/Router.php
vendored
Executable file
635
vendor/claudecio/axiumphp/src/Core/Router.php
vendored
Executable file
@@ -0,0 +1,635 @@
|
||||
<?php
|
||||
namespace AxiumPHP\Core;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Router {
|
||||
private static $routes = [];
|
||||
private static $params = [];
|
||||
private static $ROUTER_MODE = null;
|
||||
private static $APP_SYS_MODE = null;
|
||||
private static $ROUTER_ALLOWED_ORIGINS = ['*'];
|
||||
private static $currentGroupPrefix = '';
|
||||
private static $currentGroupMiddlewares = [];
|
||||
private static array $requiredConstants = [
|
||||
'ROUTER_MODE',
|
||||
'APP_SYS_MODE'
|
||||
];
|
||||
private static array $allowedHttpRequests = [
|
||||
'GET',
|
||||
'POST',
|
||||
'PUT',
|
||||
'PATCH',
|
||||
'DELETE',
|
||||
'OPTIONS'
|
||||
];
|
||||
|
||||
/**
|
||||
* Construtor que vai garantir que as constantes necessárias estejam definidas antes de
|
||||
* instanciar a view.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Verificar as constantes no momento da criação da instância
|
||||
$this->checkRequiredConstant();
|
||||
|
||||
// Define constante
|
||||
self::$ROUTER_MODE = strtoupper(string: ROUTER_MODE);
|
||||
self::$APP_SYS_MODE = strtoupper(string: APP_SYS_MODE);
|
||||
|
||||
// Se o modo de roteamento for JSON, define as origens permitidas
|
||||
if(self::$ROUTER_MODE === 'JSON') {
|
||||
self::$ROUTER_ALLOWED_ORIGINS = ROUTER_ALLOWED_ORIGINS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna o modo de roteamento atual.
|
||||
*
|
||||
* Este método estático simplesmente retorna o valor da propriedade estática `self::$ROUTER_MODE`.
|
||||
* Ele é usado para obter o modo de operação do roteador, que pode indicar se está
|
||||
* em modo de desenvolvimento, produção ou outro modo configurado.
|
||||
*
|
||||
* @return string O modo de roteamento como uma string.
|
||||
*/
|
||||
public static function getMode(): string {
|
||||
return (string) self::$ROUTER_MODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se todas as constantes necessárias estão definidas.
|
||||
*
|
||||
* @throws Exception Se alguma constante necessária não estiver definida.
|
||||
*/
|
||||
private function checkRequiredConstant(): void {
|
||||
foreach (self::$requiredConstants as $constant) {
|
||||
if (!defined(constant_name: $constant)) {
|
||||
http_response_code(response_code: 500);
|
||||
header(header: "Content-Type: application/json; charset=utf-8");
|
||||
echo json_encode(value: [
|
||||
"status" => 'error',
|
||||
"message" => "Constante '{$constant}' não definida.",
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adiciona uma rota com método GET à lista de rotas da aplicação.
|
||||
*
|
||||
* Este método é um atalho para adicionar rotas com o método HTTP GET. Ele
|
||||
* chama o método `addRoute` internamente, passando os parâmetros
|
||||
* fornecidos e o método 'GET'.
|
||||
*
|
||||
* @param string $uri O caminho da rota (ex: '/usuarios', '/produtos').
|
||||
* @param array $handler Um array contendo o nome do controlador e o nome da ação
|
||||
* que devem ser executados quando a rota for
|
||||
* corresponder (ex: ['UsuarioController', 'index']).
|
||||
* @param array $middlewares Um array opcional contendo os nomes dos middlewares que
|
||||
* devem ser executados antes do handler da rota.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function GET(string $uri, array $handler, array $middlewares = []): void {
|
||||
self::addRoute(method: "GET", uri: $uri, handler: $handler, middlewares: $middlewares);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adiciona uma rota com método POST à lista de rotas da aplicação.
|
||||
*
|
||||
* Este método é um atalho para adicionar rotas com o método HTTP POST. Ele
|
||||
* chama o método `addRoute` internamente, passando os parâmetros
|
||||
* fornecidos e o método 'POST'.
|
||||
*
|
||||
* @param string $uri O caminho da rota (ex: '/usuarios', '/produtos').
|
||||
* @param array $handler Um array contendo o nome do controlador e o nome da ação
|
||||
* que devem ser executados quando a rota for
|
||||
* corresponder (ex: ['UsuarioController', 'salvar']).
|
||||
* @param array $middlewares Um array opcional contendo os nomes dos middlewares que
|
||||
* devem ser executados antes do handler da rota.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function POST(string $uri, array $handler, array $middlewares = []): void {
|
||||
self::addRoute(method: "POST", uri: $uri, handler: $handler, middlewares: $middlewares);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adiciona uma rota com método PUT à lista de rotas da aplicação.
|
||||
*
|
||||
* Este método é um atalho para adicionar rotas com o método HTTP PUT. Ele
|
||||
* chama o método `addRoute` internamente, passando os parâmetros
|
||||
* fornecidos e o método 'PUT'.
|
||||
*
|
||||
* @param string $uri O caminho da rota (ex: '/usuarios', '/produtos').
|
||||
* @param array $handler Um array contendo o nome do controlador e o nome da ação
|
||||
* que devem ser executados quando a rota for
|
||||
* corresponder (ex: ['UsuarioController', 'salvar']).
|
||||
* @param array $middlewares Um array opcional contendo os nomes dos middlewares que
|
||||
* devem ser executados antes do handler da rota.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function PUT(string $uri, array $handler, array $middlewares = []): void {
|
||||
self::addRoute(method: "PUT", uri: $uri, handler: $handler, middlewares: $middlewares);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adiciona uma nova rota que responde a requisições PATCH.
|
||||
*
|
||||
* Este é um método estático de conveniência que simplifica o registro de rotas
|
||||
* para o método HTTP PATCH. Ele delega a tarefa principal para o método
|
||||
* privado `addRoute`, passando o método HTTP, a URI, a função de manipulação
|
||||
* (`handler`) e quaisquer middlewares associados.
|
||||
*
|
||||
* @param string $uri A URI da rota (ex: '/api/resource/{id}').
|
||||
* @param array $handler Um array de dois elementos que define o manipulador da rota: o nome da classe do controlador e o nome do método a ser executado.
|
||||
* @param array $middlewares Um array opcional de middlewares a serem executados antes do manipulador da rota.
|
||||
* @return void
|
||||
*/
|
||||
public static function PATCH(string $uri, array $handler, array $middlewares = []): void {
|
||||
self::addRoute(method: "PATCH", uri: $uri, handler: $handler, middlewares: $middlewares);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adiciona uma rota com método DELETE à lista de rotas da aplicação.
|
||||
*
|
||||
* Este método é um atalho para adicionar rotas com o método HTTP DELETE. Ele
|
||||
* chama o método `addRoute` internamente, passando os parâmetros
|
||||
* fornecidos e o método 'DELETE'.
|
||||
*
|
||||
* @param string $uri O caminho da rota (ex: '/usuarios', '/produtos').
|
||||
* @param array $handler Um array contendo o nome do controlador e o nome da ação
|
||||
* que devem ser executados quando a rota for
|
||||
* corresponder (ex: ['UsuarioController', 'salvar']).
|
||||
* @param array $middlewares Um array opcional contendo os nomes dos middlewares que
|
||||
* devem ser executados antes do handler da rota.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function DELETE(string $uri, array $handler, array $middlewares = []): void {
|
||||
self::addRoute(method: "DELETE", uri: $uri, handler: $handler, middlewares: $middlewares);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adiciona uma rota à lista de rotas da aplicação.
|
||||
*
|
||||
* Este método estático armazena informações sobre uma rota (método HTTP,
|
||||
* caminho, controlador, ação e middlewares) em um array interno `$routes`
|
||||
* para posterior processamento pelo roteador.
|
||||
*
|
||||
* @param string $method O método HTTP da rota (ex: 'GET', 'POST', 'PUT', 'DELETE').
|
||||
* @param string $uri O caminho da rota (ex: '/usuarios', '/produtos/:id').
|
||||
* @param array $handler Um array contendo o nome do controlador e o nome da ação
|
||||
* que devem ser executados quando a rota for
|
||||
* corresponder (ex: ['UsuarioController', 'index']).
|
||||
* @param array $middlewares Um array opcional contendo os nomes dos middlewares que
|
||||
* devem ser executados antes do handler da rota.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function addRoute(string $method, string $uri, array $handler, array $middlewares = []): void {
|
||||
self::$routes[] = [
|
||||
'method' => strtoupper(string: $method),
|
||||
'path' => '/' . trim(string: self::$currentGroupPrefix . '/' . trim(string: $uri, characters: '/'), characters: '/'),
|
||||
'controller' => $handler[0],
|
||||
'action' => $handler[1],
|
||||
'middlewares' => array_merge(self::$currentGroupMiddlewares, $middlewares)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se um caminho de rota corresponde a um caminho de requisição.
|
||||
*
|
||||
* Este método estático compara um caminho de rota definido (ex: '/usuarios/:id')
|
||||
* com um caminho de requisição (ex: '/usuarios/123'). Ele suporta parâmetros
|
||||
* de rota definidos entre chaves (ex: ':id', ':nome'). Os parâmetros
|
||||
* correspondentes do caminho de requisição são armazenados no array estático
|
||||
* `$params` da classe.
|
||||
*
|
||||
* @param string $routePath O caminho da rota a ser comparado.
|
||||
* @param string $requestPath O caminho da requisição a ser comparado.
|
||||
*
|
||||
* @return bool True se o caminho da requisição corresponder ao caminho da
|
||||
* rota, false caso contrário.
|
||||
*/
|
||||
private static function matchPath($routePath, $requestPath): bool {
|
||||
// Limpa os parâmetros antes de capturar novos
|
||||
self::$params = []; // Certifica que a cada nova tentativa, a lista de parâmetros começa vazia
|
||||
|
||||
$routeParts = explode(separator: '/', string: trim(string: $routePath, characters: '/'));
|
||||
$requestParts = explode(separator: '/', string: trim(string: $requestPath, characters: '/'));
|
||||
|
||||
if (count(value: $routeParts) !== count(value: $requestParts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($routeParts as $i => $part) {
|
||||
if (preg_match(pattern: '/^{\w+}$/', subject: $part)) {
|
||||
self::$params[] = $requestParts[$i];
|
||||
} elseif ($part !== $requestParts[$i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrupa rotas sob um prefixo e middlewares.
|
||||
*
|
||||
* Este método estático permite agrupar rotas que compartilham um prefixo de
|
||||
* caminho e/ou middlewares. O prefixo e os middlewares definidos dentro do
|
||||
* grupo serão aplicados a todas as rotas definidas dentro da função de
|
||||
* callback.
|
||||
*
|
||||
* @param string $prefix O prefixo a ser adicionado aos caminhos das rotas
|
||||
* dentro do grupo (ex: '/admin', '/api/v1').
|
||||
* @param callable $callback Uma função anônima (callback) que define as rotas
|
||||
* que pertencem a este grupo.
|
||||
* @param array $middlewares Um array opcional contendo os middlewares que devem
|
||||
* ser aplicados a todas as rotas dentro do
|
||||
* grupo.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function group(string $prefix, callable $callback, array $middlewares = []): void {
|
||||
$previousPrefix = self::$currentGroupPrefix ?? '';
|
||||
$previousMiddlewares = self::$currentGroupMiddlewares ?? [];
|
||||
|
||||
self::$currentGroupPrefix = $previousPrefix . $prefix;
|
||||
self::$currentGroupMiddlewares = array_merge($previousMiddlewares, $middlewares);
|
||||
|
||||
call_user_func(callback: $callback);
|
||||
|
||||
self::$currentGroupPrefix = $previousPrefix;
|
||||
self::$currentGroupMiddlewares = $previousMiddlewares;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai os dados do corpo da requisição HTTP.
|
||||
*
|
||||
* Este método estático é responsável por analisar e retornar os dados enviados
|
||||
* no corpo de uma requisição HTTP, especialmente para métodos como `PUT` e `DELETE`,
|
||||
* onde os dados não são automaticamente populados em superglobais como `$_POST` ou `$_GET`.
|
||||
*
|
||||
* O fluxo de extração é o seguinte:
|
||||
* 1. **Lê o Corpo da Requisição:** `file_get_contents('php://input')` lê o conteúdo bruto do corpo da requisição HTTP.
|
||||
* 2. **Processamento Condicional:**
|
||||
* * **Para métodos `PUT` ou `DELETE`:**
|
||||
* * Verifica se o corpo da requisição (`$inputData`) não está vazio.
|
||||
* * **Verifica o `Content-Type`:**
|
||||
* * Se o `Content-Type` for `application/json`, tenta decodificar o corpo da requisição como JSON.
|
||||
* * Se houver um erro na decodificação JSON, ele define o código de status HTTP para 500,
|
||||
* envia uma resposta JSON com uma mensagem de erro e encerra a execução.
|
||||
* * Se o `Content-Type` não for `application/json` (ou não estiver definido), tenta analisar o corpo da requisição
|
||||
* como uma string de query URL (`parse_str`), o que é comum para `application/x-www-form-urlencoded`
|
||||
* em requisições `PUT`/`DELETE`.
|
||||
* * **Para outros métodos (e.g., `POST`, `GET`):** O método não tenta extrair dados do `php://input`.
|
||||
* Nesses casos, presume-se que os dados já estariam disponíveis em `$_POST`, `$_GET`, etc.
|
||||
* 3. **Limpeza Final:** Remove a chave `_method` dos dados, se presente. Essa chave é frequentemente
|
||||
* usada em formulários HTML para simular métodos `PUT` ou `DELETE` através de um campo oculto.
|
||||
*
|
||||
* @param string $method O método HTTP da requisição (e.g., 'GET', 'POST', 'PUT', 'DELETE').
|
||||
* @return array Um array associativo contendo os dados extraídos do corpo da requisição.
|
||||
* Em caso de erro de decodificação JSON, a execução é encerrada com uma resposta de erro HTTP.
|
||||
*/
|
||||
private static function extractRequestData(string $method): array {
|
||||
$inputData = file_get_contents(filename: 'php://input');
|
||||
$data = [];
|
||||
|
||||
// Só processa se for PUT ou DELETE e tiver dados no corpo
|
||||
if (in_array(needle: $method, haystack: ['PUT', 'DELETE', 'PATCH']) && !empty($inputData)) {
|
||||
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
||||
|
||||
// Se for JSON
|
||||
if (str_contains(haystack: $contentType, needle: 'application/json')) {
|
||||
$data = json_decode(json: $inputData, associative: true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
http_response_code(response_code: 500);
|
||||
header(header: "Content-Type: application/json; charset=utf-8");
|
||||
$json_error = json_last_error_msg();
|
||||
|
||||
echo json_encode(value: [
|
||||
"status" => 'error',
|
||||
"message" => "Erro ao decodificar JSON: {$json_error}"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
// Se for outro tipo (ex: x-www-form-urlencoded)
|
||||
else {
|
||||
parse_str(string: $inputData, result: $data);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove o _method se tiver
|
||||
if(isset($data['_method'])) {
|
||||
unset($data['_method']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executa os middlewares.
|
||||
*
|
||||
* Este método estático itera sobre um array de middlewares e executa cada um
|
||||
* deles. Os middlewares podem ser especificados como 'Classe::metodo' ou
|
||||
* 'Classe::metodo:argumento1:argumento2' para passar argumentos para o
|
||||
* método do middleware. Se um middleware retornar `false`, a execução é
|
||||
* interrompida e a função retorna `false`.
|
||||
*
|
||||
* @param array $middlewares Um array contendo os middlewares a serem executados.
|
||||
*
|
||||
* @return bool True se todos os middlewares forem executados com sucesso,
|
||||
* false se algum middleware falhar.
|
||||
* @throws Exception Se o formato do middleware for inválido ou se o método
|
||||
* do middleware não existir.
|
||||
*/
|
||||
public static function runMiddlewares(array $middlewares): bool {
|
||||
foreach ($middlewares as $middleware) {
|
||||
if (strpos(haystack: $middleware, needle: '::') !== false) {
|
||||
[$middlewareClass, $methodWithArgs] = explode(separator: '::', string: $middleware);
|
||||
|
||||
// Suporte a argumentos no middleware (ex: Middleware::Permission:ADMIN)
|
||||
$methodParts = explode(separator: ':', string: $methodWithArgs);
|
||||
$method = $methodParts[0];
|
||||
$args = array_slice(array: $methodParts, offset: 1);
|
||||
|
||||
if (method_exists(object_or_class: $middlewareClass, method: $method)) {
|
||||
$result = call_user_func_array(callback: [$middlewareClass, $method], args: $args);
|
||||
|
||||
// Se retornar false -> erro genérico
|
||||
if ($result === false) {
|
||||
http_response_code(response_code: 403);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(value: [
|
||||
"status" => 'error',
|
||||
"message" => "Middleware {$middlewareClass}::{$method} bloqueou a requisição."
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Se retornar array com status 'success' -> mostra a mensagem
|
||||
if (is_array(value: $result) && ($result['status'] ?? '') !== 'success') {
|
||||
http_response_code(response_code: $result['response_code'] ?? 403);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$response = [
|
||||
"response_code" => $result['response_code'] ?? 403,
|
||||
"status" => $result['status'] ?? 'error',
|
||||
"message" => $result['message'] ?? 'Erro no middleware'
|
||||
];
|
||||
|
||||
if(isset($result['output'])) {
|
||||
$response['output'] = $result['output'];
|
||||
}
|
||||
|
||||
echo json_encode(value: $response);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
http_response_code(response_code: 500);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(value: [
|
||||
"status" => 'error',
|
||||
"message" => "Método '{$method}' não existe na classe '{$middlewareClass}'"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
} else {
|
||||
http_response_code(response_code: 500);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(value: [
|
||||
"status" => 'error',
|
||||
"message" => "Formato inválido do middleware: '{$middleware}'"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // todos os middlewares passaram
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se uma rota corresponde à requisição.
|
||||
*
|
||||
* Este método verifica se o método HTTP e o caminho da requisição correspondem
|
||||
* aos da rota fornecida.
|
||||
*
|
||||
* @param string $method O método HTTP da requisição.
|
||||
* @param string $uri O caminho da requisição.
|
||||
* @param array $route Um array associativo contendo os dados da rota.
|
||||
*
|
||||
* @return bool Retorna true se a rota corresponder, false caso contrário.
|
||||
*/
|
||||
private static function matchRoute(string $method, string $uri, array $route) {
|
||||
// Verifica se o método HTTP da rota corresponde ao da requisição
|
||||
if ($route['method'] !== $method) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verifica se o caminho da requisição corresponde ao caminho da rota
|
||||
return self::matchPath(routePath: $route['path'], requestPath: $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepara os parâmetros para um método de requisição.
|
||||
*
|
||||
* Este método combina os parâmetros da rota, os parâmetros GET e, para
|
||||
* requisições PUT ou DELETE, os parâmetros fornecidos, retornando um
|
||||
* array de parâmetros preparados.
|
||||
*
|
||||
* @param string $method O método HTTP da requisição (GET, POST, PUT, DELETE).
|
||||
* @param array|null $params Um array opcional de parâmetros adicionais.
|
||||
*
|
||||
* @return array Um array contendo os parâmetros preparados.
|
||||
*/
|
||||
private static function prepareMethodParameters(string $method, ?array $params = []): array {
|
||||
// Guarda os parâmetros atuais (da rota)
|
||||
$atuallParams = self::$params;
|
||||
|
||||
// Adiciona os dados de PUT/DELETE/PATCH como um array no final
|
||||
if (in_array(needle: $method, haystack: ['PUT', 'DELETE', 'PATCH'])) {
|
||||
self::$params = array_merge($atuallParams, $params);
|
||||
}
|
||||
|
||||
// Normaliza os parâmetros para um array sequencial
|
||||
$preparedParams = array_values(array: self::$params);
|
||||
|
||||
return $preparedParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Despacha a requisição para o controlador e ação correspondentes.
|
||||
*
|
||||
* Este método estático analisa a requisição (método HTTP e caminho), encontra
|
||||
* a rota correspondente na lista de rotas definidas, executa os middlewares
|
||||
* da rota (se houver), instancia o controlador e chama a ação (método)
|
||||
* especificada na rota, passando os parâmetros da requisição (parâmetros da
|
||||
* rota, parâmetros GET e dados de PUT/DELETE). Se nenhuma rota
|
||||
* corresponder, um erro 404 é enviado.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function dispatch(): void {
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$uri = parse_url(url: $_SERVER['REQUEST_URI'], component: PHP_URL_PATH);
|
||||
$uri = trim(string: rtrim(string: $uri, characters: '/'), characters: '/');
|
||||
|
||||
// Suporte ao _method em POST para PUT/DELETE/PATCH
|
||||
if ($method === 'POST' && isset($_POST['_method'])) {
|
||||
$method = strtoupper(string: $_POST['_method']);
|
||||
}
|
||||
|
||||
// Verifica se o método HTTP é permitido
|
||||
if(!in_array(needle: $method, haystack: self::$allowedHttpRequests)) {
|
||||
http_response_code(response_code: 405);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(value: [
|
||||
"status" => 'error',
|
||||
"message" => "Método HTTP '{$method}' não permitido."
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// =============================
|
||||
// HEADERS CORS PARA TODOS OS MÉTODOS
|
||||
// =============================
|
||||
if (self::$ROUTER_MODE === 'JSON') {
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
|
||||
|
||||
if (in_array(needle: '*', haystack: self::$ROUTER_ALLOWED_ORIGINS)) {
|
||||
header(header: "Access-Control-Allow-Origin: *");
|
||||
} elseif (in_array(needle: $origin, haystack: self::$ROUTER_ALLOWED_ORIGINS)) {
|
||||
header(header: "Access-Control-Allow-Origin: $origin");
|
||||
} else {
|
||||
if (self::$APP_SYS_MODE === 'DEV') {
|
||||
header(header: "Access-Control-Allow-Origin: $origin");
|
||||
} else {
|
||||
http_response_code(response_code: 403);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(value: [
|
||||
"status" => 'error',
|
||||
"message" => "Origem '{$origin}' não permitida pelo CORS."
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
header(header: 'Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
||||
header(header: 'Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
}
|
||||
|
||||
// =============================
|
||||
// TRATAMENTO DO PRE-FLIGHT (OPTIONS)
|
||||
// =============================
|
||||
if ($method === 'OPTIONS') {
|
||||
http_response_code(response_code: 204); // No Content
|
||||
exit;
|
||||
}
|
||||
|
||||
// =============================
|
||||
// EXTRAI DADOS DE PUT, DELETE, PATCH
|
||||
// =============================
|
||||
$requestData = match($method) {
|
||||
'GET' => null,
|
||||
'POST' => null,
|
||||
default => self::extractRequestData(method: $method)
|
||||
};
|
||||
|
||||
// =============================
|
||||
// LOOP PARA PROCESSAR ROTAS
|
||||
// =============================
|
||||
foreach (self::$routes as $route) {
|
||||
if (self::matchRoute(method: $method, uri: $uri, route: $route)) {
|
||||
// Executa middlewares
|
||||
if (!empty($route['middlewares']) && !self::runMiddlewares(middlewares: $route['middlewares'])) {
|
||||
return; // Middleware bloqueou
|
||||
}
|
||||
|
||||
$controller = new $route['controller']();
|
||||
$action = $route['action'];
|
||||
$params = self::prepareMethodParameters(method: $method, params: [$requestData]);
|
||||
|
||||
switch (self::$ROUTER_MODE) {
|
||||
case 'VIEW':
|
||||
if (method_exists(object_or_class: $controller, method: $action)) {
|
||||
http_response_code(response_code: 200);
|
||||
call_user_func_array(callback: [$controller, $action], args: $params);
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'JSON':
|
||||
if (method_exists(object_or_class: $controller, method: $action)) {
|
||||
http_response_code(response_code: 200);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
call_user_func_array(callback: [$controller, $action], args: $params);
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================
|
||||
// ROTA NÃO ENCONTRADA
|
||||
// =============================
|
||||
self::pageNotFound();
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Exibe a página de erro 404 (Página não encontrada).
|
||||
*
|
||||
* Este método estático define o código de resposta HTTP como 404 e renderiza
|
||||
* a view "/Errors/404" para exibir a página de erro. Após a renderização,
|
||||
* o script é encerrado.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function pageNotFound(): void {
|
||||
switch (self::$ROUTER_MODE) {
|
||||
case 'VIEW':
|
||||
// Notifica erro em caso constante não definida
|
||||
if(!defined(constant_name: 'ERROR_404_VIEW_PATH')) {
|
||||
http_response_code(response_code: 500);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode( value: [
|
||||
"status" => 'error',
|
||||
"message" => "Constante 'ERROR_404_VIEW_PATH' não foi definida.",
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Caso o arquivo da constante não exista, notifica erro
|
||||
if(!file_exists(filename: ERROR_404_VIEW_PATH)) {
|
||||
http_response_code(response_code: 500);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode( value: [
|
||||
"status" => 'error',
|
||||
"message" => "Arquivo da constante 'ERROR_404_VIEW_PATH' não foi encontrado.",
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(response_code: 404);
|
||||
require_once ERROR_404_VIEW_PATH;
|
||||
break;
|
||||
|
||||
case 'JSON':
|
||||
http_response_code(response_code: 404);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode( value: [
|
||||
"status" => 'error',
|
||||
"message" => "Página não encontrada.",
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
140
vendor/claudecio/axiumphp/src/Core/View.php
vendored
Executable file
140
vendor/claudecio/axiumphp/src/Core/View.php
vendored
Executable file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
namespace AxiumPHP\Core;
|
||||
|
||||
use Exception;
|
||||
|
||||
class View {
|
||||
private array $requiredConstants = [
|
||||
'VIEW_PATH',
|
||||
];
|
||||
|
||||
/**
|
||||
* Construtor que vai garantir que as constantes necessárias estejam definidas antes de
|
||||
* instanciar a view.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Verificar as constantes no momento da criação da instância
|
||||
$this->checkRequiredConstants();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se todas as constantes necessárias estão definidas.
|
||||
*
|
||||
* @throws Exception Se alguma constante necessária não estiver definida.
|
||||
*/
|
||||
private function checkRequiredConstants(): void {
|
||||
foreach ($this->requiredConstants as $constant) {
|
||||
if (!defined(constant_name: $constant)) {
|
||||
throw new Exception(message: "Constante '{$constant}' não definida.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca o nome real de uma subpasta dentro de um diretório base,
|
||||
* ignorando a diferença entre maiúsculas e minúsculas.
|
||||
*
|
||||
* Este método privado recebe um `$basePath` (o diretório onde procurar)
|
||||
* e um `$targetName` (o nome da pasta desejada). Primeiro, verifica se o
|
||||
* `$basePath` é um diretório válido. Se não for, retorna null. Em seguida,
|
||||
* lê todos os arquivos e pastas dentro do `$basePath`. Para cada entrada,
|
||||
* compara o nome da entrada com o `$targetName` de forma case-insensitive.
|
||||
* Se encontrar uma entrada que corresponda ao `$targetName` (ignorando o case)
|
||||
* e que seja um diretório, retorna o nome da entrada com o seu case real.
|
||||
* Se após verificar todas as entradas nenhuma pasta correspondente for
|
||||
* encontrada, retorna null.
|
||||
*
|
||||
* @param string $basePath O caminho para o diretório base onde a subpasta será procurada.
|
||||
* @param string $targetName O nome da subpasta a ser encontrada (a comparação é case-insensitive).
|
||||
* @return string|null O nome real da pasta (com o case correto) se encontrada, ou null caso contrário.
|
||||
*/
|
||||
private static function getRealFolderName(string $basePath, string $targetName): ?string {
|
||||
if (!is_dir(filename: $basePath)) return null;
|
||||
|
||||
$entries = scandir(directory: $basePath);
|
||||
foreach ($entries as $entry) {
|
||||
if (strcasecmp(string1: $entry, string2: $targetName) === 0 && is_dir(filename: $basePath . '/' . $entry)) {
|
||||
return $entry; // Nome com o case real
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderiza uma view dentro de um layout, com suporte a módulos.
|
||||
*
|
||||
* Este método estático inclui o arquivo de view especificado, permitindo a
|
||||
* passagem de dados para a view através do array `$data`. As variáveis
|
||||
* do array `$data` são extraídas para uso dentro da view usando `extract()`.
|
||||
* O método também permite especificar um layout e um módulo para a view.
|
||||
*
|
||||
* @param string $view O caminho para o arquivo de view, relativo ao diretório
|
||||
* `views` ou `modules/{$module}/views` (ex:
|
||||
* 'usuarios/listar', 'index').
|
||||
* @param array $data Um array associativo contendo os dados a serem passados
|
||||
* para a view. As chaves do array se tornarão variáveis
|
||||
* disponíveis dentro da view.
|
||||
* @param string $layout O caminho para o arquivo de layout, relativo ao
|
||||
* diretório `views` (ex: 'layouts/main').
|
||||
* @param string $module O nome do módulo ao qual a view pertence (opcional).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function render(string $view, array $data = [], ?string $layout = null, ?string $module = null): void {
|
||||
$viewPath = VIEW_PATH . "/{$view}.php";
|
||||
|
||||
// Se for módulo, resolve o nome da pasta do módulo e da pasta Views
|
||||
if ($module) {
|
||||
$realModule = self::getRealFolderName(basePath: MODULE_PATH, targetName: $module);
|
||||
if (!$realModule) {
|
||||
http_response_code(response_code: 404);
|
||||
die("Módulo '{$module}' não encontrado.");
|
||||
}
|
||||
|
||||
$realViews = self::getRealFolderName(basePath: MODULE_PATH . "/{$realModule}", targetName: 'Views');
|
||||
if (!$realViews) {
|
||||
http_response_code(response_code: 404);
|
||||
die("Pasta 'Views' do módulo '{$module}' não encontrada.");
|
||||
}
|
||||
|
||||
$moduleViewPath = MODULE_PATH . "/{$realModule}/{$realViews}/{$view}.php";
|
||||
if (file_exists(filename: $moduleViewPath)) {
|
||||
$viewPath = $moduleViewPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists(filename: $viewPath)) {
|
||||
http_response_code(response_code: 404);
|
||||
die("View '{$view}' não encontrada.");
|
||||
}
|
||||
|
||||
if (!empty($data)) {
|
||||
extract($data, EXTR_SKIP);
|
||||
}
|
||||
|
||||
ob_start();
|
||||
require_once $viewPath;
|
||||
$content = ob_get_clean();
|
||||
|
||||
if ($layout) {
|
||||
if ($module) {
|
||||
// Mesmo esquema pra layout dentro de módulo
|
||||
$realModule = self::getRealFolderName(basePath: MODULE_PATH, targetName: $module);
|
||||
$realViews = self::getRealFolderName(basePath: MODULE_PATH . "/{$realModule}", targetName: 'Views');
|
||||
$layoutPath = MODULE_PATH . "/{$realModule}/{$realViews}/{$layout}.php";
|
||||
} else {
|
||||
$layoutPath = VIEW_PATH . "/{$layout}.php";
|
||||
}
|
||||
|
||||
if (file_exists(filename: $layoutPath)) {
|
||||
require_once $layoutPath;
|
||||
} else {
|
||||
http_response_code(response_code: 404);
|
||||
die("Layout '{$layout}' não encontrado.");
|
||||
}
|
||||
} else {
|
||||
echo $content;
|
||||
}
|
||||
}
|
||||
}
|
||||
108
vendor/claudecio/axiumphp/src/Helpers/NavigationHelper.php
vendored
Executable file
108
vendor/claudecio/axiumphp/src/Helpers/NavigationHelper.php
vendored
Executable file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
namespace AxiumPHP\Helpers;
|
||||
|
||||
class NavigationHelper {
|
||||
private static $max_stack;
|
||||
|
||||
/**
|
||||
* Construtor da classe que define a profundidade máxima da pilha.
|
||||
*
|
||||
* Este método inicializa a classe e configura a propriedade estática `self::$max_stack`
|
||||
* com o valor fornecido por `$max_stack`. Essa propriedade provavelmente
|
||||
* controla o número máximo de itens ou operações que podem ser armazenados em uma pilha.
|
||||
* O valor padrão é 5.
|
||||
*
|
||||
* @param int $max_stack O número máximo de itens permitidos na pilha. O valor padrão é 5.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(int $max_stack = 5) {
|
||||
self::$max_stack = $max_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rastreia a navegação do usuário, mantendo um histórico das páginas visitadas
|
||||
* na sessão.
|
||||
*
|
||||
* Este método estático obtém a URI atual da requisição. Se a URI corresponder
|
||||
* a um padrão de API ou chamada AJAX, a função retorna imediatamente sem
|
||||
* registrar a navegação.
|
||||
*
|
||||
* Se a variável de sessão 'navigation_stack' não existir, ela é inicializada
|
||||
* como um array vazio.
|
||||
*
|
||||
* Para evitar duplicatas, verifica se a URI atual é diferente da última URI
|
||||
* registrada na pilha de navegação. Se for diferente, e se a pilha atingir
|
||||
* um tamanho máximo definido por `self::$max_stack`, a URI mais antiga é removida
|
||||
* do início da pilha. A URI atual é então adicionada ao final da pilha.
|
||||
*
|
||||
* A URI atual também é armazenada na variável de sessão 'current_page', e a
|
||||
* página anterior (obtida através do método `self::getPreviousPage()`) é
|
||||
* armazenada em 'previous_page'.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @see self::getPreviousPage()
|
||||
*/
|
||||
public static function trackNavigation(): void {
|
||||
$currentUri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
|
||||
// Ignora chamadas para API ou AJAX
|
||||
if (preg_match(pattern: '/\/api\/|\/ajax\//i', subject: $currentUri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['navigation_stack'])) {
|
||||
$_SESSION['navigation_stack'] = [];
|
||||
}
|
||||
|
||||
// Evita duplicar a última página
|
||||
$last = end($_SESSION['navigation_stack']);
|
||||
if ($last !== $currentUri) {
|
||||
if (count(value: $_SESSION['navigation_stack']) >= self::$max_stack) {
|
||||
array_shift($_SESSION['navigation_stack']);
|
||||
}
|
||||
|
||||
$_SESSION['navigation_stack'][] = $currentUri;
|
||||
}
|
||||
|
||||
$_SESSION['current_page'] = $currentUri;
|
||||
$_SESSION['previous_page'] = self::getPreviousPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai a string de query da URI da requisição.
|
||||
*
|
||||
* Este método estático obtém a URI completa da requisição do servidor (`$_SERVER['REQUEST_URI']`) e a divide em duas partes usando o caractere `?` como separador. A primeira parte é a URI base, e a segunda é a string de query (os parâmetros da URL).
|
||||
*
|
||||
* @return string|null A string de query completa (por exemplo, "param1=value1¶m2=value2"), ou `null` se a URI não contiver uma string de query.
|
||||
*/
|
||||
public static function extractQueryString(): ?string {
|
||||
$parts = explode(separator: '?', string: $_SERVER['REQUEST_URI'], limit: 2); // limite 2 garante que só divide em duas partes
|
||||
$request = $parts[1] ?? null; // se não existir, define null
|
||||
|
||||
return ($request !== null && $request !== '') ? "?{$request}" : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtém a URI da página anterior visitada pelo usuário, com base na pilha
|
||||
* de navegação armazenada na sessão.
|
||||
*
|
||||
* Este método estático acessa a variável de sessão 'navigation_stack'. Se a
|
||||
* pilha contiver mais de uma URI, retorna a penúltima URI da pilha, que
|
||||
* representa a página visitada imediatamente antes da atual.
|
||||
*
|
||||
* Se a pilha contiver apenas uma ou nenhuma URI, significa que não há uma
|
||||
* página anterior no histórico de navegação da sessão, e o método retorna null.
|
||||
*
|
||||
* @return string|null A URI da página anterior, ou null se não houver uma.
|
||||
*/
|
||||
public static function getPreviousPage(): ?string {
|
||||
$stack = $_SESSION['navigation_stack'] ?? [];
|
||||
|
||||
if (count(value: $stack) > 1) {
|
||||
return $stack[count(value: $stack) - 2];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
270
vendor/claudecio/axiumphp/src/Helpers/RequestHelper.php
vendored
Executable file
270
vendor/claudecio/axiumphp/src/Helpers/RequestHelper.php
vendored
Executable file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
namespace AxiumPHP\Helpers;
|
||||
|
||||
class RequestHelper {
|
||||
|
||||
/**
|
||||
* Retorna os dados de entrada (GET, POST, COOKIE ou SERVER) filtrados.
|
||||
*
|
||||
* Permite passar filtros personalizados por campo. Se nenhum filtro for passado,
|
||||
* usa o filtro padrão (`FILTER_DEFAULT`).
|
||||
*
|
||||
* @param int $form_type Tipo de entrada (INPUT_GET, INPUT_POST, INPUT_COOKIE ou INPUT_SERVER).
|
||||
* @param array|null $filters Filtros personalizados no formato aceito por filter_input_array().
|
||||
* @return array Retorna um array associativo com os dados filtrados, ou array vazio se nenhum dado for encontrado.
|
||||
*/
|
||||
public static function getFilteredInput(int $form_type = INPUT_GET, ?array $filters = null): array {
|
||||
switch ($form_type) {
|
||||
case INPUT_GET:
|
||||
$form = $filters !== null ? filter_input_array(type: INPUT_GET, options: $filters) : filter_input_array(type: INPUT_GET);
|
||||
break;
|
||||
case INPUT_POST:
|
||||
$inputData = file_get_contents(filename: 'php://input');
|
||||
$data = [];
|
||||
|
||||
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
||||
|
||||
if (strpos(haystack: $contentType, needle: 'application/json') !== false) {
|
||||
$data = json_decode(json: $inputData, associative: true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
http_response_code(response_code: 500);
|
||||
header(header: "Content-Type: application/json; charset=utf-8");
|
||||
echo json_encode(value: [
|
||||
"success" => false,
|
||||
"message" => "Erro ao decodificar JSON: " . json_last_error_msg()
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if(isset($data['_method'])) {
|
||||
unset($data['_method']);
|
||||
}
|
||||
$form = $data;
|
||||
} else {
|
||||
$form = $filters !== null ? filter_input_array(type: INPUT_POST, options: $filters) : filter_input_array(type: INPUT_POST);
|
||||
}
|
||||
break;
|
||||
case INPUT_COOKIE:
|
||||
$form = $filters !== null ? filter_input_array(type: INPUT_COOKIE, options: $filters) : filter_input_array(type: INPUT_COOKIE);
|
||||
break;
|
||||
case INPUT_SERVER:
|
||||
$form = $filters !== null ? filter_input_array(type: INPUT_SERVER, options: $filters) : filter_input_array(type: INPUT_SERVER);
|
||||
break;
|
||||
default:
|
||||
$form = $filters !== null ? filter_input_array(type: INPUT_GET, options: $filters) : filter_input_array(type: INPUT_GET);
|
||||
break;
|
||||
}
|
||||
|
||||
return $form && is_array(value: $form) ? $form : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia uma resposta JSON padronizada e encerra a execução do script.
|
||||
*
|
||||
* Este método estático é um utilitário para endpoints de API, garantindo que as respostas
|
||||
* enviadas ao cliente sigam um formato consistente. Ele define o código de resposta HTTP,
|
||||
* o cabeçalho `Content-Type` e um corpo JSON estruturado.
|
||||
*
|
||||
* O corpo da resposta inclui sempre um `status` e uma `message`. Opcionalmente, pode
|
||||
* incluir um array de `data`. A validação interna garante que os parâmetros sejam
|
||||
* consistentes e que a execução seja interrompida após o envio da resposta.
|
||||
*
|
||||
* #### Validações e Comportamento:
|
||||
* - O parâmetro `status` é restrito a 'success', 'error' ou 'fail'. Se um valor inválido for passado,
|
||||
* ele será padronizado para 'error'.
|
||||
* - Se o parâmetro `message` estiver vazio, o código de resposta é alterado para `422` (Entidade
|
||||
* Não Processável), e uma mensagem de erro padrão é definida.
|
||||
* - O código de resposta HTTP é definido com `http_response_code()`.
|
||||
* - O cabeçalho `Content-Type` é configurado para `application/json; charset=utf-8`.
|
||||
* - O array de resposta é codificado para JSON e impresso.
|
||||
* - A execução do script é finalizada com `exit`.
|
||||
*
|
||||
* @param int $response_code O código de status HTTP da resposta (padrão: 200).
|
||||
* @param string $status O status da operação ('success', 'error' ou 'fail'). Padrão: 'success'.
|
||||
* @param string $message Uma mensagem descritiva da resposta. Este campo é obrigatório e será validado.
|
||||
* @param array $data Um array associativo opcional com os dados a serem retornados. Padrão: `[]`.
|
||||
* @return void Este método não tem retorno, pois ele finaliza a execução do script.
|
||||
*/
|
||||
public static function sendJsonResponse(int $response_code = 200, string $status = 'success', string $message = '', array $output = []): void {
|
||||
// Garante que o status seja válido
|
||||
if (!in_array(needle: $status, haystack: ['success', 'error', 'fail'])) {
|
||||
$status = 'error';
|
||||
}
|
||||
|
||||
// Se não tiver mensagem, é erro de uso do método
|
||||
if (empty($message)) {
|
||||
$response_code = 422;
|
||||
$status = 'error';
|
||||
$message = "Mensagem obrigatória não informada.";
|
||||
}
|
||||
|
||||
$responseArray = [
|
||||
'response_code' => $response_code,
|
||||
'status' => $status,
|
||||
'message' => $message
|
||||
];
|
||||
|
||||
if (isset($output['data']) && !empty($output['data'])) {
|
||||
$responseArray['data'] = $output['data'];
|
||||
}
|
||||
|
||||
if (isset($output['errors']) && !empty($output['errors'])) {
|
||||
$responseArray['errors'] = $output['errors'];
|
||||
}
|
||||
|
||||
http_response_code(response_code: $response_code);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(value: $responseArray);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula o valor de OFFSET para consultas de paginação SQL.
|
||||
*
|
||||
* Este método estático recebe o número da página desejada (`$page`) e o
|
||||
* número de itens por página (`$limit`). Ele calcula o valor de OFFSET
|
||||
* que deve ser usado em uma consulta SQL para buscar os registros corretos
|
||||
* para a página especificada. O OFFSET é calculado como `($page - 1) * $limit`.
|
||||
* A função `max(0, ...)` garante que o OFFSET nunca seja negativo, o que
|
||||
* pode acontecer se um valor de página menor que 1 for passado.
|
||||
*
|
||||
* @param int $page O número da página para a qual se deseja calcular o OFFSET (a primeira página é 1).
|
||||
* @param int $limit O número de itens a serem exibidos por página.
|
||||
* @return int O valor de OFFSET a ser utilizado na cláusula LIMIT de uma consulta SQL.
|
||||
*/
|
||||
public static function getOffset(int $page, int $limit): int {
|
||||
return max(0, ($page - 1) * $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepara e formata os resultados de uma consulta paginada.
|
||||
*
|
||||
* Este método estático organiza os dados de uma resposta de banco de dados,
|
||||
* juntamente com informações de paginação, em um formato padronizado.
|
||||
* Ele é útil para enviar dados para a camada de visualização (frontend)
|
||||
* de forma consistente.
|
||||
*
|
||||
* @param mixed $atuallPage O número da página atual. Pode ser um `int` ou `string`.
|
||||
* @param array $dbResponse Um array contendo os resultados da consulta ao banco de dados para a página atual.
|
||||
* @param string $totalResults O número total de resultados encontrados pela consulta, antes da aplicação do limite de paginação. Deve ser uma `string` (será convertido para `float`).
|
||||
* @param mixed $limit O limite de resultados por página. Pode ser um `int` ou `string` (padrão é "10").
|
||||
* @return array Um array associativo contendo:
|
||||
* - `atuallPage`: A página atual.
|
||||
* - `response`: Os dados da resposta do banco de dados para a página.
|
||||
* - `totalResults`: O total de resultados, convertido para `float`.
|
||||
* - `limit`: O limite de resultados por página.
|
||||
*/
|
||||
public static function preparePaginationResult(mixed $atuallPage, array $dbResponse, string $totalResults, mixed $limit = "10"): array {
|
||||
return [
|
||||
'atuallPage' => $atuallPage,
|
||||
'response' => $dbResponse,
|
||||
'totalResults' => floatval(value: $totalResults),
|
||||
'limit' => $limit
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera HTML para um componente de paginação.
|
||||
*
|
||||
* Este método estático recebe a página atual, o total de registros e um limite
|
||||
* de registros por página para gerar uma navegação de paginação em HTML,
|
||||
* utilizando classes do Bootstrap para estilização.
|
||||
*
|
||||
* Primeiro, calcula o número total de páginas. Em seguida, constrói a query
|
||||
* string da URL atual, removendo os parâmetros 'page' e 'url' para evitar
|
||||
* duplicações nos links de paginação.
|
||||
*
|
||||
* Limita o número máximo de botões de página exibidos e calcula o início e
|
||||
* o fim da janela de botões a serem mostrados, ajustando essa janela para
|
||||
* garantir que o número máximo de botões seja exibido, dentro dos limites
|
||||
* do total de páginas.
|
||||
*
|
||||
* Se o total de registros for maior que o limite por página, o HTML da
|
||||
* paginação é gerado, incluindo botões "Anterior" (desabilitado na primeira
|
||||
* página), os números das páginas dentro da janela calculada (com a página
|
||||
* atual marcada como ativa) e um botão "Próximo" (desabilitado na última
|
||||
* página). Se o total de registros não for maior que o limite, uma string
|
||||
* vazia é retornada.
|
||||
*
|
||||
* @param int $current_page O número da página atualmente visualizada.
|
||||
* @param int $total_rows O número total de registros disponíveis.
|
||||
* @param int $limit O número máximo de registros a serem exibidos por página (padrão: 20).
|
||||
*
|
||||
* @return string O HTML da navegação de paginação, estilizado com classes do Bootstrap,
|
||||
* ou uma string vazia se não houver necessidade de paginação.
|
||||
*/
|
||||
public static function generatePaginationHtml(int $current_page, int $total_rows, int $limit = 20): string {
|
||||
$current_page ??= 1;
|
||||
$total_rows ??= 0;
|
||||
|
||||
// Calcula o total de páginas
|
||||
$total_paginas = ceil(num: $total_rows / $limit);
|
||||
|
||||
// Construir a query string com os parâmetros atuais, exceto 'page'
|
||||
$query_params = $_GET;
|
||||
unset($query_params['page']); // Remove 'page' para evitar duplicação
|
||||
unset($query_params['url']); // Remove 'url' para evitar duplicação
|
||||
$query_string = http_build_query(data: $query_params);
|
||||
|
||||
// Limitar a quantidade máxima de botões a serem exibidos
|
||||
$max_botoes = 10;
|
||||
$inicio = max(1, $current_page - intval(value: $max_botoes / 2));
|
||||
$fim = min($total_paginas, $inicio + $max_botoes - 1);
|
||||
|
||||
// Ajustar a janela de exibição se atingir o limite inferior ou superior
|
||||
if ($fim - $inicio + 1 < $max_botoes) {
|
||||
$inicio = max(1, $fim - $max_botoes + 1);
|
||||
}
|
||||
|
||||
// Validação das paginações
|
||||
if($total_rows > $limit){
|
||||
// Inicia a criação do HTML da paginação
|
||||
$html = "<nav aria-label='Page navigation'>";
|
||||
$html .= "<ul class='pagination justify-content-center'>";
|
||||
|
||||
// Botão Anterior (desabilitado na primeira página)
|
||||
if ($current_page > 1) {
|
||||
$anterior = $current_page - 1;
|
||||
$html .= "<li class='page-item'>";
|
||||
$html .= "<a class='page-link' href='?{$query_string}&page={$anterior}'>Anterior</a>";
|
||||
$html .= "</li>";
|
||||
} else {
|
||||
$html .= "<li class='page-item disabled'>";
|
||||
$html .= "<a class='page-link'>Anterior</a>";
|
||||
$html .= "</li>";
|
||||
}
|
||||
|
||||
// Geração dos links de cada página dentro da janela definida
|
||||
for ($i = $inicio; $i <= $fim; $i++) {
|
||||
if ($i == $current_page) {
|
||||
$html .= "<li class='page-item active'>";
|
||||
$html .= "<a class='page-link' href='?{$query_string}&page={$i}'>{$i}</a>";
|
||||
$html .= "</li>";
|
||||
} else {
|
||||
$html .= '<li class="page-item">';
|
||||
$html .= "<a class='page-link' href='?{$query_string}&page={$i}'>{$i}</a>";
|
||||
$html .= "</li>";
|
||||
}
|
||||
}
|
||||
|
||||
// Botão Próximo (desabilitado na última página)
|
||||
if ($current_page < $total_paginas) {
|
||||
$proxima = $current_page + 1;
|
||||
$html .= "<li class='page-item'>";
|
||||
$html .= "<a class='page-link' href='?{$query_string}&page={$proxima}'>Próximo</a>";
|
||||
$html .= "</li>";
|
||||
} else {
|
||||
$html .= "<li class='page-item disabled'>";
|
||||
$html .= "<a class='page-link'>Próximo</a>";
|
||||
$html .= "</li>";
|
||||
}
|
||||
|
||||
$html .= "</ul>";
|
||||
$html .= "</nav>";
|
||||
} else {
|
||||
$html = "";
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user