first commit

This commit is contained in:
Claudecio Martins
2025-11-04 18:22:02 +01:00
commit c1184d2878
4394 changed files with 444123 additions and 0 deletions

5
.gitignore vendored Executable file
View File

@@ -0,0 +1,5 @@
# Arquivo de variáveis de ambiente
.env
# Pasta de Logs
app/Storage/logs

42
app/Common/Config/Config.php Executable file
View File

@@ -0,0 +1,42 @@
<?php
namespace Zampet\Config;
use Dotenv\Dotenv;
class Config {
private $config = [];
private static $instance = null;
/**
* Método construtor
*/
public function __construct() {
// Carrega apenas para $_ENV, não para $_SERVER
$dotEnv = Dotenv::createUnsafeImmutable(
paths: realpath(path: __DIR__ . "/../../../"),
names: '.env'
);
$dotEnv->safeLoad(); // não dá erro se .env não existir
// Carrega as configurações
$this->config = require_once realpath(path: __DIR__ . "/Consts.php");
}
/**
* Retorna a instância única da classe
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Config();
}
return self::$instance;
}
/**
* Retorna uma configuração
*/
public function get(string $key) {
return $this->config[$key] ?? null;
}
}

21
app/Common/Config/Consts.php Executable file
View File

@@ -0,0 +1,21 @@
<?php
return [
'SYSTEM_URL' => (string) getenv(name: 'SYSTEM_URL'),
'SYSTEM_VERSION' => (string) getenv(name: 'SYSTEM_VERSION'),
'SYSTEM_TIMEZONE' => (string) getenv(name: 'SYSTEM_TIMEZONE'),
'SYSTEM_ENVIRONMENT_ID' => (int) getenv(name: 'SYSTEM_ENVIRONMENT_ID'),
'SYSTEM_FRONTEND_URL' => (string) getenv(name: 'SYSTEM_FRONTEND_URL'),
'JWT_ALGO' => getenv(name: 'JWT_ALGO'),
'JWT_ISSUER' => getenv(name: 'JWT_ISSUER'),
'JWT_SECRET' => getenv(name: 'JWT_SECRET'),
'JWT_EXPIRATION' => (int) getenv(name: 'JWT_EXPIRATION'),
'DEFAULT_DATABASE_HOST' => getenv(name: 'DEFAULT_DATABASE_HOST'),
'DEFAULT_DATABASE_PORT' => getenv(name: 'DEFAULT_DATABASE_PORT'),
'DEFAULT_DATABASE_DRIVER' => getenv(name: 'DEFAULT_DATABASE_DRIVER'),
'DEFAULT_DATABASE_SCHEMA' => getenv(name: 'DEFAULT_DATABASE_SCHEMA'),
'DEFAULT_DATABASE_CHARSET' => getenv(name: 'DEFAULT_DATABASE_CHARSET'),
'DEFAULT_DATABASE_USERNAME' => getenv(name: 'DEFAULT_DATABASE_USERNAME'),
'DEFAULT_DATABASE_PASSWORD' => getenv(name: 'DEFAULT_DATABASE_PASSWORD'),
];

View File

@@ -0,0 +1,174 @@
<?php
namespace Zampet\Helpers;
class DataValidator {
private array $inputs;
private array $errors = [];
public function __construct(array $inputs) {
$this->inputs = $inputs;
}
/**
* Valida um campo usando uma regra específica.
*/
public function validate(string $field, callable|string $rule, string $message = '', array $additionalParams = []): static {
$value = $this->inputs[$field] ?? null;
// Se for string, monta callable usando a própria classe
if (is_string(value: $rule) && method_exists(object_or_class: self::class, method: $rule)) {
$rule = [self::class, $rule];
}
$params = array_merge([$value], $additionalParams);
$result = call_user_func_array(callback: $rule, args: $params);
// Caso seja validação de senha (retorna array)
if (is_array(value: $result) && isset($result['valid'])) {
if (!$result['valid']) {
$this->errors[$field] = [
'status' => 'is_invalid',
'message' => $result['errors']
];
}
}
// Para outras validações booleanas
elseif ($result === false) {
$this->errors[$field] = [
'status' => 'is_invalid',
'message' => $message
];
}
return $this;
}
public function getErrors(): array {
return $this->errors;
}
public function passes(): bool {
return empty($this->errors);
}
// ---------- Regras comuns ----------
public static function validateEmail(string $value): bool {
return (bool) filter_var(value: $value, filter: FILTER_VALIDATE_EMAIL);
}
public static function validatePhone(string $value, int $minLength = 10, int $maxLength = 13): bool {
$digits = preg_replace(pattern: '/\D+/', replacement: '', subject: $value);
$len = strlen(string: $digits);
return $len >= $minLength && $len <= $maxLength;
}
public static function validateCpf(string $value): bool {
$cpf = preg_replace(pattern: '/\D+/', replacement: '', subject: $value);
if (strlen(string: $cpf) !== 11 || preg_match(pattern: '/(\d)\1{10}/', subject: $cpf)) {
return false;
}
for ($t = 9; $t < 11; $t++) {
$d = 0;
for ($c = 0; $c < $t; $c++) {
$d += $cpf[$c] * (($t + 1) - $c);
}
$d = ((10 * $d) % 11) % 10;
if ($cpf[$c] != $d) {
return false;
}
}
return true;
}
public static function validateCnpj(string $value): bool {
$cnpj = preg_replace(pattern: '/\D+/', replacement: '', subject: $value);
if (strlen(string: $cnpj) != 14) {
return false;
}
if (preg_match(pattern: '/(\d)\1{13}/', subject: $cnpj)) {
return false;
}
$tamanho = 12;
$multiplicadores = [5,4,3,2,9,8,7,6,5,4,3,2];
for ($i = 0; $i < 2; $i++) {
$soma = 0;
for ($j = 0; $j < $tamanho; $j++) {
$soma += $cnpj[$j] * $multiplicadores[$j + ($i == 1 ? 1 : 0)];
}
$digito = ($soma % 11 < 2) ? 0 : 11 - ($soma % 11);
if ($cnpj[$tamanho] != $digito) {
return false;
}
$tamanho++;
}
return true;
}
public static function notEmpty(mixed $value): bool {
if (is_array(value: $value)) {
return !empty(array_filter(array: $value, callback: fn($v) => $v !== null && $v !== ''));
}
return !empty(trim(string: (string) $value));
}
public static function validatePassword(string $password, array $rules = []): array {
$errors = [];
$minLength = $rules['minLength'] ?? 8;
$maxLength = $rules['maxLength'] ?? 64;
$requireUpper = $rules['upper'] ?? true;
$requireLower = $rules['lower'] ?? true;
$requireDigit = $rules['digit'] ?? true;
$requireSymbol = $rules['symbol'] ?? true;
if (strlen(string: $password) < $minLength) {
$errors[] = "Senha deve ter no mínimo {$minLength} caracteres.";
}
if (strlen(string: $password) > $maxLength) {
$errors[] = "Senha deve ter no máximo {$maxLength} caracteres.";
}
if ($requireUpper && !preg_match(pattern: '/[A-Z]/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos uma letra maiúscula.";
}
if ($requireLower && !preg_match(pattern: '/[a-z]/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos uma letra minúscula.";
}
if ($requireDigit && !preg_match(pattern: '/\d/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos um número.";
}
if ($requireSymbol && !preg_match(pattern: '/[\W_]/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos um símbolo.";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
public static function validateList(mixed $value, array $validOptions): bool {
if (is_array(value: $value)) {
foreach ($value as $item) {
if (!in_array(needle: $item, haystack: $validOptions, strict: true)) {
return false;
}
}
return true;
}
return in_array(needle: $value, haystack: $validOptions, strict: true);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Zampet\Helpers;
use DateTime;
class Sanitizer {
public static function string(mixed $value): string|null {
if(is_null($value)) {
return null;
}
return trim(string: strip_tags(string: $value));
}
public static function int(mixed $value): int {
return filter_var(value: $value, filter: FILTER_VALIDATE_INT) ?? 0;
}
public static function float(mixed $value): float {
return filter_var(value: $value, filter: FILTER_VALIDATE_FLOAT) ?? 0.0;
}
public static function email(string $value): string {
$value = trim(string: strtolower(string: $value));
return filter_var(value: $value, filter: FILTER_VALIDATE_EMAIL) ?: '';
}
public static function phone(string $value): string {
// remove tudo que não for número
return preg_replace(pattern: '/\D+/', replacement: '', subject: $value) ?? '';
}
public static function document(string $value): string {
// CPF, CNPJ, RG etc. só números
return preg_replace(pattern: '/\D+/', replacement: '', subject: $value) ?? '';
}
public static function date(string $value, string $format = 'Y-m-d'): string {
$value = trim($value);
if ($value === '') {
return '';
}
$date = DateTime::createFromFormat('!'.$format, $value);
if (!$date) {
// tenta converter qualquer formato válido
$date = date_create($value);
}
return $date ? $date->format($format) : '';
}
public static function datetime(string $value, string $format = 'Y-m-d H:i:s'): string {
$value = trim($value);
if ($value === '') {
return '';
}
$date = DateTime::createFromFormat('!'.$format, $value);
if (!$date) {
$date = date_create($value);
}
return $date ? $date->format($format) : '';
}
}

8
app/Common/Services/DB.php Executable file
View File

@@ -0,0 +1,8 @@
<?php
namespace Zampet\Services;
use AxiumPHP\Core\Database;
class DB extends Database {
}

View File

@@ -0,0 +1,172 @@
<?php
namespace Zampet\Services;
use DateTime;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Zampet\Config\Config;
class JWTService {
private static string $issuer;
private static int $expiration;
private static string $algorithm;
private static string $secretKey;
/**
* Inicializa as configurações para a geração de tokens JWT.
*
* Este método estático carrega as informações necessárias para a criação e validação de tokens
* JWT (JSON Web Tokens) a partir de uma classe de configuração (`Config`). Ele busca a chave secreta,
* o algoritmo de criptografia, o emissor e o tempo de expiração definidos para a aplicação
* e os armazena como propriedades estáticas da classe.
*
* Este método deve ser chamado antes de qualquer operação que envolva a geração ou
* verificação de tokens, garantindo que as configurações estejam prontas para uso.
*
* @return void
*/
public static function init(): void {
$Config = Config::getInstance();
self::$issuer = $Config->get(key: 'JWT_ISSUER');
self::$algorithm = $Config->get(key: 'JWT_ALGO');
self::$secretKey = $Config->get(key: 'JWT_SECRET');
self::$expiration = $Config->get(key: 'JWT_EXPIRATION');
}
/**
* Gera um token JWT com base em um payload e nas configurações da aplicação.
*
* Este método cria um token JWT válido a partir de um array de dados fornecido (`$payload`),
* adicionando automaticamente metadados essenciais como a data de emissão, a data de expiração e o emissor.
*
* O processo detalhado inclui:
* 1. **Inicialização:** Chama o método `init()` para garantir que todas as configurações do JWT
* (chave secreta, algoritmo, etc.) estejam carregadas.
* 2. **Definição dos Timestamps:** Calcula o timestamp de emissão (`iat`) e o de expiração (`exp`)
* com base no tempo de expiração definido nas configurações.
* 3. **Montagem do Payload:** Adiciona as chaves `iat`, `exp` e `iss` (emissor) ao array `$payload`.
* 4. **Codificação do Token:** Utiliza a biblioteca `JWT::encode()` para codificar o payload
* com a chave secreta e o algoritmo definidos nas configurações.
*
* @param array $payload Um array associativo contendo os dados personalizados que serão incluídos no token.
* @return string O token JWT gerado como uma string.
*/
public static function generateToken(array $payload): string {
// Carrega as informações do arquivo de configuração
self::init();
// Define data de emissão e calcula a data de expiração do token
$expiration = self::$expiration;
$createdAt = (new DateTime())->getTimestamp();
$expireAt = (new DateTime())->modify(modifier: "+{$expiration} seconds")->getTimestamp();
// Adiciona data de criação ao payload
$payload['iat'] = $createdAt;
// Adiciona a expiração ao payload
$payload['exp'] = $expireAt;
// Adiciona o emissor ao payload
$payload['iss'] = self::$issuer;
return JWT::encode(payload: $payload, key: self::$secretKey, alg: self::$algorithm);
}
/**
* Valida um token JWT e retorna o objeto de payload decodificado.
*
* Este método estático decodifica e valida um token JWT fornecido, utilizando
* a chave secreta e o algoritmo de criptografia configurados na classe.
*
* #### Como funciona:
* 1. Recebe o token (`$token`) que precisa ser validado.
* 2. Usa a biblioteca `JWT::decode()` para decodificar o token.
* 3. Para a decodificação, ele cria uma nova instância de `Key`, passando
* a chave secreta (`self::$secretKey`) e o algoritmo (`self::$algorithm`).
* Isso garante que o token só será decodificado se tiver sido assinado com a chave e algoritmo corretos.
* 4. Se o token for válido e a assinatura corresponder, a função retorna o payload do token
* como um objeto.
* 5. Se o token for inválido (por exemplo, assinatura incorreta, expirado ou formato incorreto),
* a biblioteca JWT lançará uma exceção, que a função que chama este método deve capturar.
*
* @param string $token O token JWT a ser validado e decodificado.
* @return object O objeto de payload decodificado do token.
*/
public static function validateToken(string $token): object {
// Inicializa as configurações se ainda não estiverem definidas
if (!isset(self::$secretKey)) {
self::init();
}
return JWT::decode(jwt: $token, keyOrKeyArray: new Key(keyMaterial: self::$secretKey, algorithm: self::$algorithm));
}
/**
* Decodifica um token JWT e retorna seu payload como um array associativo.
*
* Este método estático é responsável por decodificar e validar um token JWT. Antes de decodificar, ele garante que as configurações de chave secreta e algoritmo (`self::$secretKey` e `self::$algorithm`) já foram inicializadas chamando o método `init()` se necessário.
*
* #### Como Funciona:
* 1. **Inicialização:** Primeiro, verifica se a chave secreta (`self::$secretKey`) já foi carregada. Se não, ele chama `self::init()` para carregar as configurações de um arquivo externo.
* 2. **Decodificação:** Usa a biblioteca `Firebase\JWT\JWT::decode()` para decodificar o token fornecido. O método passa a chave secreta e o algoritmo para verificar a assinatura do token.
* 3. **Conversão:** O payload decodificado, que por padrão é um objeto, é convertido para uma string JSON e depois para um array associativo, o que facilita o acesso aos dados.
* 4. **Retorno:** Retorna o payload do token como um array associativo. Se o token for inválido (assinatura incorreta, expirado, etc.), a biblioteca JWT lançará uma exceção, que a função que chama este método deve capturar.
*
* @param string $token O token JWT a ser decodificado.
* @return array O payload do token decodificado como um array associativo.
*/
public static function decode(string $token): array {
// Inicializa as configurações se ainda não estiverem definidas
if (!isset(self::$secretKey)) {
self::init();
}
$decoded = JWT::decode(jwt: $token, keyOrKeyArray: new Key(keyMaterial: self::$secretKey, algorithm: self::$algorithm));
return json_decode(json: json_encode(value: $decoded), associative: true);
}
/**
* Extrai o token de autenticação do tipo 'Bearer' do cabeçalho da requisição HTTP.
*
* Este método estático é um utilitário projetado para buscar um token JWT (JSON Web Token)
* que é enviado no cabeçalho `Authorization`. Ele é robusto o suficiente para verificar
* o token em diferentes superglobais do PHP, dependendo da configuração do servidor web.
*
* #### Fluxo de Busca:
* 1. Primeiro, tenta obter o cabeçalho `Authorization` diretamente da superglobal `$_SERVER`.
* 2. Em seguida, tenta a chave `HTTP_AUTHORIZATION`, que é um nome de variável comum em alguns ambientes.
* 3. Por último, usa a função `apache_request_headers()` para tentar obter o cabeçalho, caso as
* superglobais não estejam preenchidas. Isso garante compatibilidade com servidores Apache onde o cabeçalho pode não ser exposto.
* 4. Se o cabeçalho for encontrado, o valor é sanitizado (removendo espaços em branco) e a busca continua.
* 5. Se nenhum cabeçalho de autenticação for encontrado, o método retorna `null`.
*
* #### Extração do Token:
* - Depois de encontrar o cabeçalho, o método usa uma expressão regular (`/Bearer\s(\S+)/`)
* para verificar se o valor do cabeçalho está no formato `Bearer <token>`.
* - Se o padrão corresponder, ele extrai e retorna a string do token.
* - Se o padrão não corresponder, o método retorna `null`.
*
* @return string|null O token de autenticação do tipo 'Bearer' como uma string, ou `null`
* se o cabeçalho não for encontrado ou não estiver no formato esperado.
*/
public static function getBearerToken(): ?string {
if (!isset(self::$secretKey)) {
self::init();
}
// Tenta pegar o header de todas as fontes possíveis
$headers = $_SERVER['Authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? (function_exists(function: 'apache_request_headers') ? apache_request_headers() : []);
// Se veio do apache_request_headers, normaliza chaves
if (is_array(value: $headers)) {
$headers = array_change_key_case(array: $headers, case: CASE_LOWER);
$headers = $headers['authorization'] ?? '';
}
$headers = trim(string: $headers);
if ($headers && preg_match(pattern: '/Bearer\s(\S+)/', subject: $headers, matches: $matches)) {
return $matches[1];
}
return null;
}
}

0
app/Module/Auth/blank_file Executable file
View File

View File

@@ -0,0 +1,158 @@
<?php
namespace Zampet\Module\Auth\v0\Controllers;
use Zampet\Helpers\Sanitizer;
use Zampet\Services\JWTService;
use AxiumPHP\Helpers\RequestHelper;
use Zampet\Module\Auth\v0\DTO\UsuarioDTO;
use Zampet\Module\Auth\v0\Services\AuthService;
class AuthController {
/**
* Lida com a requisição de registro (criação) de um novo usuário.
*
* Este método atua como um **controlador** para o endpoint de registro. Ele recebe os dados do formulário, realiza uma validação de presença básica nos campos obrigatórios, sanitiza os dados e delega a lógica de negócio e persistência para o `AuthService`. A resposta final é formatada e enviada como JSON.
*
* #### Fluxo de Operação:
* 1. **Coleta de Dados:** Obtém os dados do formulário (`fullName`, `email`, `password`) via requisição POST.
* 2. **Validação de Presença:** Itera sobre os campos obrigatórios (`fullName`, `email`, `password`). Se qualquer um estiver vazio, o método envia uma resposta JSON com o código **400 Bad Request** e encerra a execução.
* 3. **Sanitização e DTO:** Os dados são sanitizados (string, email) e encapsulados em um **`UsuarioDTO`**.
* 4. **Delegação ao Serviço:** O método `register` do `AuthService` é chamado com o DTO. O serviço é responsável por validações de complexidade, checagem de duplicidade, criptografia de senha e persistência no banco de dados.
* 5. **Envio da Resposta JSON:** O resultado do serviço (`serviceResult`) é formatado e enviado de volta ao cliente. O código de resposta HTTP e o status JSON são extraídos do array retornado pelo serviço.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function register(): void {
// Receber dados do formulário de registro
$form = RequestHelper::getFilteredInput(form_type: INPUT_POST);
// Dados Obrigatórios
$required_fields = [
'fullName',
'email',
'cpf',
'birthDate',
'password'
];
// Verificar se todos os campos obrigatórios estão presentes
foreach ($required_fields as $field) {
if (empty($form[$field])) {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Field '{$field}' is required"
);
exit;
}
}
// Chama o serviço de autenticação para registrar o usuário
$serviceResult = (new AuthService())->register(usuarioDTO:
new UsuarioDTO(data: [
'nome_completo' => Sanitizer::string(value: $form['fullName']),
'email' => Sanitizer::email(value: $form['email']),
'senha' => Sanitizer::string(value: $form['password']),
'documentcpf' => Sanitizer::document(value: $form['cpf']),
'data_nascimento' => Sanitizer::string(value: $form['birthDate'])
])
);
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
/**
* Lida com a requisição de login de um usuário via POST.
*
* Este método atua como um **controlador** para o endpoint de login. Ele processa as credenciais enviadas pelo usuário, realiza uma validação de presença básica e delega o processo de autenticação a um serviço. A resposta da autenticação é então formatada e enviada de volta ao cliente como um JSON.
*
* #### Fluxo de Operação:
* 1. **Coleta de Dados:** Obtém os dados do formulário (`email` e `password`) enviados via requisição POST.
* 2. **Validação de Presença:** Itera sobre os campos obrigatórios. Se qualquer 3campo estiver vazio, uma resposta JSON com o código **400 Bad Request** é enviada e a execução é encerrada.
* 3. **Delegação ao Serviço:** O método `login` do **`AuthService`** é chamado com o e-mail e a senha fornecidos. O serviço é responsável por toda a lógica de autenticação (busca, verificação de senha, revogação de tokens e geração de novo JWT).
* 4. **Envio da Resposta JSON:** O resultado do serviço (`serviceResult`) é formatado para uma resposta JSON padronizada usando **`RequestHelper::sendJsonResponse`**, incluindo o código HTTP, status, mensagem e os dados de saída (como o token JWT). A execução é encerrada.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function login(): void {
// Receber dados do formulário de login
$form = RequestHelper::getFilteredInput(form_type: INPUT_POST);
// Dados Obrigatórios
$required_fields = [
'email',
'password'
];
// Verificar se todos os campos obrigatórios estão presentes
foreach ($required_fields as $field) {
if (empty($form[$field])) {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Field '{$field}' is required"
);
exit;
}
}
// Chamar o serviço de autenticação para fazer login
$serviceResult = (new AuthService())->login(email: $form['email'], password: $form['password']);
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
/**
* Lida com a requisição de logout, revogando o token de autenticação do usuário.
*
* Este método atua como um **controlador** para o endpoint de logout. Ele extrai o token JWT do cabeçalho da requisição, delega a lógica de revogação para a camada de serviço e envia uma resposta JSON formatada de volta ao cliente.
*
* #### Fluxo de Operação:
* 1. **Obtenção do Token:** O token Bearer é extraído do cabeçalho da requisição usando `JWTService::getBearerToken()`.
* 2. **Delegação ao Serviço:** O método `logout` do **`AuthService`** é chamado com o token. O serviço é responsável por revogar o token no banco de dados e gerenciar a transação.
* 3. **Envio da Resposta JSON:** O resultado da operação do serviço (`serviceResult`) é formatado para uma resposta JSON padronizada usando **`RequestHelper::sendJsonResponse`**, garantindo que o código HTTP, status, mensagem e quaisquer dados de saída sejam comunicados ao cliente.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function logout(): void {
// Chamar o serviço de autenticação para fazer logout
$serviceResult = (new AuthService())->logout(token: JWTService::getBearerToken());
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
public function getUserData(): void {
// Chamar o serviço de autenticação para obter os dados do usuário
$serviceResult = (new AuthService())->getUserData(token: JWTService::getBearerToken());
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
}

View File

@@ -0,0 +1,237 @@
<?php
namespace Zampet\Module\Auth\v0\DTO;
class UsuarioDTO {
private ?int $id = null;
private ?string $uuid = null;
private ?string $nome_completo = null;
private ?int $status_id = 1;
private ?string $documentcpf = null;
private ?string $documentcrmv = null;
private ?string $email = null;
private ?string $senha = null;
private ?string $telefone = null;
private ?string $data_nascimento = null;
private ?string $endereco_rua = null;
private ?string $endereco_numero = null;
private ?string $endereco_complemento = null;
private ?string $endereco_bairro = null;
private ?string $endereco_cidade = null;
private ?string $endereco_uf = null;
private ?string $endereco_cep = null;
private ?string $created_at = null;
private ?string $updated_at = null;
private ?string $deleted_at = null;
/**
* Construtor do DTO.
*
* Permite inicializar os atributos do objeto a partir de um array associativo.
*
* @param array<string, mixed> $data Array associativo opcional para popular os atributos.
*/
public function __construct(array $data = []) {
if (!empty($data)) {
$this->fromArray($data);
}
}
/**
* Popula o DTO a partir de um array.
*
* @param array<string, mixed> $data
* @return void
*/
public function fromArray(array $data): void {
$this->id = $data['id'] ?? $this->id;
$this->uuid = $data['uuid'] ?? $this->uuid;
$this->nome_completo = $data['nome_completo'] ?? $this->nome_completo;
$this->status_id = $data['status_id'] ?? $this->status_id;
$this->documentcpf = $data['documentcpf'] ?? $this->documentcpf;
$this->documentcrmv = $data['documentcrmv'] ?? $this->documentcrmv;
$this->email = $data['email'] ?? $this->email;
$this->senha = $data['senha'] ?? $this->senha;
$this->telefone = $data['telefone'] ?? $this->telefone;
$this->data_nascimento = $data['data_nascimento'] ?? $this->data_nascimento;
$this->endereco_rua = $data['endereco_rua'] ?? $this->endereco_rua;
$this->endereco_numero = $data['endereco_numero'] ?? $this->endereco_numero;
$this->endereco_complemento = $data['endereco_complemento'] ?? $this->endereco_complemento;
$this->endereco_bairro = $data['endereco_bairro'] ?? $this->endereco_bairro;
$this->endereco_cidade = $data['endereco_cidade'] ?? $this->endereco_cidade;
$this->endereco_uf = $data['endereco_uf'] ?? $this->endereco_uf;
$this->endereco_cep = $data['endereco_cep'] ?? $this->endereco_cep;
$this->created_at = $data['created_at'] ?? $this->created_at;
$this->updated_at = $data['updated_at'] ?? $this->updated_at;
$this->deleted_at = $data['deleted_at'] ?? $this->deleted_at;
}
/**
* Converte o DTO para um array associativo.
*
* @return array<string, mixed>
*/
public function toArray(): array {
return [
'id' => $this->id,
'uuid' => $this->uuid,
'nome_completo' => $this->nome_completo,
'status_id' => $this->status_id,
'documentcpf' => $this->documentcpf,
'documentcrmv' => $this->documentcrmv,
'email' => $this->email,
'senha' => $this->senha,
'telefone' => $this->telefone,
'data_nascimento' => $this->data_nascimento,
'endereco_rua' => $this->endereco_rua,
'endereco_numero' => $this->endereco_numero,
'endereco_complemento' => $this->endereco_complemento,
'endereco_bairro' => $this->endereco_bairro,
'endereco_cidade' => $this->endereco_cidade,
'endereco_uf' => $this->endereco_uf,
'endereco_cep' => $this->endereco_cep,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
];
}
public function getId(): ?int {
return $this->id;
}
public function setId(?int $id): void {
$this->id = $id;
}
public function getUuid(): ?string {
return $this->uuid;
}
public function setUuid(?string $uuid): void {
$this->uuid = $uuid;
}
public function getNomeCompleto(): ?string {
return $this->nome_completo;
}
public function setNomeCompleto(?string $nome_completo): void {
$this->nome_completo = $nome_completo;
}
public function getStatusId(): ?int {
return $this->status_id;
}
public function setStatusId(?int $status_id): void {
$this->status_id = $status_id;
}
public function getDocumentCpf(): ?string {
return $this->documentcpf;
}
public function setDocumentCpf(?string $documentcpf): void {
$this->documentcpf = $documentcpf;
}
public function getDocumentCrmv(): ?string {
return $this->documentcrmv;
}
public function setDocumentCrmv(?string $documentcrmv): void {
$this->documentcrmv = $documentcrmv;
}
public function getEmail(): ?string {
return $this->email;
}
public function setEmail(?string $email): void {
$this->email = $email;
}
public function getSenha(): ?string {
return $this->senha;
}
public function setSenha(?string $senha): void {
$this->senha = $senha;
}
public function getTelefone(): ?string {
return $this->telefone;
}
public function setTelefone(?string $telefone): void {
$this->telefone = $telefone;
}
public function getDataNascimento(): ?string {
return $this->data_nascimento;
}
public function setDataNascimento(?string $data_nascimento): void {
$this->data_nascimento = $data_nascimento;
}
public function getEnderecoRua(): ?string {
return $this->endereco_rua;
}
public function setEnderecoRua(?string $endereco_rua): void {
$this->endereco_rua = $endereco_rua;
}
public function getEnderecoNumero(): ?string {
return $this->endereco_numero;
}
public function setEnderecoNumero(?string $endereco_numero): void {
$this->endereco_numero = $endereco_numero;
}
public function getEnderecoComplemento(): ?string {
return $this->endereco_complemento;
}
public function setEnderecoComplemento(?string $endereco_complemento): void {
$this->endereco_complemento = $endereco_complemento;
}
public function getEnderecoBairro(): ?string {
return $this->endereco_bairro;
}
public function setEnderecoBairro(?string $endereco_bairro): void {
$this->endereco_bairro = $endereco_bairro;
}
public function getEnderecoCidade(): ?string {
return $this->endereco_cidade;
}
public function setEnderecoCidade(?string $endereco_cidade): void {
$this->endereco_cidade = $endereco_cidade;
}
public function getEnderecoUf(): ?string {
return $this->endereco_uf;
}
public function setEnderecoUf(?string $endereco_uf): void {
$this->endereco_uf = $endereco_uf;
}
public function getEnderecoCep(): ?string {
return $this->endereco_cep;
}
public function setEnderecoCep(?string $endereco_cep): void {
$this->endereco_cep = $endereco_cep;
}
public function getCreatedAt(): ?string {
return $this->created_at;
}
public function setCreatedAt(?string $created_at): void {
$this->created_at = $created_at;
}
public function getUpdatedAt(): ?string {
return $this->updated_at;
}
public function setUpdatedAt(?string $updated_at): void {
$this->updated_at = $updated_at;
}
public function getDeletedAt(): ?string {
return $this->deleted_at;
}
public function setDeletedAt(?string $deleted_at): void {
$this->deleted_at = $deleted_at;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Zampet\Module\Auth\v0\Middlewares;
use Exception;
use Zampet\Services\JWTService;
use Zampet\Module\Auth\v0\Services\AuthService;
class AuthMiddleware {
/**
* Gerencia o processo de autenticação de uma requisição de API via token Bearer.
*
* Este método estático atua como um **middleware de autenticação**, verificando a presença e a validade de um token JWT em uma requisição HTTP.
*
* #### Fluxo de Operação:
* 1. **Obtenção do Token:** Tenta extrair o token Bearer do cabeçalho da requisição usando `JWTService::getBearerToken()`.
* 2. **Verificação de Presença:** Se o token não for encontrado, uma `Exception` é lançada com o código `401 Unauthorized`.
* 3. **Validação do Token:** Chama `AuthService::validateToken()` para verificar a validade do token (assinatura, expiração e status no banco de dados).
* 4. **Conclusão:**
* * Se a validação for bem-sucedida, o método retorna `true`, permitindo que o processamento da rota continue.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo (token ausente, falha de validação JWT, token expirado) é capturada.
* - O método retorna um array de erro formatado, contendo o código HTTP e a mensagem de erro da exceção, o que é útil para a camada de controle enviar uma resposta JSON padronizada ao cliente.
*
* @return array|bool Retorna `true` se o token for válido; caso contrário, retorna um array associativo com o status da operação, o código de resposta HTTP e uma mensagem de erro.
*/
public static function handle(): array|bool {
// Recebe o token JWT do cabeçalho Authorization
$bearerToken = JWTService::getBearerToken();
try {
// Verifica se o token está presente
if (!$bearerToken) {
throw new Exception(message: "Token not provided", code: 401);
}
// Verifica se o token é válido
$validateTokenService = (new AuthService())->validateToken(token: $bearerToken);
if ($validateTokenService['status'] !== 'success') {
throw new Exception(message: $validateTokenService['message'], code: 401);
}
return true;
} catch(Exception $e) {
return [
'response_code' => $e->getCode(),
'status' => 'error',
'message' => $e->getMessage()
];
}
}
}

View File

@@ -0,0 +1,396 @@
<?php
namespace Zampet\Module\Auth\v0\Models;
use DateTime;
use Exception;
use Zampet\Services\DB;
use Zampet\Module\Auth\v0\DTO\UsuarioDTO;
class UsuarioModel {
protected string $usuarioTable = 'usuario';
protected string $usuarioTokenTable = 'usuario_token';
/**
* Cria e armazena um novo usuário no banco de dados.
*
* Este método é responsável por inserir os dados completos de um novo usuário, incluindo informações pessoais, documentos, credenciais e endereço, na tabela `{$this->usuarioTable}`. A operação é atômica e utiliza uma transação de banco de dados para garantir a consistência dos dados.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Execução da Query:** Prepara e executa a instrução `INSERT` com todos os campos preenchidos a partir do `UsuarioDTO`.
* 3. **Confirmação:** Se a inserção for bem-sucedida, a transação é confirmada (`DB::commit()`).
* 4. **Retorno de Sucesso:** Retorna um array com `status` 'success', o código `201 Created`, uma mensagem de êxito e os dados do usuário criado (ID e UUID).
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo faz com que a transação seja revertida (`DB::rollBack()`).
* - Retorna um array com `status` 'error', código `500 Internal Server Error` e uma mensagem genérica de erro.
*
* @param UsuarioDTO $usuarioDTO O objeto DTO contendo os dados do usuário a ser criado.
* @return array Um array associativo com o status da operação, um código de resposta HTTP e uma mensagem.
*/
public function store(UsuarioDTO $usuarioDTO): array {
try {
$sql =
"INSERT INTO {$this->usuarioTable} (
uuid,
nome_completo,
status_id,
documentcpf,
documentcrmv,
email,
senha,
telefone,
data_nascimento,
endereco_rua,
endereco_numero,
endereco_complemento,
endereco_bairro,
endereco_cidade,
endereco_uf,
endereco_cep,
created_at,
updated_at,
deleted_at
) VALUES (
:uuid,
:nome_completo,
:status_id,
:documentcpf,
:documentcrmv,
:email,
:senha,
:telefone,
:data_nascimento,
:endereco_rua,
:endereco_numero,
:endereco_complemento,
:endereco_bairro,
:endereco_cidade,
:endereco_uf,
:endereco_cep,
:created_at,
:updated_at,
:deleted_at
)";
$params = [
":uuid" => $usuarioDTO->getUuid(),
":nome_completo" => $usuarioDTO->getNomeCompleto(),
":status_id" => $usuarioDTO->getStatusId(),
":documentcpf" => $usuarioDTO->getDocumentCpf(),
":documentcrmv" => $usuarioDTO->getDocumentCrmv(),
":email" => $usuarioDTO->getEmail(),
":senha" => $usuarioDTO->getSenha(),
":telefone" => $usuarioDTO->getTelefone(),
":data_nascimento" => $usuarioDTO->getDataNascimento(),
":endereco_rua" => $usuarioDTO->getEnderecoRua(),
":endereco_numero" => $usuarioDTO->getEnderecoNumero(),
":endereco_complemento" => $usuarioDTO->getEnderecoComplemento(),
":endereco_bairro" => $usuarioDTO->getEnderecoBairro(),
":endereco_cidade" => $usuarioDTO->getEnderecoCidade(),
":endereco_uf" => $usuarioDTO->getEnderecoUf(),
":endereco_cep" => $usuarioDTO->getEnderecoCep(),
":created_at" => $usuarioDTO->getCreatedAt(),
":updated_at" => $usuarioDTO->getUpdatedAt(),
":deleted_at" => $usuarioDTO->getDeletedAt()
];
// Executa a query de inserção
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Usuário registrado com sucesso.',
'output' => [
'data' => [
'user_id' => DB::lastInsertId(),
'uuid' => $usuarioDTO->getUuid()
]
]
];
} catch (Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Ocorreu um erro ao registrar o usuário. Tente novamente mais tarde.',
'output' => ['errors' => $e->getMessage()]
];
}
}
/**
* Busca um único usuário ativo no banco de dados por um identificador específico.
*
* Este método recupera todos os detalhes de um usuário na tabela `{$this->usuarioTable}`. A busca é flexível e permite usar qualquer coluna como identificador (`$identifier`), mas é estritamente restrita a registros que não foram logicamente excluídos (`deleted_at IS NULL`).
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'email', 'documentcpf').
* @param mixed $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do usuário, se encontrado.
*/
public function getUsuarioByIdentifier(string $identifier, mixed $value): array {
try {
$sql =
"SELECT
id,
uuid,
nome_completo,
status_id,
documentcpf,
documentcrmv,
email,
senha,
telefone,
data_nascimento,
endereco_rua,
endereco_numero,
endereco_complemento,
endereco_bairro,
endereco_cidade,
endereco_uf,
endereco_cep,
created_at,
updated_at,
deleted_at
FROM {$this->usuarioTable}
WHERE {$identifier} = :value
AND deleted_at IS NULL";
$params = [
':value' => $value
];
$result = DB::fetchOne(sql: $sql, params: $params);
if (!$result) {
return [
'response_code' => 404,
'status' => 'fail',
'message' => 'Usuário não encontrado.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'output' => ['data' => $result]
];
} catch(Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Ocorreu um erro ao buscar usuário. Tente novamente mais tarde.',
];
}
}
/**
* Armazena um novo token de autenticação no banco de dados para um usuário específico.
*
* Este método é responsável por registrar um token na tabela `{$this->usuarioTokenTable}`. Ele associa o token a um ID de usuário, define o status inicial como não revogado (`revoked = 0`) e registra o timestamp de criação.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** O método inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Execução da Query:** Prepara e executa a instrução `INSERT` com o ID do usuário, a string do token, o status inicial de revogação e o timestamp atual.
* 3. **Confirmação:** Se a inserção for bem-sucedida, a transação é confirmada (`DB::commit()`).
* 4. **Retorno de Sucesso:** Retorna um array com `status` 'success', o código `201 Created` e uma mensagem de êxito.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo faz com que a transação seja revertida (`DB::rollback()`).
* - Retorna um array com `status` 'error', o código `500 Internal Server Error` e uma mensagem genérica de erro.
*
* @param int $user_id O ID do usuário ao qual o token pertence.
* @param string $token A string do token de autenticação (JWT ou similar).
* @return array Um array associativo contendo o status da operação ("success" ou "error"), um código de resposta HTTP e uma mensagem.
*/
public function storeToken(int $user_id, string $token): array {
try {
$sql =
"INSERT INTO {$this->usuarioTokenTable} (
usuario_id,
token,
revoked,
created_at
) VALUES (
:usuario_id,
:token,
:revoked,
:created_at
)";
$params = [
":usuario_id" => $user_id,
":token" => $token,
":revoked" => 0,
":created_at" => (new DateTime())->format(format: 'Y-m-d H:i:s')
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Token registrado com sucesso.'
];
} catch (Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Erro ao registrar token. Tente novamente mais tarde.'
];
}
}
/**
* Revoga todos os tokens de autenticação ativos de um usuário específico.
*
* Este método atualiza a coluna `revoked` para `1` (revogado) para todos os tokens não revogados (`revoked = 0`) associados a um `user_id` específico na tabela `{$this->usuarioTokenTable}`. Isso invalida todas as sessões ativas do usuário, forçando-o a fazer login novamente.
*
* #### Fluxo de Operação:
* 1. **Execução da Query:** Prepara e executa a instrução `UPDATE` para revogar os tokens.
* 2. **Confirmação:** Se a atualização for bem-sucedida, retorna um array com `status` 'success' e código `200 OK`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo reverte qualquer transação de banco de dados e retorna um array com `status` 'error' e código `500 Internal Server Error`, com uma mensagem de erro genérica.
*
* @param int $user_id O ID do usuário para o qual os tokens serão revogados.
* @return array Um array associativo contendo o status da operação ("success" ou "error"), um código de resposta HTTP e uma mensagem descritiva.
*/
public function revokeOldTokens(int $user_id): array {
try {
$sql =
"UPDATE {$this->usuarioTokenTable} SET
revoked = 1,
updated_at = CURRENT_TIMESTAMP
WHERE usuario_id = :usuario_id
AND revoked = 0";
$params = [
":usuario_id" => $user_id
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Old tokens revoked successfully.'
];
} catch (Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Error revoking old tokens. Please try again later.'
];
}
}
/**
* Busca um token de usuário no banco de dados por um identificador específico.
*
* Este método recupera os dados de um único token de autenticação da tabela `{$this->usuarioTokenTable}` de forma flexível, permitindo a busca por qualquer coluna (`$identifier`) e o valor correspondente.
*
* #### Fluxo de Operação:
* 1. **Execução da Query**: Monta e executa uma consulta `SELECT` que usa o `$identifier` de forma dinâmica na cláusula `WHERE` e um parâmetro nomeado (`:value`) para o valor.
* 2. **Verificação de Resultados**:
* - Se o token for encontrado, retorna um array com **`status` 'success'**, código **200 OK** e os dados do token.
* - Se o token **não for encontrado**, retorna um array com **`status` 'fail'** e código **404 Not Found**.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante a consulta retorna um array com **`status` 'error'** e código **500 Internal Server Error**, com uma mensagem genérica de erro.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'id', 'token').
* @param mixed $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do token, se encontrado.
*/
public function getTokenByidentifier(string $identifier, mixed $value): array {
try {
$sql =
"SELECT
id,
usuario_id,
token,
revoked,
created_at,
updated_at
FROM {$this->usuarioTokenTable}
WHERE {$identifier} = :value";
$params = [
':value' => $value
];
$result = DB::fetchOne(sql: $sql, params: $params);
if (!$result) {
return [
'response_code' => 404,
'status' => 'fail',
'message' => 'Token não encontrado.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'output' => ['data' => $result]
];
} catch(Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Ocorreu um erro ao buscar token. Tente novamente mais tarde.',
];
}
}
/**
* Revoga um token de autenticação de usuário no banco de dados.
*
* Este método marca um token específico como revogado, atualizando a coluna `revoked` para `1`. Ele garante que apenas tokens que ainda não foram revogados (`revoked = 0`) sejam atualizados.
*
* #### Fluxo de Operação:
* 1. **Execução da Query**: Prepara e executa a instrução `UPDATE` para definir `revoked = 1` para o token correspondente.
* 2. **Confirmação**: Se a atualização for bem-sucedida, retorna um array com **`status` 'success'** e código **200 OK**.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'** e código **500 Internal Server Error**, com uma mensagem de erro genérica.
*
* @param string $token O token de autenticação a ser revogado.
* @return array Um array associativo contendo o status da operação ("success" ou "error"), um código de resposta HTTP e uma mensagem.
*/
public function revokeToken(string $token): array {
try {
$sql =
"UPDATE {$this->usuarioTokenTable} SET
revoked = 1,
updated_at = CURRENT_TIMESTAMP
WHERE token = :token
AND revoked = 0";
$params = [
":token" => $token
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Token revoked successfully.'
];
} catch (Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Error revoking token. Please try again later.'
];
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
use AxiumPHP\Core\Router;
use Zampet\Module\Auth\v0\Controllers\AuthController;
use Zampet\Module\Auth\v0\Middlewares\AuthMiddleware;
Router::group(
prefix: '/auth',
callback: function () {
// Criar novo usuário
Router::POST(
uri: '/register',
handler: [AuthController::class, 'register']
);
// Login no Sistema
Router::POST(
uri: '/login',
handler: [AuthController::class, 'login']
);
// Logout do Sistema
Router::POST(
uri: '/logout',
handler: [AuthController::class, 'logout'],
middlewares: [
AuthMiddleware::class.'::handle'
]
);
}
);

View File

@@ -0,0 +1,12 @@
<?php
use AxiumPHP\Core\Router;
Router::group(
prefix: '/v0',
callback: function() {
require 'AuthRoutes.php';
},
middlewares: [
]
);

View File

@@ -0,0 +1,346 @@
<?php
namespace Zampet\Module\Auth\v0\Services;
use DateTime;
use Exception;
use Ramsey\Uuid\Uuid;
use Zampet\Services\DB;
use Zampet\Services\JWTService;
use Zampet\Helpers\DataValidator;
use Zampet\Module\Auth\v0\DTO\UsuarioDTO;
use Zampet\Module\Auth\v0\Models\UsuarioModel;
class AuthService {
/**
* Registra um novo usuário no sistema após realizar validações rigorosas.
*
* Este método orquestra o processo de criação de um novo usuário, incluindo validação de dados, checagem de duplicidade de e-mail e persistência no banco de dados. A operação é atômica e utiliza uma transação de banco de dados (`DB::beginTransaction()`).
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados.
* 2. **Validação de Dados:** Um `DataValidator` é usado para validar campos obrigatórios (`nome_completo`, `email`, `senha`) e a complexidade da senha (mínimo de 8 caracteres, maiúsculas, números e símbolos).
* 3. **Checagem de Duplicidade:** O método `getUsuarioByIdentifier` é chamado para verificar se o e-mail já está em uso por outro usuário.
* 4. **Encerramento em Caso de Falha na Validação/Duplicidade:** Se houver erros de validação (código **400**) ou se o e-mail já estiver em uso (código **409**), a transação é revertida (`DB::rollBack()`) e o erro é retornado.
* 5. **Preparação Final dos Dados:** Se a validação passar, o **UUID** do usuário e o timestamp de criação são definidos. A senha é criptografada usando **`PASSWORD_ARGON2ID`** antes de ser persistida.
* 6. **Persistência:** O método `store` do `UsuarioModel` é chamado para inserir o novo registro.
* 7. **Confirmação:** Se a persistência for bem-sucedida (código **201**), a transação é confirmada (`DB::commit()`) e o resultado de sucesso é retornado.
* 8. **Tratamento de Erro de Persistência:** Se o `store` falhar por um motivo inesperado (código **500**), a transação é revertida.
*
* @param UsuarioDTO $usuarioDTO O objeto DTO contendo os dados do novo usuário, incluindo nome, email e senha.
* @return array Um array associativo contendo o código HTTP, status da operação, mensagem e dados de saída.
*/
public function register(UsuarioDTO $usuarioDTO): array {
// Inicia conexão com o banco de dados
DB::beginTransaction();
// Valida os dados do DTO
$dataValidador = new DataValidator(inputs: $usuarioDTO->toArray());
$dataValidador->validate(field: 'nome_completo', rule: 'notEmpty', message: 'Nome completo é obrigatório');
$dataValidador->validate(field: 'email', rule: 'notEmpty', message: 'Email é obrigatório');
$dataValidador->validate(field: 'email', rule: 'validateEmail', message: 'Email inválido');
$dataValidador->validate(field: 'senha', rule: 'notEmpty', message: 'Senha é obrigatória');
$dataValidador->validate(field: 'documentcpf', rule: 'notEmpty', message: 'CPF é obrigatório');
$dataValidador->validate(field: 'documentcpf', rule: 'validateCpf', message: 'CPF informado é inválido.');
$dataValidador->validate(field: 'senha', rule: 'validatePassword', message: 'Senha inválida', additionalParams: [
[
'minLength' => 8,
'upper' => true,
'digit' => true,
'symbol' => true
]
]
);
// Se houver erros de validação, retornar resposta de erro
if(!$dataValidador->passes()) {
DB::rollBack();
return [
'response_code' => 400,
'status' => 'error',
'message' => 'Validation errors',
'output' => [
'errors' => $dataValidador->getErrors()
]
];
}
// Verifica se o email já está em uso
$existingEmailUser = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'email', value: $usuarioDTO->getEmail());
if($existingEmailUser['status'] === 'success' && !empty($existingEmailUser['output']['data'])) {
DB::rollBack();
return [
'response_code' => 409,
'status' => 'error',
'message' => 'E-mail já está em uso por outro usuário.',
'output' => []
];
}
// Verifica se o CPF já está em uso
$existingCpfUser = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'documentcpf', value: $usuarioDTO->getDocumentCpf());
if($existingCpfUser['status'] === 'success' && !empty($existingCpfUser['output']['data'])) {
DB::rollBack();
return [
'response_code' => 409,
'status' => 'error',
'message' => 'CPF já está em uso por outro usuário.',
'output' => []
];
}
// Define dados adicionais
$usuarioDTO->setUuid(uuid: Uuid::uuid7()->toString());
$usuarioDTO->setCreatedAt(created_at: (new DateTime())->format(format: 'Y-m-d H:i:s'));
$usuarioDTO->setSenha(senha: password_hash(password: $usuarioDTO->getSenha(), algo: PASSWORD_ARGON2ID));
// Persiste o novo usuário no banco de dados
$storeUsuario = (new UsuarioModel())->store(usuarioDTO: $usuarioDTO);
if($storeUsuario['status'] !== 'success') {
DB::rollBack();
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Erro ao registrar usuário. Tente novamente mais tarde.',
'output' => $storeUsuario['output'] ?? []
];
}
DB::commit();
return [
'response_code' => $storeUsuario['response_code'],
'status' => $storeUsuario['status'],
'message' => $storeUsuario['message'],
'output' => $storeUsuario['output']
];
}
/**
* Realiza a autenticação de um usuário por e-mail e senha, gerando e registrando um token JWT.
*
* Este método gerencia todo o fluxo de login, incluindo a verificação de credenciais, revogação de tokens antigos, geração de um novo JWT e persistência desse novo token no banco de dados. A operação é atômica e utiliza uma transação de banco de dados para garantir a consistência dos dados.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Consulta do Usuário:** Busca o usuário pelo e-mail. Se o usuário não for encontrado, uma `Exception` é lançada com o código `401 Unauthorized`.
* 3. **Validação da Senha:** Verifica a senha fornecida contra o hash armazenado. Se a senha for inválida, uma `Exception` é lançada com o código `401 Unauthorized`.
* 4. **Revogação de Tokens Antigos:** Chama o método `revokeOldTokens` do `UsuarioModel` para invalidar todas as sessões anteriores do usuário.
* 5. **Geração do JWT:** Monta o payload do JWT com os dados essenciais do usuário e os timestamps `iat` (emitido em) e `exp` (expira em), gerando o novo token.
* 6. **Registro do Token:** Armazena o novo token no banco de dados usando `storeToken`. Se a operação falhar, uma `Exception` é lançada com o código `500 Internal Server Error`.
* 7. **Confirmação e Retorno:** Se todas as etapas forem bem-sucedidas, a transação é confirmada (`DB::commit()`), e o token gerado é retornado com `status` 'success' e código `200 OK`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo faz com que a transação seja revertida (`DB::rollBack()`).
* - Retorna um array com `status` 'error', o código de erro da exceção (ou 500 como fallback) e uma mensagem descritiva.
*
* @param string $email O e-mail do usuário para autenticação.
* @param string $password A senha em texto plano fornecida pelo usuário.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e o token JWT em caso de sucesso.
*/
public function login(string $email, string $password): array {
try {
// Inicia Transação no Banco de dados
DB::beginTransaction();
// Consulta as informações do usuário pelo email
$userResult = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'email', value: $email);
if($userResult['status'] !== 'success' || empty($userResult['output']['data'])) {
throw new Exception(message: 'E-mail ou Senha incorretos.', code: 401);
}
// Valida a senha fornecida com a armazenada
if(!password_verify(password: $password, hash: $userResult['output']['data']['senha'])) {
throw new Exception(message: 'E-mail ou Senha incorretos.', code: 401);
}
// Revoga os tokens antigos do usuário
$revokeOldTokens = (new UsuarioModel())->revokeOldTokens(user_id: $userResult['output']['data']['id']);
if($revokeOldTokens['status'] !== 'success') {
throw new Exception(message: 'Erro ao revogar tokens antigos.', code: 500);
}
// Prepara o Payload do JWT
$jwtPayload = [
'user_uuid' => $userResult['output']['data']['uuid'],
'email' => $userResult['output']['data']['email'],
'nome_completo' => $userResult['output']['data']['nome_completo'],
'iat' => (new DateTime())->getTimestamp(),
'exp' => (new DateTime())->modify(modifier: '+8 hour')->getTimestamp()
];
$token = JWTService::generateToken(payload: $jwtPayload);
// Armazena o token no banco de dados
$storeToken = (new UsuarioModel())->storeToken(user_id: $userResult['output']['data']['id'], token: $token);
if($storeToken['status'] !== 'success') {
throw new Exception(message: 'Erro ao armazenar token de autenticação.', code: 500);
}
DB::commit();
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Login successful.',
'output' => [
'data' => [
'token' => $token
]
]
];
} catch(Exception $e) {
DB::rollBack();
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred during login.'
];
}
}
/**
* Finaliza a sessão do usuário revogando um token JWT específico.
*
* Este método é o responsável por orquestrar o processo de logout seguro, garantindo que o token de autenticação seja validado e, em seguida, invalidado no banco de dados.
*
* #### Fluxo de Operação:
* 1. **Validação do Token:** O método chama `validateToken` para verificar se o token existe e ainda é válido (não expirado ou revogado no DB). Se a validação falhar, uma `Exception` é lançada com o código de erro apropriado.
* 2. **Revogação no Banco de Dados:** Tenta revogar o token usando `UsuarioModel::revokeToken()`. Se a revogação falhar (ex: erro de banco de dados), uma `Exception` é lançada.
* 3. **Retorno de Sucesso:** Se ambas as operações (validação e revogação) forem bem-sucedidas, retorna um array com **`status` 'success'**, código **200 OK** e uma mensagem de êxito.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo é capturada, e o método retorna um array de erro formatado, contendo o código HTTP e a mensagem da exceção.
*
* @param string $token O token JWT a ser revogado.
* @return array Um array associativo contendo o status da operação ("success" ou "error"), o código de resposta HTTP e uma mensagem.
*/
public function logout(string $token): array {
try {
// Validar se o token existe e não está revogado
$validateToken = $this->validateToken(token: $token);
if($validateToken['status'] !== 'success') {
throw new Exception(message: $validateToken['message'], code: $validateToken['response_code']);
}
// Revoga o token fornecido
$revokeToken = (new UsuarioModel())->revokeToken(token: $token);
if($revokeToken['status'] !== 'success') {
throw new Exception(message: 'Erro ao revogar token.', code: $revokeToken['response_code']);
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Logout successful.',
'output' => []
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred during logout.'
];
}
}
/**
* Valida um token de autenticação verificando sua presença no banco de dados e seu status de expiração.
*
* Este método realiza uma validação de duas etapas: 1) Checa se o token existe e não está revogado no banco de dados, e 2) verifica se o timestamp de expiração (exp) no payload do token já passou. Se o token estiver expirado, ele é revogado no banco de dados.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`) para garantir que a revogação, se necessária, seja atômica.
* 2. **Busca no DB:** Tenta encontrar o token usando o `UsuarioModel`. Se a busca falhar (token não encontrado ou revogado), uma `Exception` é lançada com o código `401 Unauthorized`.
* 3. **Decodificação:** O token é decodificado usando `JWTService::decode()` para acessar o payload e o timestamp de expiração (`exp`).
* 4. **Verificação de Expiração:** Compara o timestamp atual com o `exp` do payload.
* * **Se Expirado:** O método tenta revogar o token no DB. Se a revogação for bem-sucedida, a transação é confirmada e o método retorna um erro com código `401`. Se a revogação falhar, a transação é revertida e um erro `500` é retornado.
* * **Se Válido:** A transação é confirmada e o método retorna `status` 'success' com o código `200 OK` e os dados do payload.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo (falha de busca, falha de decodificação JWT, falha de revogação) é capturada. Se a falha ocorrer após o início da transação, um `DB::rollBack()` é implícito ou explicitamente chamado antes do retorno.
* - Retorna um array com `status` 'error', o código de erro (ou `500` como fallback) e uma mensagem descritiva.
*
* @param string $token O token JWT a ser validado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do token, se válido.
*/
public function validateToken(string $token): array {
// Inicia transação no banco de dados
DB::beginTransaction();
try {
$dbTokenData = (new UsuarioModel())->getTokenByidentifier(identifier: 'token', value: $token);
if($dbTokenData['status'] !== 'success' || empty($dbTokenData['output']['data'])) {
throw new Exception(message: 'Token inválido ou expirado.', code: 401);
}
// Decodifica o token para verificar sua validade
$decodedToken = JWTService::decode(token: $dbTokenData['output']['data']['token']);
// Verifica se o token expirou
$currentTimestamp = (new DateTime())->getTimestamp();
switch (true) {
case $currentTimestamp > $decodedToken['exp']:
// Revoga o token expirado
$revokeToken = (new UsuarioModel())->revokeToken(token: $dbTokenData['output']['data']['token']);
if($revokeToken['status'] !== 'success') {
DB::rollBack();
throw new Exception(message: 'Erro ao revogar token expirado.', code: 500);
}
DB::commit();
return [
'response_code' => 401,
'status' => 'error',
'message' => 'Token expirado.',
'output' => []
];
case $dbTokenData['output']['data']['revoked'] === 1:
DB::commit();
return [
'response_code' => 401,
'status' => 'error',
'message' => 'Token inválido ou expirado.',
'output' => []
];
}
DB::commit();
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Token is valid.',
'output' => ['token_data' => $decodedToken]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred during token validation.'
];
}
}
public function getUserData(string $token): array {
try {
// Validar se o token existe e não está revogado
$validateToken = $this->validateToken(token: $token);
if($validateToken['status'] !== 'success') {
throw new Exception(message: $validateToken['message'], code: $validateToken['response_code']);
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'User data retrieved successfully.',
'output' => [
'data' => $validateToken['output']['token_data']
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred while retrieving user data.'
];
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Zampet\Module\Auth;
use DateTime;
use Exception;
// Caminho do manifest.json
$manifestPath = realpath(path: __DIR__ . "/manifest.json");
if (!$manifestPath || !file_exists(filename: $manifestPath)) {
throw new Exception(message: "Arquivo 'manifest.json' não encontrado.");
}
// Lê e decodifica
$manifest = json_decode(json: file_get_contents(filename: $manifestPath), associative: true);
// Verifica estrutura
if (!isset($manifest['apis']) || !is_array(value: $manifest['apis'])) {
throw new Exception(message: "Estrutura de 'apis' inválida no manifest.");
}
$now = (new DateTime())->format(format: 'Y-m-d');
$newApis = [];
$updated = false;
// Checa se tem versão esperando que começa hoje
foreach ($manifest['apis'] as $api) {
if ($api['status'] === 'planned' && $api['start_date'] === $now) {
// Promote a nova
$api['status'] = 'active';
$newApis[] = $api;
$updated = true;
} elseif ($api['status'] === 'active' && $api['start_date'] !== $now) {
// Ignora versão antiga ativa
continue;
} else {
$newApis[] = $api;
}
}
// Atualiza manifest se tiver mudança
if ($updated) {
$manifest['apis'] = $newApis;
file_put_contents(filename: $manifestPath, data: json_encode(value: $manifest, flags: JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// Carrega a versão ativa
$loaded = false;
foreach ($manifest['apis'] as $api) {
$startDate = new DateTime(datetime: $api['start_date']);
if ($api['status'] === 'active' && $startDate <= new DateTime()) {
$routeFile = MODULE_PATH . "/{$manifest['dirName']}/{$api['version']}/Routes/Routes.php";
if (file_exists(filename: $routeFile)) {
require_once realpath(path: $routeFile);
$loaded = true;
break;
} else {
throw new Exception(message: "Arquivo de rotas não encontrado para a versão '{$api['version']}'.");
}
}
}
// Verifica se carregou
if (!$loaded) {
throw new Exception(message: "Nenhuma versão ativa da API foi carregada.");
}

View File

@@ -0,0 +1,16 @@
{
"name": "Autenticação",
"dirName": "Auth",
"slug": "auth",
"description": "Gerencia login, logout, sessão e permissões de acesso.",
"uuid": "0199abc8-026a-75b0-9b25-0f9456a44c70",
"version": "1.0",
"dependencies": [],
"apis": [
{
"version": "v0",
"status": "active",
"start_date": "2025-08-01"
}
]
}

0
app/Module/Tutor/blank_file Executable file
View File

View File

@@ -0,0 +1,322 @@
<?php
namespace Zampet\Module\Tutor\v0\Controllers;
use Zampet\Helpers\Sanitizer;
use Zampet\Services\JWTService;
use AxiumPHP\Helpers\RequestHelper;
use Zampet\Module\Tutor\v0\DTO\PetDTO;
use Zampet\Module\Tutor\v0\DTO\PetRacaDTO;
use Zampet\Module\Tutor\v0\Services\PetService;
class PetController {
/**
* Lida com a requisição para listar todas as espécies de pets disponíveis.
*
* Este método atua como um **controlador** para um endpoint de API. Ele delega a busca da lista completa de espécies para o `PetService` e, em seguida, formata e envia a resposta ao cliente em JSON.
*
* #### Fluxo de Operação:
* 1. **Delegação ao Serviço:** Uma instância de `PetService` é criada, e o método `getAllEspecies` é chamado para obter a lista de espécies. O serviço é responsável pela lógica de busca no modelo.
* 2. **Envio da Resposta JSON:** O resultado retornado pelo serviço (`especiesPets`) é formatado para uma resposta JSON padronizada usando **`RequestHelper::sendJsonResponse`**. Os dados da resposta (código HTTP, status, mensagem e lista de espécies) são comunicados ao cliente.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function getAllEspecies(): void {
// Lista as espécies de pets
$especiesPets = (new PetService())->getAllEspecies();
// Retorna a resposta em JSON
RequestHelper::sendJsonResponse(
response_code: $especiesPets['response_code'],
status: $especiesPets['status'],
message: $especiesPets['message'],
output: $especiesPets['output'] ?? []
);
exit;
}
/**
* Lista todos os pets associados ao usuário atualmente autenticado.
*
* Este método atua como um **controlador** para um endpoint de API, responsável por identificar o usuário autenticado, buscar a lista de seus pets e retornar os dados em formato JSON padronizado.
*
* #### Fluxo de Operação:
* 1. **Obtenção da Identidade do Usuário:** O método primeiro extrai e decodifica o token JWT presente no cabeçalho `Authorization` usando `JWTService::getBearerToken()` e `JWTService::decode()`. O UUID do usuário (tutor) é obtido diretamente do payload decodificado.
* 2. **Delegação ao Serviço:** O método `listPetsByTutorUuid` do **`PetService`** é chamado, passando o UUID do usuário para buscar todos os pets associados.
* 3. **Envio da Resposta JSON:** O resultado retornado pelo serviço (`petsTutor`) é formatado e enviado de volta ao cliente usando **`RequestHelper::sendJsonResponse`**.
* * O código HTTP, status e mensagem são extraídos do array de resposta do serviço, garantindo que a resposta reflita o sucesso ou falha da operação de busca.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function listMyPets(): void {
// Recebe o token JWT do cabeçalho Authorization
$decodedToken = JWTService::decode(token: JWTService::getBearerToken());
// Inicia o serviço de Pet
$petsTutor = (new PetService())->listPetsByTutorUuid(uuid: $decodedToken['user_uuid']);
// Retorna a resposta em JSON
RequestHelper::sendJsonResponse(
response_code: $petsTutor['response_code'],
status: $petsTutor['status'],
message: $petsTutor['message'],
output: $petsTutor['output'] ?? []
);
exit;
}
/**
* Lida com a requisição de criação de uma nova raça de pet.
*
* Este método atua como um **controlador** de API, responsável por receber os dados da nova raça (descrição) e o UUID da espécie, validar as entradas e orquestrar a criação do novo registro na camada de serviço.
*
* #### Fluxo de Operação:
* 1. **Validação de Presença:** Verifica se os campos obrigatórios (`especie_uuid` e `descricao`) estão presentes. Se não estiverem, retorna uma resposta JSON com o código **400 Bad Request** e encerra a execução.
* 2. **Busca e Validação da Espécie:** Chama `PetService::getEspecieByIdentifier` para confirmar a existência da espécie pelo UUID e obter seu ID interno. Se a espécie não for encontrada, retorna um erro `400`.
* 3. **Preparação do DTO:** Cria e popula um **`PetRacaDTO`** com o ID interno da espécie e a descrição sanitizada.
* 4. **Delegação ao Serviço:** O método `storeRaca` do **`PetService`** é chamado para executar a lógica de persistência e validação de negócio.
* 5. **Envio da Resposta JSON:** O resultado do serviço (`serviceResult`) é formatado e enviado de volta ao cliente, comunicando o status (`201 Created` em caso de sucesso) e a mensagem da operação.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function storeRaca(): void {
// Receber dados do formulário de registro
$form = RequestHelper::getFilteredInput(form_type: INPUT_POST);
// Campos Obrigatórios
$required_fields = [
'especie_uuid',
'descricao'
];
// Verificar se todos os campos obrigatórios estão presentes
foreach ($required_fields as $field) {
if (empty($form[$field])) {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Field '{$field}' is required"
);
exit;
}
}
// Consulta informações da espécie com base no UUID fornecido
$getEspecie = (new PetService())->getEspecieByIdentifier(
identifier: 'uuid',
value: $form['especie_uuid']
);
if($getEspecie['status'] !== 'success') {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Espécie não encontrada",
output: ['data' => $form]
);
exit;
}
$especieData = $getEspecie['output']['data'];
// Chama o serviço de criar raça
$serviceResult = (new PetService())->storeRaca(
petRacaDTO: new PetRacaDTO(data: [
'especie_id' => $especieData['id'],
'descricao' => Sanitizer::string(value: $form['descricao'] ?? '')
])
);
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
/**
* Lida com a requisição de criação de um novo pet (animal de estimação).
*
* Este método atua como um **controlador** de API, responsável por receber os dados do formulário, realizar validações de presença e delegar a lógica de negócio (criação do pet e associação ao tutor) para o `PetService`.
*
* #### Fluxo de Operação:
* 1. **Validação de Presença:** Verifica se os campos obrigatórios (`name`, `raca_uuid`, `birthDate`, `sexo`) estão presentes no formulário. Se algum estiver faltando, retorna um erro **400 Bad Request** e encerra a execução.
* 2. **Busca e Validação da Raça/Espécie:** Chama `PetService::getEspecieByIdentifier` (apesar do nome, a lógica sugere que este método deve retornar a raça e a espécie) para validar o `raca_uuid` fornecido e obter os IDs internos. Se a raça não for encontrada ou a busca falhar, retorna um erro **400**.
* 3. **Preparação do DTO:** Cria e popula um **`PetDTO`** com os dados sanitizados e os IDs internos da raça e espécie.
* 4. **Delegação ao Serviço:** O método `storePet` do **`PetService`** é chamado para executar a lógica de persistência e associação do pet ao tutor autenticado.
* 5. **Envio da Resposta JSON:** O resultado do serviço (`serviceResult`) é formatado e enviado de volta ao cliente, comunicando o status (código **201 Created** em caso de sucesso) e a mensagem da operação.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function storePet(): void {
// Receber dados do formulário de registro
$form = RequestHelper::getFilteredInput(form_type: INPUT_POST);
// Campos Obrigatórios
$required_fields = [
'name',
'raca_uuid',
'birthDate',
'sexo',
];
// Verificar se todos os campos obrigatórios estão presentes
foreach ($required_fields as $field) {
if (empty($form[$field])) {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Field '{$field}' is required"
);
exit;
}
}
// Consulta informações da raça com base no UUID fornecido
$getRaca = (new PetService())->getRacaByIdentifier(
identifier: 'uuid',
value: $form['raca_uuid']
);
if($getRaca['status'] !== 'success') {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Raça não encontrada"
);
exit;
}
// Chama o serviço de criar pet
$serviceResult = (new PetService())->storePet(
petDTO: new PetDTO(data: [
"nome" => Sanitizer::string(value: $form['name'] ?? ''),
"data_nascimento" => Sanitizer::string(value: $form['birthDate'] ?? ''),
"sexo" => Sanitizer::string(value: $form['sexo'] ?? ''),
"raca_id" => $getRaca['output']['data']['id'],
"especie_id" => $getRaca['output']['data']['especie_id'],
"registro_geral_animal" => Sanitizer::string(value: $form['registro_geral_animal'] ?? NULL),
])
);
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
/**
* Lida com a requisição para obter detalhes de um pet específico por seu UUID.
*
* Este método atua como um **controlador** para um endpoint de API. Ele é responsável por delegar a busca dos dados do pet para o serviço e formatar a resposta para o cliente em JSON.
*
* #### Fluxo de Operação:
* 1. **Busca de Detalhes:** O método chama `PetService::getPetByIdentifier()`, usando o UUID fornecido como identificador. O serviço lida com a lógica de consulta no modelo.
* 2. **Envio da Resposta JSON:** O resultado retornado pelo serviço (`petDetails`) é formatado e enviado de volta ao cliente usando **`RequestHelper::sendJsonResponse`**. O código HTTP, status e mensagem são extraídos do array de resposta do serviço, comunicando o resultado da busca (sucesso ou falha).
*
* @param string $uuid O UUID do pet cujos detalhes devem ser buscados.
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function getPetDetails(string $uuid): void {
// Consulta detalhes do pet com base no UUID fornecido
$petDetails = (new PetService())->getPetByIdentifier(
identifier: 'uuid',
value: $uuid
);
if($petDetails['status'] !== 'success') {
RequestHelper::sendJsonResponse(
response_code: $petDetails['response_code'],
status: $petDetails['status'],
message: $petDetails['message'],
output: $petDetails['output'] ?? []
);
exit;
}
$pet = $petDetails['output']['data'];
$pet['vacinas'] = [
[
"id" => 1,
"nome" => "Antirrábica",
"fabricante" => "Zoetis",
"lote" => "ZOE-2025-001",
"validade" => "2026-02-15",
"data_aplicacao" => "2025-02-10",
"data_reforco" => "2026-02-10",
"veterinario" => "Dr. Carlos Mendes",
"created_at" => "2025-02-10 14:30:00",
"updated_at" => null,
"deleted_at" => null
],
[
"id" => 2,
"nome" => "V10",
"fabricante" => "Boehringer Ingelheim",
"lote" => "BIO-2025-044",
"validade" => "2026-06-20",
"data_aplicacao" => "2025-03-05",
"data_reforco" => "2026-03-05",
"veterinario" => "Dra. Fernanda Lopes",
"created_at" => "2025-03-05 09:10:00",
"updated_at" => null,
"deleted_at" => null
],
[
"id" => 3,
"nome" => "Giárdia",
"fabricante" => "Ceva",
"lote" => "CEV-2025-321",
"validade" => "2026-08-01",
"data_aplicacao" => "2025-04-15",
"data_reforco" => "2025-05-15",
"veterinario" => "Dr. Rafael Silva",
"created_at" => "2025-04-15 11:45:00",
"updated_at" => null,
"deleted_at" => null
],
[
"id" => 4,
"nome" => "Leptospirose",
"fabricante" => "MSD",
"lote" => "MSD-2025-112",
"validade" => "2026-09-30",
"data_aplicacao" => "2025-05-20",
"data_reforco" => "2026-05-20",
"veterinario" => "Dra. Ana Paula",
"created_at" => "2025-05-20 10:00:00",
"updated_at" => null,
"deleted_at" => null
],
[
"id" => 5,
"nome" => "Cinomose",
"fabricante" => "Vanguard",
"lote" => "VAN-2025-210",
"validade" => "2026-11-12",
"data_aplicacao" => "2025-06-10",
"data_reforco" => "2026-06-10",
"veterinario" => "Dr. Eduardo Gomes",
"created_at" => "2025-06-10 15:20:00",
"updated_at" => null,
"deleted_at" => null
]
];
// Retorna a resposta em JSON
RequestHelper::sendJsonResponse(
response_code: $petDetails['response_code'],
status: $petDetails['status'],
message: $petDetails['message'],
output: ['data' => $pet] ?? []
);
exit;
}
public function updatePet(string $uuid, array $params): void {
}
}

View File

@@ -0,0 +1,177 @@
<?php
namespace Zampet\Module\Tutor\v0\DTO;
class PetDTO {
private ?int $id = null;
private ?string $uuid = null;
private ?string $nome = null;
private ?int $especie_id = null;
private ?int $raca_id = null;
private ?string $caminho_foto = null;
private ?string $registro_geral_animal = null;
private ?string $photo_path = null;
private ?string $data_nascimento = null;
private ?string $data_obito = null;
private ?string $sexo = null;
private ?string $created_at = null;
private ?string $updated_at = null;
private ?string $deleted_at = null;
/**
* Construtor do DTO.
*
* Permite inicializar os atributos do objeto a partir de um array associativo.
*
* @param array<string, mixed> $data Array associativo opcional para popular os atributos.
*/
public function __construct(array $data = []) {
if (!empty($data)) {
$this->fromArray($data);
}
}
/**
* Popula o DTO a partir de um array.
*
* @param array<string, mixed> $data
* @return void
*/
public function fromArray(array $data): void {
$this->id = $data['id'] ?? $this->id;
$this->uuid = $data['uuid'] ?? $this->uuid;
$this->nome = $data['nome'] ?? $this->nome;
$this->especie_id = $data['especie_id'] ?? $this->especie_id;
$this->raca_id = $data['raca_id'] ?? $this->raca_id;
$this->caminho_foto = $data['caminho_foto'] ?? $this->caminho_foto;
$this->registro_geral_animal = $data['registro_geral_animal'] ?? $this->registro_geral_animal;
$this->photo_path = $data['photo_path'] ?? $this->photo_path;
$this->data_nascimento = $data['data_nascimento'] ?? $this->data_nascimento;
$this->data_obito = $data['data_obito'] ?? $this->data_obito;
$this->sexo = $data['sexo'] ?? $this->sexo;
$this->created_at = $data['created_at'] ?? $this->created_at;
$this->updated_at = $data['updated_at'] ?? $this->updated_at;
$this->deleted_at = $data['deleted_at'] ?? $this->deleted_at;
}
/**
* Converte o DTO para um array associativo.
*
* @return array<string, mixed>
*/
public function toArray(): array {
return [
'id' => $this->id,
'uuid' => $this->uuid,
'nome' => $this->nome,
'especie_id' => $this->especie_id,
'raca_id' => $this->raca_id,
'caminho_foto' => $this->caminho_foto,
'registro_geral_animal' => $this->registro_geral_animal,
'photo_path' => $this->photo_path,
'data_nascimento' => $this->data_nascimento,
'data_obito' => $this->data_obito,
'sexo' => $this->sexo,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
];
}
public function getId(): ?int {
return $this->id;
}
public function setId(?int $id): void {
$this->id = $id;
}
public function getUuid(): ?string {
return $this->uuid;
}
public function setUuid(?string $uuid): void {
$this->uuid = $uuid;
}
public function getNome(): ?string {
return $this->nome;
}
public function setNome(?string $nome): void {
$this->nome = $nome;
}
public function getEspecieId(): ?int {
return $this->especie_id;
}
public function setEspecieId(?int $especie_id): void {
$this->especie_id = $especie_id;
}
public function getRacaId(): ?int {
return $this->raca_id;
}
public function setRacaId(?int $raca_id): void {
$this->raca_id = $raca_id;
}
public function getCaminhoFoto(): ?string {
return $this->caminho_foto;
}
public function setCaminhoFoto(?string $caminho_foto): void {
$this->caminho_foto = $caminho_foto;
}
public function getRegistroGeralAnimal(): ?string {
return $this->registro_geral_animal;
}
public function setRegistroGeralAnimal(?string $registro_geral_animal): void {
$this->registro_geral_animal = $registro_geral_animal;
}
public function getPhotoPath(): ?string {
return $this->photo_path;
}
public function setPhotoPath(?string $photo_path): void {
$this->photo_path = $photo_path;
}
public function getDataNascimento(): ?string {
return $this->data_nascimento;
}
public function setDataNascimento(?string $data_nascimento): void {
$this->data_nascimento = $data_nascimento;
}
public function getDataObito(): ?string {
return $this->data_obito;
}
public function setDataObito(?string $data_obito): void {
$this->data_obito = $data_obito;
}
public function getSexo(): ?string {
return $this->sexo;
}
public function setSexo(?string $sexo): void {
$this->sexo = $sexo;
}
public function getCreatedAt(): ?string {
return $this->created_at;
}
public function setCreatedAt(?string $created_at): void {
$this->created_at = $created_at;
}
public function getUpdatedAt(): ?string {
return $this->updated_at;
}
public function setUpdatedAt(?string $updated_at): void {
$this->updated_at = $updated_at;
}
public function getDeletedAt(): ?string {
return $this->deleted_at;
}
public function setDeletedAt(?string $deleted_at): void {
$this->deleted_at = $deleted_at;
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Zampet\Module\Tutor\v0\DTO;
class PetRacaDTO {
public ?int $id = null;
public ?string $uuid = null;
public ?int $especie_id = null;
public ?string $descricao = null;
/**
* Construtor do DTO.
*
* Permite inicializar os atributos do objeto a partir de um array associativo.
*
* @param array<string, mixed> $data Array associativo opcional para popular os atributos.
*/
public function __construct(array $data = []) {
if (!empty($data)) {
$this->fromArray($data);
}
}
/**
* Popula o DTO a partir de um array.
*
* @param array<string, mixed> $data
* @return void
*/
public function fromArray(array $data): void {
$this->id = $data['id'] ?? $this->id;
$this->uuid = $data['uuid'] ?? $this->uuid;
$this->especie_id = $data['especie_id'] ?? $this->especie_id;
$this->descricao = $data['descricao'] ?? $this->descricao;
}
/**
* Converte o DTO para um array associativo.
*
* @return array<string, mixed>
*/
public function toArray(): array {
return [
'id' => $this->id,
'uuid' => $this->uuid,
'especie_id' => $this->especie_id,
'descricao' => $this->descricao,
];
}
public function getId(): ?int {
return $this->id;
}
public function setId(?int $id): void {
$this->id = $id;
}
public function getUuid(): ?string {
return $this->uuid;
}
public function setUuid(?string $uuid): void {
$this->uuid = $uuid;
}
public function getEspecieId(): ?int {
return $this->especie_id;
}
public function setEspecieId(?int $especie_id): void {
$this->especie_id = $especie_id;
}
public function getDescricao(): ?string {
return $this->descricao;
}
public function setDescricao(?string $descricao): void {
$this->descricao = $descricao;
}
}

View File

@@ -0,0 +1,651 @@
<?php
namespace Zampet\Module\Tutor\v0\Models;
use Exception;
use Zampet\Services\DB;
use Zampet\Module\Tutor\v0\DTO\PetDTO;
use Zampet\Module\Tutor\v0\DTO\PetRacaDTO;
use Zampet\Module\Auth\v0\Models\UsuarioModel;
class PetModel {
protected string $petTable = 'pet';
protected string $usuarioPet = 'usuario_pet';
protected string $petAuxRacaTable = 'pet_aux_raca';
protected string $petAuxEspecieTable = 'pet_aux_especie';
/**
* Lista todos os pets associados a um tutor, buscando o tutor por um identificador flexível.
*
* Este método busca primeiro o registro do tutor usando um identificador (UUID, ID, e-mail, etc.) e, em seguida, usa o ID interno do tutor para consultar todos os pets a ele associados.
*
* #### Fluxo de Operação:
* 1. **Busca do Tutor:** Utiliza `UsuarioModel::getUsuarioByIdentifier` para buscar os dados do tutor. Se o tutor não for encontrado, lança uma `Exception` com o código 404.
* 2. **Consulta dos Pets:** Executa uma consulta `SELECT` que une as tabelas de associação (`up`), pets (`p`), raças (`par`) e espécies (`pae`) para obter uma lista detalhada de todos os pets vinculados ao `usuario_id` do tutor.
* 3. **Verificação de Pets:**
* * Se a consulta não retornar pets (`empty($petsUsuario)`), retorna um array com `status` 'fail' e uma mensagem informativa.
* * Se pets forem encontrados, retorna um array com `status` 'success', código `200 OK` e os dados dos pets na chave `output`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo (como tutor não encontrado ou erro de consulta) é capturada.
* - Retorna um array com `status` 'error', o código de resposta HTTP da exceção e uma mensagem descritiva.
*
* @param string $identifier A coluna da tabela de usuário a ser usada para identificar o tutor (ex: 'uuid', 'id').
* @param mixed $value O valor correspondente ao identificador do tutor.
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados dos pets (`output['data']`), ou uma mensagem de erro.
*/
public function listPetsByTutorIdentifier(string $identifier, mixed $value): array {
try {
$usuarioData = (new UsuarioModel())->getUsuarioByIdentifier(identifier: $identifier, value: $value);
if(!$usuarioData) {
throw new Exception(message: 'Tutor not found', code: 404);
}
$usuarioData = $usuarioData['output']['data'];
$sql =
"SELECT
p.id,
p.uuid,
p.nome,
p.especie_id,
pae.descricao AS especie,
p.raca_id,
par.descricao AS raca,
p.caminho_foto,
p.registro_geral_animal,
p.photo_path,
p.data_nascimento,
p.data_obito,
p.sexo,
p.created_at,
p.updated_at,
p.deleted_at
FROM {$this->usuarioPet} up
LEFT JOIN {$this->petTable} p ON up.pet_id = p.id
LEFT JOIN {$this->petAuxRacaTable} par ON p.raca_id = par.id
LEFT JOIN {$this->petAuxEspecieTable} pae ON p.especie_id = pae.id
WHERE up.usuario_id = :usuario_id";
$params = [
':usuario_id' => $usuarioData['id'],
];
$petsUsuario = DB::fetchAll(sql: $sql, params: $params);
if(empty($petsUsuario)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Você não possui pets cadastrados.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Pets encontrados com sucesso.',
'output' => [
'data' => $petsUsuario
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Lista todas as espécies de pets disponíveis no sistema auxiliar.
*
* Este método consulta a tabela de espécies (`{$this->petAuxEspecieTable}`) e retorna uma lista de todos os registros (ID e descrição).
*
* #### Fluxo de Operação:
* 1. **Execução da Consulta:** Executa uma consulta `SELECT` para buscar todos os IDs e descrições da tabela auxiliar de espécies.
* 2. **Verificação de Resultados:**
* - Se a consulta for bem-sucedida, mas **não retornar nenhuma espécie** (`empty($stmt)`), retorna um array com `status` 'fail' e código `200 OK`.
* - Se a consulta for bem-sucedida e **retornar espécies**, retorna um array com `status` 'success', código `200 OK` e os dados encontrados na chave `output`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante a consulta retorna um array com `status` 'error', o código de erro da exceção e uma mensagem detalhada de erro.
*
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e os dados das espécies (`output['data']`).
*/
public function getAllEspecies(): array {
try {
$sql =
"SELECT
id,
uuid,
descricao
FROM {$this->petAuxEspecieTable}";
$result = DB::fetchAll(sql: $sql);
if(empty($result)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Nenhuma espécie encontrada.',
'output' => [
'data' => []
]
];
}
$preparedResult = [];
foreach($result as $key => $especie) {
$sql =
"SELECT
uuid,
descricao
FROM {$this->petAuxRacaTable}
WHERE especie_id = :especie_id";
$params = [
':especie_id' => $especie['id']
];
$result = DB::fetchAll(sql: $sql, params: $params);
$preparedResult[] = [
'uuid' => $especie['uuid'],
'descricao' => $especie['descricao'],
'racas' => $result
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Espécies encontradas com sucesso.',
'output' => [
'data' => $preparedResult
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Busca uma única espécie de pet no banco de dados por um identificador específico.
*
* Este método recupera os dados de uma espécie da tabela auxiliar `{$this->petAuxEspecieTable}` de forma flexível, permitindo a busca por qualquer coluna (como 'id' ou 'uuid') e o valor correspondente.
*
* #### Fluxo de Operação:
* 1. **Execução da Consulta:** O método monta e executa uma consulta `SELECT` que usa o `$identifier` de forma dinâmica na cláusula `WHERE` e um parâmetro nomeado seguro (`:value`) para o valor. A consulta utiliza `DB::fetchOne`, indicando que apenas um registro é esperado.
* 2. **Verificação de Resultados:**
* - Se a consulta for bem-sucedida e **nenhuma espécie for encontrada** (`empty($result)`), retorna um array com **`status` 'fail'** e código **200 OK**.
* - Se uma espécie for encontrada, retorna um array com **`status` 'success'**, código **200 OK** e os dados do registro na chave `output`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante a consulta retorna um array com **`status` 'error'**, o código de erro da exceção e uma mensagem de erro detalhada.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'id', 'uuid', 'descricao').
* @param mixed $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados da espécie, se encontrada.
*/
public function getEspecieByIdentifier(string $identifier, mixed $value): array {
try {
$sql =
"SELECT
id,
uuid,
descricao
FROM {$this->petAuxEspecieTable}
WHERE {$identifier} = :value
ORDER BY descricao ASC";
$params = [
':value' => $value
];
$result = DB::fetchOne(sql: $sql, params: $params);
if(empty($result)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Espécie não encontrada.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Espécie encontrada com sucesso.',
'output' => [
'data' => $result
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Busca uma única raça de pet no banco de dados por um identificador específico.
*
* Este método recupera os dados de uma raça da tabela auxiliar `{$this->petAuxRacaTable}` de forma flexível, permitindo a busca por qualquer coluna como identificador (`$identifier`) e o valor correspondente. A consulta retorna apenas um registro e é ordenada pela descrição.
*
* #### Fluxo de Operação:
* 1. **Execução da Consulta:** O método monta e executa uma consulta `SELECT` que usa o `$identifier` de forma dinâmica na cláusula `WHERE` e um parâmetro nomeado seguro (`:value`) para o valor.
* 2. **Verificação de Resultados:**
* - Se uma raça for encontrada, retorna um array com **`status` 'success'**, código **200 OK** e os dados da raça na chave `output`.
* - Se a consulta for bem-sucedida, mas **nenhuma raça for encontrada** (`empty($result)`), retorna um array com **`status` 'success'** (indicando que a consulta foi bem-sucedida) e uma mensagem de "Raça não encontrada.".
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante a consulta retorna um array com **`status` 'error'**, o código de erro da exceção (ou `500` como fallback) e uma mensagem de erro detalhada.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'id', 'descricao').
* @param mixed $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados da raça, se encontrada.
*/
public function getRacaByIdentifier(string $identifier, mixed $value): array {
try {
$sql =
"SELECT
id,
uuid,
especie_id,
descricao
FROM {$this->petAuxRacaTable}
WHERE {$identifier} = :value
ORDER BY descricao ASC";
$params = [
':value' => $value
];
$result = DB::fetchOne(sql: $sql, params: $params);
if(empty($result)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Raça não encontrada.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Raça encontrada com sucesso.',
'output' => [
'data' => $result
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Busca todas as raças de pets associadas a uma espécie específica.
*
* Este método primeiro consulta a espécie usando um identificador (UUID, ID, etc.) e, em seguida, usa o ID interno da espécie para listar todas as raças vinculadas a ela na tabela auxiliar `{$this->petAuxRacaTable}`.
*
* #### Fluxo de Operação:
* 1. **Busca da Espécie:** O método chama `getEspecieByIdentifier` para encontrar o ID da espécie. Se a busca falhar, o resultado de erro é retornado imediatamente.
* 2. **Consulta das Raças:** Executa uma consulta `SELECT` que busca todas as raças onde `especie_id` corresponde ao ID da espécie encontrado. Os resultados são ordenados alfabeticamente pela descrição.
* 3. **Verificação de Resultados:**
* - Se a consulta for bem-sucedida, mas **nenhuma raça for encontrada**, retorna um array com **`status` 'fail'** e código **200 OK**.
* - Se raças forem encontradas, retorna um array com **`status` 'success'**, código **200 OK** e os dados encontrados na chave `output`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo (além daquelas tratadas na busca inicial) retorna um array com **`status` 'error'**, o código de erro da exceção e uma mensagem de erro detalhada.
*
* @param string $identifier A coluna da tabela auxiliar de espécies a ser usada para identificar a espécie (ex: 'uuid', 'id').
* @param mixed $value O valor correspondente ao identificador da espécie.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e os dados das raças (`output['data']`).
*/
public function getRacasByEspecieIdentifier(string $identifier, mixed $value): array {
try {
$especieData = $this->getEspecieByIdentifier(identifier: $identifier, value: $value);
if($especieData['status'] !== 'success') {
return $especieData;
}
$especieData = $especieData['output']['data'];
$sql =
"SELECT
id,
uuid,
descricao
FROM {$this->petAuxRacaTable}
WHERE especie_id = :especie_id
ORDER BY descricao ASC";
$params = [
':especie_id' => $especieData['id']
];
$racas = DB::fetchAll(sql: $sql, params: $params);
if(empty($racas)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Nenhuma raça encontrada para a espécie informada.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Raças encontradas com sucesso.',
'output' => [
'data' => $racas
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Armazena uma nova raça de pet no banco de dados.
*
* Este método insere os dados de uma nova raça na tabela auxiliar `{$this->petAuxRacaTable}`. A raça é associada a uma espécie existente. A operação utiliza um bloco `try-catch` para garantir o tratamento de erros.
*
* #### Fluxo de Operação:
* 1. **Execução da Query:** Prepara e executa a instrução `INSERT` para inserir o novo registro com o UUID, ID da espécie e a descrição.
* 2. **Confirmação e Retorno de Sucesso:** Se a inserção for bem-sucedida, retorna um array com **`status` 'success'**, código **201 Created** e uma mensagem de êxito.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'**, o código de erro da exceção e uma mensagem detalhada de erro.
*
* @param PetRacaDTO $petRacaDTO O objeto DTO contendo o UUID, o ID da espécie (`especie_id`) e a descrição da raça a ser criada.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e uma mensagem.
*/
public function storeRaca(PetRacaDTO $petRacaDTO): array {
try {
$sql =
"INSERT INTO {$this->petAuxRacaTable} (
uuid,
especie_id,
descricao
) VALUES (
:uuid,
:especie_id,
:descricao
)";
$params = [
':uuid' => $petRacaDTO->getUuid(),
':especie_id' => $petRacaDTO->getEspecieId(),
':descricao' => $petRacaDTO->getDescricao()
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Raça armazenada com sucesso.',
'output' => [
'data' => [
'id' => DB::lastInsertId(),
'uuid' => $petRacaDTO->getUuid(),
'especie_id' => $petRacaDTO->getEspecieId(),
'descricao' => $petRacaDTO->getDescricao()
]
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Armazena um novo pet no banco de dados.
*
* Este método insere os dados de um novo pet na tabela `{$this->petTable}`, utilizando um objeto `PetDTO` que encapsula todas as informações relevantes. A operação é atômica e utiliza um bloco `try-catch` para garantir o tratamento de erros.
*
* #### Fluxo de Operação:
* 1. **Execução da Query:** Prepara e executa a instrução `INSERT` para inserir o novo registro com todos os detalhes do pet (UUID, nome, espécie, raça, dados de saúde, foto, etc.).
* 2. **Confirmação e Retorno de Sucesso:** Se a inserção for bem-sucedida, retorna um array com **`status` 'success'**, código **201 Created** e uma mensagem de êxito. Os dados do pet recém-criado, incluindo o ID gerado pelo banco, são retornados na chave `output`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'**, o código de erro da exceção e uma mensagem detalhada de erro.
*
* @param PetDTO $petDTO O objeto DTO contendo os dados do pet a ser criado.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e os dados do pet, se a operação for bem-sucedida.
*/
public function storePet(PetDTO $petDTO): array {
try {
$sql =
"INSERT INTO {$this->petTable} (
uuid,
nome,
especie_id,
raca_id,
caminho_foto,
registro_geral_animal,
photo_path,
data_nascimento,
data_obito,
sexo,
created_at,
updated_at,
deleted_at
) VALUES (
:uuid,
:nome,
:especie_id,
:raca_id,
:caminho_foto,
:registro_geral_animal,
:photo_path,
:data_nascimento,
:data_obito,
:sexo,
:created_at,
:updated_at,
:deleted_at
)";
$params = [
':uuid' => $petDTO->getUuid(),
':nome' => $petDTO->getNome(),
':especie_id' => $petDTO->getEspecieId(),
':raca_id' => $petDTO->getRacaId(),
':caminho_foto' => $petDTO->getCaminhoFoto(),
':registro_geral_animal' => $petDTO->getRegistroGeralAnimal(),
':photo_path' => $petDTO->getPhotoPath(),
':data_nascimento' => $petDTO->getDataNascimento(),
':data_obito' => $petDTO->getDataObito(),
':sexo' => $petDTO->getSexo(),
':created_at' => $petDTO->getCreatedAt(),
':updated_at' => $petDTO->getUpdatedAt(),
':deleted_at' => $petDTO->getDeletedAt()
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Pet armazenado com sucesso.',
'output' => [
'data' => [
'id' => DB::lastInsertId(),
'uuid' => $petDTO->getUuid(),
'nome' => $petDTO->getNome(),
'especie_id' => $petDTO->getEspecieId(),
'raca_id' => $petDTO->getRacaId(),
'caminho_foto' => $petDTO->getCaminhoFoto(),
'registro_geral_animal' => $petDTO->getRegistroGeralAnimal(),
'photo_path' => $petDTO->getPhotoPath(),
'data_nascimento' => $petDTO->getDataNascimento(),
'data_obito' => $petDTO->getDataObito(),
'sexo' => $petDTO->getSexo(),
'created_at' => $petDTO->getCreatedAt(),
'updated_at' => $petDTO->getUpdatedAt(),
'deleted_at' => $petDTO->getDeletedAt()
]
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Associa um pet a um usuário (tutor) no banco de dados.
*
* Este método insere um novo registro na tabela de associação `{$this->usuarioPet}`, estabelecendo a relação de propriedade entre o usuário e o pet.
*
* #### Fluxo de Operação:
* 1. **Execução da Query:** Prepara e executa a instrução `INSERT` para inserir os IDs do usuário e do pet na tabela de associação.
* 2. **Retorno de Sucesso:** Se a inserção for bem-sucedida, retorna um array com **`status` 'success'**, código **201 Created** e uma mensagem de êxito.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'**, o código de erro da exceção (ou `500` como fallback) e uma mensagem detalhada de erro.
*
* @param int $usuario_id O ID do usuário (tutor) a ser associado.
* @param int $pet_id O ID do pet a ser associado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e uma mensagem.
*/
public function storeUsuarioPet(int $usuario_id, int $pet_id): array {
try {
$sql =
"INSERT INTO {$this->usuarioPet} (
usuario_id,
pet_id
) VALUES (
:usuario_id,
:pet_id
)";
$params = [
':usuario_id' => $usuario_id,
':pet_id' => $pet_id
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Associação entre usuário e pet criada com sucesso.'
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Busca um único pet no banco de dados por um identificador específico.
*
* Este método recupera todos os detalhes de um pet da tabela `{$this->petTable}`. A busca é flexível e permite usar qualquer coluna como identificador (`$identifier`) e o valor correspondente.
*
* #### Fluxo de Operação:
* 1. **Execução da Query:** O método monta e executa uma consulta `SELECT` que usa o `$identifier` de forma dinâmica na cláusula `WHERE`. A consulta usa `DB::fetchOne`, indicando que apenas um registro é esperado, e ordena o resultado pelo nome.
* 2. **Verificação de Resultados:**
* - Se o pet for encontrado, retorna um array com **`status` 'success'**, código **200 OK** e os dados do pet na chave `output`.
* - Se o pet **não for encontrado** (`empty($result)`), retorna um array com **`status` 'success'** (indicando que a consulta foi bem-sucedida) e uma mensagem de "Pet não encontrado.".
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'**, o código de erro da exceção (ou `500` como fallback) e uma mensagem de erro detalhada.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'id', 'registro_geral_animal').
* @param string $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do pet, se encontrado.
*/
public function getPetByIdentifier(string $identifier, string $value): array {
try {
$sql =
"SELECT
p.id,
p.uuid,
p.nome,
p.especie_id,
pae.descricao AS especie,
p.raca_id,
par.descricao AS raca,
p.caminho_foto,
p.registro_geral_animal,
p.photo_path,
p.data_nascimento,
p.data_obito,
p.sexo,
p.created_at,
p.updated_at,
p.deleted_at
FROM {$this->petTable} p
LEFT JOIN {$this->petAuxRacaTable} par ON p.raca_id = par.id
LEFT JOIN {$this->petAuxEspecieTable} pae ON p.especie_id = pae.id
WHERE p.{$identifier} = :value
ORDER BY p.nome ASC";
$params = [
':value' => $value
];
$result = DB::fetchOne(sql: $sql, params: $params);
if(empty($result)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Pet não encontrado.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Pet encontrado com sucesso.',
'output' => [
'data' => $result
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
use AxiumPHP\Core\Router;
use Zampet\Module\Tutor\v0\Controllers\PetController;
Router::group(
prefix: '/pet',
callback: function() {
// Rota para listar todos os pets do tutor autenticado
Router::GET(
uri: '/list-my-pets',
handler: [PetController::class, 'listMyPets']
);
// Rota para listar todas as espécies e raças de pets
Router::GET(
uri: '/especies',
handler: [PetController::class, 'getAllEspecies']
);
// Cria Nova Raça
Router::POST(
uri: '/create-raca',
handler: [PetController::class, 'storeRaca']
);
// Cria Novo Pet
Router::POST(
uri: '/create-pet',
handler: [PetController::class, 'storePet']
);
// Consulta Detalhes do Pet
Router::GET(
uri: '/{pet_uuid}',
handler: [PetController::class, 'getPetDetails']
);
},
middlewares: [
]
);

View File

@@ -0,0 +1,9 @@
<?php
use AxiumPHP\Core\Router;
Router::group(
prefix: '/tutor/v0',
callback: function() {
require 'PetRoutes.php';
}
);

View File

@@ -0,0 +1,241 @@
<?php
namespace Zampet\Module\Tutor\v0\Services;
use DateTime;
use Exception;
use Ramsey\Uuid\Uuid;
use Zampet\Module\Auth\v0\Models\UsuarioModel;
use Zampet\Services\DB;
use Zampet\Services\JWTService;
use Zampet\Helpers\DataValidator;
use Zampet\Module\Tutor\v0\DTO\PetDTO;
use Zampet\Module\Tutor\v0\DTO\PetRacaDTO;
use Zampet\Module\Tutor\v0\Models\PetModel;
class PetService {
/**
* Retorna a lista de todas as espécies de pets disponíveis no sistema.
*
* Este método atua como um **wrapper** (invólucro) para o método de mesmo nome na classe `PetModel`. Ele delega a responsabilidade de buscar a lista completa das espécies de pets cadastradas para a camada de modelo (DAO).
*
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados das espécies, conforme retornado pelo `PetModel`.
*/
public function getAllEspecies(): array {
return (new PetModel())->getAllEspecies();
}
/**
* Lista todos os pets de um tutor específico, buscando pelo UUID do tutor.
*
* Este método atua como um **wrapper** para o método `listPetsByTutorIdentifier` da classe `PetModel`. Ele delega a responsabilidade de buscar a lista completa e detalhada de pets associados a um tutor, utilizando o UUID como o identificador principal.
*
* @param string $uuid O UUID do tutor cujos pets devem ser listados.
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados dos pets, conforme retornado pelo `PetModel`.
*/
public function listPetsByTutorUuid(string $uuid): array {
return (new PetModel())->listPetsByTutorIdentifier(identifier: 'uuid', value: $uuid);
}
/**
* Busca uma espécie de pet no sistema por um identificador específico.
*
* Este método atua como um **wrapper** para o método de busca de espécies na classe `PetModel`. Ele delega a responsabilidade de consultar a espécie para a camada de modelo (DAO), que lida com a lógica de acesso ao banco de dados.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'id', 'uuid', 'descricao').
* @param string $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados da espécie, conforme retornado pelo `PetModel`.
*/
public function getEspecieByIdentifier(string $identifier, string $value): array {
return (new PetModel())->getEspecieByIdentifier(identifier: $identifier, value: $value);
}
/**
* Busca uma raça de pet no sistema por um identificador específico.
*
* Este método atua como um **wrapper** (invólucro) para o método de busca de raças na classe `PetModel`. Ele delega a responsabilidade de consultar a raça para a camada de modelo (DAO), que lida com a lógica de acesso ao banco de dados e retorna o resultado.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'id', 'descricao').
* @param string $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados da raça, conforme retornado pelo `PetModel`.
*/
public function getRacaByIdentifier(string $identifier, string $value): array {
return (new PetModel())->getRacaByIdentifier(identifier: $identifier, value: $value);
}
/**
* Busca um pet no sistema por um identificador específico.
*
* Este método atua como um **wrapper** (invólucro) para o método de busca de pets na classe `PetModel`. Ele delega a responsabilidade de consultar o pet para a camada de modelo (DAO), que lida com a lógica de acesso ao banco de dados.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'id', 'nome').
* @param string $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados do pet, conforme retornado pelo `PetModel`.
*/
public function getPetByIdentifier(string $identifier, string $value): array {
return (new PetModel())->getPetByIdentifier(identifier: $identifier, value: $value);
}
/**
* Cria uma nova raça de pet no sistema após validação.
*
* Este método orquestra a criação de uma nova raça de pet. Ele realiza a validação dos dados de entrada, define metadados essenciais (UUID) e persiste o registro no banco de dados, garantindo a atomicidade da operação através de uma transação.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Validação de Dados:** Utiliza o `DataValidator` para verificar se os campos **'especie_id'** e **'descricao'** foram preenchidos.
* 3. **Encerramento em Caso de Falha na Validação:** Se houver erros de validação, a transação é revertida (`DB::rollBack()`) e um array de erro formatado é retornado com o código `400 Bad Request`.
* 4. **Preparação Final dos Dados:** Define um **UUID** único para a nova raça.
* 5. **Persistência:** Chama o método `storeRaca` do `PetModel` para inserir o registro no banco de dados. Se a persistência falhar, uma `Exception` é lançada com a mensagem de erro do modelo.
* 6. **Confirmação e Retorno:** Se a persistência for bem-sucedida, a transação é **confirmada** (`DB::commit()`) e o resultado de sucesso é retornado com o código `201 Created`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo é capturada, e a transação é **revertida** (`DB::rollBack()`).
* - Retorna um array de erro formatado contendo o código HTTP e a mensagem da exceção.
*
* @param PetRacaDTO $petRacaDTO O objeto DTO contendo o ID da espécie e a descrição da nova raça.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e os dados de saída, ou erros de validação.
*/
public function storeRaca(PetRacaDTO $petRacaDTO): array {
// Inicia transação no banco de dados
DB::beginTransaction();
try {
// Valida os dados do DTO
$dataValidador = new DataValidator(inputs: $petRacaDTO->toArray());
$dataValidador->validate(field: 'especie_id', rule: 'notEmpty', message: 'Espécie é obrigatória.');
$dataValidador->validate(field: 'descricao', rule: 'notEmpty', message: 'Descrição é obrigatória.');
// Se houver erros de validação, retornar resposta de erro
if(!$dataValidador->passes()) {
DB::rollBack();
return [
'response_code' => 400,
'status' => 'error',
'message' => 'Validation errors',
'output' => [
'errors' => $dataValidador->getErrors()
]
];
}
// Define dados adicionais
$petRacaDTO->setUuid(uuid: Uuid::uuid7()->toString());
// Cria a nova raça no banco de dados
$storeRaca = (new PetModel())->storeRaca(petRacaDTO: $petRacaDTO);
if($storeRaca['status'] !== 'success') {
throw new Exception(message: $storeRaca['message'], code: $storeRaca['response_code']);
}
DB::commit();
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Raça criada com sucesso.',
'output' => $storeRaca['output']
];
} catch(Exception $e) {
DB::rollBack();
return [
'response_code' => $e->getCode(),
'status' => 'error',
'message' => $e->getMessage()
];
}
}
/**
* Armazena um novo pet no sistema e o associa ao usuário autenticado.
*
* Este método é responsável por orquestrar a criação de um novo pet, desde a validação inicial até a persistência do pet e o registro da relação com o tutor. A operação é **atômica** e garante a integridade dos dados através de uma transação de banco de dados.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Validação de Dados:** O `DataValidator` verifica a presença e validade dos campos obrigatórios (nome, raça UUID, data de nascimento e sexo). Se falhar, a transação é revertida e um erro `400 Bad Request` é retornado.
* 3. **Validação de Duplicidade (RGA):** Se fornecido, o Registro Geral Animal (RGA) é verificado quanto à unicidade. Se um pet com o mesmo RGA for encontrado, uma `Exception` é lançada com o código `409 Conflict`.
* 4. **Preparação Final dos Dados:** O UUID do pet e o timestamp de criação são definidos.
* 5. **Criação do Pet:** O método `storePet` do `PetModel` é chamado para inserir o registro do pet. Se falhar, uma `Exception` é lançada.
* 6. **Associação ao Tutor:** O token JWT é decodificado para obter o UUID do tutor. O ID interno do tutor é buscado, e o pet é associado ao tutor através do método `storeUsuarioPet`. Se o tutor não for encontrado ou a associação falhar, uma `Exception` é lançada.
* 7. **Confirmação e Retorno:** Se todas as operações forem bem-sucedidas, a transação é **confirmada** (`DB::commit()`) e o resultado de sucesso é retornado com o código `201 Created`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo é capturada, a transação é **revertida** (`DB::rollBack()`), e um array de erro formatado contendo o código HTTP e a mensagem da exceção é retornado.
*
* @param PetDTO $petDTO O objeto DTO contendo os dados do novo pet.
* @return array Um array associativo com o status da operação, código HTTP e os dados do pet criado.
*/
public function storePet(PetDTO $petDTO): array {
// Inicia transação no banco de dados
DB::beginTransaction();
try {
// Valida os dados do DTO
$dataValidador = new DataValidator(inputs: $petDTO->toArray());
$dataValidador->validate(field: 'nome', rule: 'notEmpty', message: 'Nome do pet é obrigatório.');
$dataValidador->validate(field: 'sexo', rule: 'notEmpty', message: 'Sexo do pet é obrigatório.');
$dataValidador->validate(field: 'raca_id', rule: 'notEmpty', message: 'Raça do pet é obrigatória.');
$dataValidador->validate(field: 'especie_id', rule: 'notEmpty', message: 'Espécie do pet é obrigatória.');
$dataValidador->validate(field: 'data_nascimento', rule: 'notEmpty', message: 'Data de nascimento do pet é obrigatória.');
// Se houver erros de validação, retornar resposta de erro
if(!$dataValidador->passes()) {
return [
'response_code' => 400,
'status' => 'error',
'message' => 'Validation errors',
'output' => [
'errors' => $dataValidador->getErrors()
]
];
}
// Verifica se o Registro Geral Animal do PET (RGA) é único, se fornecido
if(($petDTO->getRegistroGeralAnimal() !== null) && !empty($petDTO->getRegistroGeralAnimal())) {
$existingPet = $this->getPetByIdentifier(identifier: 'registro_geral_animal', value: $petDTO->getRegistroGeralAnimal());
if($existingPet['status'] === 'success' && !empty($existingPet['output']['data'])) {
throw new Exception(message: 'Já existe um pet cadastrado com este Registro Geral Animal (RGA).', code: 409);
}
}
// Define dados adicionais
$petDTO->setUuid(uuid: Uuid::uuid7()->toString());
$petDTO->setCreatedAt(created_at: (new DateTime())->format(format: 'Y-m-d H:i:s'));
// Cria o novo pet no banco de dados
$storePet = (new PetModel())->storePet(petDTO: $petDTO);
if($storePet['status'] !== 'success') {
throw new Exception(message: $storePet['message'], code: $storePet['response_code']);
}
// Consulta o token JWT
$decodedToken = JWTService::decode(token: JWTService::getBearerToken());
// Consulta o ID do tutor pelo UUID
$getTutor = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'uuid', value: $decodedToken['user_uuid']);
if($getTutor['status'] !== 'success' || empty($getTutor['output']['data'])) {
throw new Exception(message: 'Tutor não encontrado.', code: 404);
}
// Relaciona o pet ao tutor autenticado
$dataValidador = (new PetModel())->storeUsuarioPet(usuario_id: $getTutor['output']['data']['id'], pet_id: $storePet['output']['data']['id']);
if($dataValidador['status'] !== 'success') {
throw new Exception(message: $dataValidador['message'], code: $dataValidador['response_code']);
}
DB::commit();
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Pet criado com sucesso.',
'output' => $storePet['output']
];
} catch(Exception $e) {
DB::rollBack();
return [
'response_code' => $e->getCode(),
'status' => 'error',
'message' => $e->getMessage()
];
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Zampet\Module\Tutor;
use DateTime;
use Exception;
// Caminho do manifest.json
$manifestPath = realpath(path: __DIR__ . "/manifest.json");
if (!$manifestPath || !file_exists(filename: $manifestPath)) {
throw new Exception(message: "Arquivo 'manifest.json' não encontrado.");
}
// Lê e decodifica
$manifest = json_decode(json: file_get_contents(filename: $manifestPath), associative: true);
// Verifica estrutura
if (!isset($manifest['apis']) || !is_array(value: $manifest['apis'])) {
throw new Exception(message: "Estrutura de 'apis' inválida no manifest.");
}
$now = (new DateTime())->format(format: 'Y-m-d');
$newApis = [];
$updated = false;
// Checa se tem versão esperando que começa hoje
foreach ($manifest['apis'] as $api) {
if ($api['status'] === 'planned' && $api['start_date'] === $now) {
// Promote a nova
$api['status'] = 'active';
$newApis[] = $api;
$updated = true;
} elseif ($api['status'] === 'active' && $api['start_date'] !== $now) {
// Ignora versão antiga ativa
continue;
} else {
$newApis[] = $api;
}
}
// Atualiza manifest se tiver mudança
if ($updated) {
$manifest['apis'] = $newApis;
file_put_contents(filename: $manifestPath, data: json_encode(value: $manifest, flags: JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// Carrega a versão ativa
$loaded = false;
foreach ($manifest['apis'] as $api) {
$startDate = new DateTime(datetime: $api['start_date']);
if ($api['status'] === 'active' && $startDate <= new DateTime()) {
$routeFile = MODULE_PATH . "/{$manifest['dirName']}/{$api['version']}/Routes/Routes.php";
if (file_exists(filename: $routeFile)) {
require_once realpath(path: $routeFile);
$loaded = true;
break;
} else {
throw new Exception(message: "Arquivo de rotas não encontrado para a versão '{$api['version']}'.");
}
}
}
// Verifica se carregou
if (!$loaded) {
throw new Exception(message: "Nenhuma versão ativa da API foi carregada.");
}

View File

@@ -0,0 +1,16 @@
{
"name": "Tutor",
"dirName": "Tutor",
"slug": "tutor",
"description": "Gerencia informações dos tutores e suas interações.",
"uuid": "0199b5de-f72c-702a-ba26-1c1d6f6d3dc2",
"version": "1.0",
"dependencies": [],
"apis": [
{
"version": "v0",
"status": "active",
"start_date": "2025-08-01"
}
]
}

0
app/Module/blank_file Executable file
View File

129
app/Storage/Logs/php_errors.log Executable file
View File

@@ -0,0 +1,129 @@
[03-Oct-2025 16:59:04 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 16:59:04 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 16:59:04 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 16:59:04 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 16:59:04 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 16:59:36 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 16:59:36 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 16:59:36 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 16:59:36 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 16:59:36 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:01:25 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:01:25 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:01:25 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:01:25 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:01:25 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:01:57 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:01:57 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:01:57 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:01:57 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:01:57 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:03:27 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:03:27 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:03:27 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:03:27 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:03:27 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:04:20 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:04:20 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:04:20 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:04:20 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:04:20 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:05:37 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:05:37 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:05:37 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:05:37 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:05:37 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:07:45 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:07:45 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:07:45 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:07:45 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:07:45 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:25:56 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:25:56 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:25:56 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:25:56 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:25:56 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:26:15 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:26:15 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:26:15 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:26:15 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:26:15 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:26:25 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:26:25 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:26:25 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:26:25 America/Fortaleza] PHP 2. require() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:26:25 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:28:02 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:28:02 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:28:02 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:28:02 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:28:02 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:30:33 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:30:33 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:30:33 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:30:33 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:4
[03-Oct-2025 17:30:33 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(4): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:30:45 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:30:45 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:30:45 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:30:45 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:30:45 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:31:46 America/Fortaleza] PHP Warning: Undefined array key 1 in /var/www/zampet/backend/zampet-cli on line 13
[03-Oct-2025 17:31:46 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:31:46 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0

43
composer.json Executable file
View File

@@ -0,0 +1,43 @@
{
"name": "cybercore/zampet",
"type": "project",
"require": {
"php": ">=8.2",
"ramsey/uuid": "4.7.4",
"firebase/php-jwt": "v6.11.1",
"vlucas/phpdotenv": "v5.6.2",
"symfony/var-dumper": "^v6.4.26",
"claudecio/axiumphp": "dev-dev-router-update",
"ext-json": "*"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/claudecio/axiumphp"
}
],
"autoload": {
"psr-4": {
"Zampet\\": "app/Common",
"Zampet\\Module\\": "app/Module"
}
},
"require-dev": {
"phpunit/phpunit": "^10.5.58",
"squizlabs/php_codesniffer": "^3.13.4"
},
"scripts": {
"test": "phpunit",
"cs": "phpcs --standard=PSR12 src/"
},
"minimum-stability": "dev",
"prefer-stable": true,
"license": "MIT",
"authors": [
{
"name": "Claudecio Martins",
"email": "contato@claudecio.is-a.dev",
"role": "Developer"
}
]
}

2726
composer.lock generated Executable file

File diff suppressed because it is too large Load Diff

0
database/blank_file Executable file
View File

View File

@@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS prestador (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
uuid UUID NOT NULL UNIQUE,
nome VARCHAR(100) NOT NULL
);

View File

@@ -0,0 +1,78 @@
CREATE TABLE IF NOT EXISTS usuario_aux_status (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
descricao VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS usuario_aux_perfil (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
descricao VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS usuario (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
uuid UUID NOT NULL UNIQUE,
nome_completo VARCHAR(255) NOT NULL,
status_id INT REFERENCES usuario_aux_status(id) ON UPDATE CASCADE ON DELETE CASCADE DEFAULT 1,
documentCpf VARCHAR(14) DEFAULT NULL UNIQUE,
documentCrmv VARCHAR(20) DEFAULT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
senha VARCHAR(255) NOT NULL,
telefone VARCHAR(20) DEFAULT NULL,
data_nascimento DATE DEFAULT NULL,
endereco_rua VARCHAR(255) DEFAULT NULL,
endereco_numero VARCHAR(20) DEFAULT NULL,
endereco_complemento VARCHAR(255) DEFAULT NULL,
endereco_bairro VARCHAR(100) DEFAULT NULL,
endereco_cidade VARCHAR(100) DEFAULT NULL,
endereco_uf VARCHAR(2) DEFAULT NULL,
endereco_cep VARCHAR(10) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT NULL,
deleted_at TIMESTAMP DEFAULT NULL
);
CREATE TABLE IF NOT EXISTS usuario_token(
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
usuario_id BIGINT REFERENCES usuario(id) ON UPDATE CASCADE ON DELETE CASCADE,
token TEXT NOT NULL UNIQUE,
revoked SMALLINT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT NULL,
UNIQUE (usuario_id, token)
);
CREATE TABLE IF NOT EXISTS pet_aux_especie (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
uuid UUID NOT NULL UNIQUE,
descricao VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS pet_aux_raca (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
uuid UUID NOT NULL UNIQUE,
especie_id BIGINT REFERENCES pet_aux_especie(id) ON UPDATE CASCADE ON DELETE CASCADE,
descricao VARCHAR(50) NOT NULL,
UNIQUE (especie_id, descricao)
);
CREATE TABLE IF NOT EXISTS pet (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
uuid UUID NOT NULL UNIQUE,
nome VARCHAR(100) NOT NULL,
especie_id BIGINT REFERENCES pet_aux_especie(id) ON UPDATE CASCADE ON DELETE CASCADE,
raca_id BIGINT REFERENCES pet_aux_raca(id) ON UPDATE CASCADE ON DELETE CASCADE,
caminho_foto TEXT DEFAULT NULL,
registro_geral_animal VARCHAR(50) DEFAULT NULL,
photo_path TEXT DEFAULT NULL,
data_nascimento DATE NOT NULL,
data_obito DATE DEFAULT NULL,
sexo CHAR(1) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT NULL,
deleted_at TIMESTAMP DEFAULT NULL
);
CREATE TABLE IF NOT EXISTS usuario_pet (
usuario_id BIGINT REFERENCES usuario(id) ON UPDATE CASCADE ON DELETE CASCADE,
pet_id BIGINT REFERENCES pet(id) ON UPDATE CASCADE ON DELETE CASCADE
);

View File

View File

@@ -0,0 +1,46 @@
-- Tabela usuario_aux_status
INSERT INTO usuario_aux_status (id, descricao) VALUES
(1, 'Ativo'),
(2, 'Inativo'),
(3, 'Aguardando Confirmação'),
(4, 'Suspenso');
SELECT setval(pg_get_serial_sequence('usuario_aux_status','id'), (SELECT MAX(id) FROM usuario_aux_status));
-- Tabela usuario_aux_perfil
INSERT INTO usuario_aux_perfil (id, descricao) VALUES
(1, 'Cliente'),
(2, 'Veterinário'),
(3, 'Funcionário'),
(4, 'Gerência do Prestador'),
(5, 'Administrador do Sistema');
SELECT setval(pg_get_serial_sequence('usuario_aux_perfil','id'), (SELECT MAX(id) FROM usuario_aux_perfil));
-- Tabela pet_aux_especie
INSERT INTO pet_aux_especie (id, uuid, descricao) VALUES
(1, '0199b633-a22a-7dbd-8425-6511d1c14200', 'Felina'),
(2, '0199b633-fed8-70de-9c99-123196f7dde6', 'Canina'),
(3, '0199b634-0fb1-70b7-98c6-7ad9f8d18da5', 'Ave'),
(4, '0199b634-2a73-7d77-8312-4c1fbc8ec324', 'Roedor'),
(5, '0199b634-45ea-7d48-b209-e4238d1de557', 'Réptil'),
(6, '0199b634-652d-7cd1-95d1-511cf57ba3ce', 'Peixe'),
(7, '0199b634-8b45-7e5f-86e0-dd34a33a4aa5', 'Anfíbio'),
(8, '0199bc00-ab91-772d-8309-e8300482d5f1', 'Procionídeo');
SELECT setval(pg_get_serial_sequence('pet_aux_especie','id'), (SELECT MAX(id) FROM pet_aux_especie));
-- Tabela pet_aux_raca
INSERT INTO pet_aux_raca (id, uuid, especie_id, descricao) VALUES
(1, '0199b634-a30b-7fbc-b69f-ba9b8c9e6716', 1, 'Sem Raça Definida (SRD)'),
(2, '0199b634-cb61-77fe-90e7-79018c839c7c', 2, 'Sem Raça Definida (SRD)');
SELECT setval(pg_get_serial_sequence('pet_aux_raca','id'), (SELECT MAX(id) FROM pet_aux_raca));
-- Tabela usuario
INSERT INTO usuario (id, uuid, nome_completo, status_id, documentcpf, documentcrmv, email, senha, telefone, data_nascimento, endereco_rua, endereco_numero, endereco_complemento, endereco_bairro, endereco_cidade, endereco_uf, endereco_cep, created_at, updated_at, deleted_at) VALUES
(1,'0199b5db-037e-73cf-9867-51ad35bc47e8','Iranildo Carlos',1,'04983082352',NULL,'iranildocarlosd@gmail.com','$argon2id$v=19$m=65536,t=4,p=1$Ym9oUEZwMTl0d2RxQ2pKdA$oqa3S2DGkHyGrGlo8A+di4Y6e1y1KS3SlyxWsI0+t5E',NULL,'2004-07-23',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2025-10-05 16:30:53.000',NULL,NULL),
(2,'0199b5db-28ab-73f7-b777-230dc096cac6','Claudecio Santos da Costa Martins Júnior',1,'06799082347',NULL,'contato@claudecio.is-a.dev','$argon2id$v=19$m=65536,t=4,p=1$YTdHbXRJbHpVOHlQN0N3Sg$NKm2mtMa1CQwu8cZQFilYN8Rab75WWRf7vjrCTAKWbA',NULL,'2004-09-07',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2025-10-05 16:31:02.000',NULL,NULL),
(3,'0199b5fa-2fb6-7187-91e4-f79d236923f0','Pablo Ruan Feitoza Cahaves',1,'06426789364',NULL,'pablorfchaves@gmail.com','$argon2id$v=19$m=65536,t=4,p=1$RW9icWNKMW1VdHhBSGhyWQ$sS9aW3pwsShei4FiRf7cJo0Kwo5pIXeZbbyTwwyfrrk',NULL,'2005-04-12',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2025-10-05 17:04:56.000',NULL,NULL);
SELECT setval(pg_get_serial_sequence('usuario','id'), (SELECT MAX(id) FROM usuario));

10
public/.htaccess Executable file
View File

@@ -0,0 +1,10 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Se não for arquivo nem pasta, manda tudo pro index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]
</IfModule>

92
public/index.php Executable file
View File

@@ -0,0 +1,92 @@
<?php
// ============================
// Configurações de erro
// ============================
ini_set(option: 'display_errors', value: 1);
ini_set(option: 'display_startup_errors', value: 1);
ini_set(option: 'log_errors', value: 1);
ini_set(option: 'error_log', value: __DIR__ . "/../app/Storage/Logs/php_errors.log");
error_reporting(error_level: E_ALL);
// Importa autoload do Composer
require_once realpath(path: __DIR__ . '/../vendor/autoload.php');
// ============================
// Namespaces usados
// ============================
use AxiumPHP\Core\Router;
use Zampet\Config\Config;
use AxiumPHP\Core\LoggerService;
use Zampet\Module\Auth\v0\Middlewares\AuthMiddleware;
// ============================
// Configurações globais
// ============================
$Config = Config::getInstance();
// ============================
// Define constantes do sistema
// ============================
const ROUTER_MODE = 'JSON';
const APP_SYS_MODE = 'DEV'; // PROD = Production, DEV = Development
define(constant_name: 'ROOT_SYSTEM_PATH', value: realpath(path: __DIR__ . "/.."));
define(constant_name: 'INI_SYSTEM_PATH', value: realpath(path: __DIR__ . "/../app"));
define(constant_name: 'MODULE_PATH', value: realpath(path: __DIR__ . "/../app/Module"));
define(constant_name: 'STORAGE_FOLDER_PATH', value: realpath(path: __DIR__ . "/../app/Storage"));
// ============================
// Inicializa LoggerService
// ============================
LoggerService::init(
driver: LoggerService::DRIVER_FILE,
logDir: __DIR__ . '/../app/Storage/Logs'
);
// ============================
// Define origens permitidas
// ============================
const ROUTER_ALLOWED_ORIGINS = [
'*'
];
// ============================
// Define fuso horário
// ============================
date_default_timezone_set(timezoneId: $Config->get(key: "SYSTEM_TIMEZONE"));
// ============================
// Inicia sessão se necessário
// ============================
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// ============================
// Inicializa o roteador
// ============================
new Router();
// ============================
// Carregamento de Módulos Essenciais do sistema
// ============================
require_once MODULE_PATH . '/Auth/v0/bootstrap.php';
Router::group(
prefix: '/module',
callback: function() {
// Carrega módulo do Tutor
require_once MODULE_PATH . '/Tutor/v0/bootstrap.php';
},
middlewares: [
AuthMiddleware::class."::handle"
]
);
// ============================
// Dispara o roteador
// ============================
// Só dispara Router se for uma requisição web
if (php_sapi_name() !== 'cli') {
Router::dispatch();
}

22
vendor/autoload.php vendored Executable file
View File

@@ -0,0 +1,22 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit1020934c13f8fb367bf4afd130c40a82::getLoader();

119
vendor/bin/php-parse vendored Executable file
View File

@@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../nikic/php-parser/bin/php-parse)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse');
}
}
return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse';

119
vendor/bin/phpcbf vendored Executable file
View File

@@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../squizlabs/php_codesniffer/bin/phpcbf)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf');
}
}
return include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf';

119
vendor/bin/phpcs vendored Executable file
View File

@@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../squizlabs/php_codesniffer/bin/phpcs)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs');
}
}
return include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs';

122
vendor/bin/phpunit vendored Executable file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../phpunit/phpunit/phpunit)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
$GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] = $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'] = array(realpath(__DIR__ . '/..'.'/phpunit/phpunit/phpunit'));
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = 'phpvfscomposer://'.$this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$data = str_replace('__DIR__', var_export(dirname($this->realpath), true), $data);
$data = str_replace('__FILE__', var_export($this->realpath, true), $data);
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
}
}
return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';

119
vendor/bin/var-dump-server vendored Executable file
View File

@@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../symfony/var-dumper/Resources/bin/var-dump-server)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
}
}
return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server';

445
vendor/brick/math/CHANGELOG.md vendored Executable file
View File

@@ -0,0 +1,445 @@
# Changelog
All notable changes to this project will be documented in this file.
## [0.11.0](https://github.com/brick/math/releases/tag/0.11.0) - 2023-01-16
💥 **Breaking changes**
- Minimum PHP version is now 8.0
- Methods accepting a union of types are now strongly typed<sup>*</sup>
- `MathException` now extends `Exception` instead of `RuntimeException`
<sup>* You may now run into type errors if you were passing `Stringable` objects to `of()` or any of the methods
internally calling `of()`, with `strict_types` enabled. You can fix this by casting `Stringable` objects to `string`
first.</sup>
## [0.10.2](https://github.com/brick/math/releases/tag/0.10.2) - 2022-08-11
👌 **Improvements**
- `BigRational::toFloat()` now simplifies the fraction before performing division (#73) thanks to @olsavmic
## [0.10.1](https://github.com/brick/math/releases/tag/0.10.1) - 2022-08-02
**New features**
- `BigInteger::gcdMultiple()` returns the GCD of multiple `BigInteger` numbers
## [0.10.0](https://github.com/brick/math/releases/tag/0.10.0) - 2022-06-18
💥 **Breaking changes**
- Minimum PHP version is now 7.4
## [0.9.3](https://github.com/brick/math/releases/tag/0.9.3) - 2021-08-15
🚀 **Compatibility with PHP 8.1**
- Support for custom object serialization; this removes a warning on PHP 8.1 due to the `Serializable` interface being deprecated (#60) thanks @TRowbotham
## [0.9.2](https://github.com/brick/math/releases/tag/0.9.2) - 2021-01-20
🐛 **Bug fix**
- Incorrect results could be returned when using the BCMath calculator, with a default scale set with `bcscale()`, on PHP >= 7.2 (#55).
## [0.9.1](https://github.com/brick/math/releases/tag/0.9.1) - 2020-08-19
**New features**
- `BigInteger::not()` returns the bitwise `NOT` value
🐛 **Bug fixes**
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
## [0.9.0](https://github.com/brick/math/releases/tag/0.9.0) - 2020-08-18
👌 **Improvements**
- `BigNumber::of()` now accepts `.123` and `123.` formats, both of which return a `BigDecimal`
💥 **Breaking changes**
- Deprecated method `BigInteger::powerMod()` has been removed - use `modPow()` instead
- Deprecated method `BigInteger::parse()` has been removed - use `fromBase()` instead
## [0.8.17](https://github.com/brick/math/releases/tag/0.8.17) - 2020-08-19
🐛 **Bug fix**
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
## [0.8.16](https://github.com/brick/math/releases/tag/0.8.16) - 2020-08-18
🚑 **Critical fix**
- This version reintroduces the deprecated `BigInteger::parse()` method, that has been removed by mistake in version `0.8.9` and should have lasted for the whole `0.8` release cycle.
**New features**
- `BigInteger::modInverse()` calculates a modular multiplicative inverse
- `BigInteger::fromBytes()` creates a `BigInteger` from a byte string
- `BigInteger::toBytes()` converts a `BigInteger` to a byte string
- `BigInteger::randomBits()` creates a pseudo-random `BigInteger` of a given bit length
- `BigInteger::randomRange()` creates a pseudo-random `BigInteger` between two bounds
💩 **Deprecations**
- `BigInteger::powerMod()` is now deprecated in favour of `modPow()`
## [0.8.15](https://github.com/brick/math/releases/tag/0.8.15) - 2020-04-15
🐛 **Fixes**
- added missing `ext-json` requirement, due to `BigNumber` implementing `JsonSerializable`
⚡️ **Optimizations**
- additional optimization in `BigInteger::remainder()`
## [0.8.14](https://github.com/brick/math/releases/tag/0.8.14) - 2020-02-18
**New features**
- `BigInteger::getLowestSetBit()` returns the index of the rightmost one bit
## [0.8.13](https://github.com/brick/math/releases/tag/0.8.13) - 2020-02-16
**New features**
- `BigInteger::isEven()` tests whether the number is even
- `BigInteger::isOdd()` tests whether the number is odd
- `BigInteger::testBit()` tests if a bit is set
- `BigInteger::getBitLength()` returns the number of bits in the minimal representation of the number
## [0.8.12](https://github.com/brick/math/releases/tag/0.8.12) - 2020-02-03
🛠️ **Maintenance release**
Classes are now annotated for better static analysis with [psalm](https://psalm.dev/).
This is a maintenance release: no bug fixes, no new features, no breaking changes.
## [0.8.11](https://github.com/brick/math/releases/tag/0.8.11) - 2020-01-23
**New feature**
`BigInteger::powerMod()` performs a power-with-modulo operation. Useful for crypto.
## [0.8.10](https://github.com/brick/math/releases/tag/0.8.10) - 2020-01-21
**New feature**
`BigInteger::mod()` returns the **modulo** of two numbers. The *modulo* differs from the *remainder* when the signs of the operands are different.
## [0.8.9](https://github.com/brick/math/releases/tag/0.8.9) - 2020-01-08
⚡️ **Performance improvements**
A few additional optimizations in `BigInteger` and `BigDecimal` when one of the operands can be returned as is. Thanks to @tomtomsen in #24.
## [0.8.8](https://github.com/brick/math/releases/tag/0.8.8) - 2019-04-25
🐛 **Bug fixes**
- `BigInteger::toBase()` could return an empty string for zero values (BCMath & Native calculators only, GMP calculator unaffected)
**New features**
- `BigInteger::toArbitraryBase()` converts a number to an arbitrary base, using a custom alphabet
- `BigInteger::fromArbitraryBase()` converts a string in an arbitrary base, using a custom alphabet, back to a number
These methods can be used as the foundation to convert strings between different bases/alphabets, using BigInteger as an intermediate representation.
💩 **Deprecations**
- `BigInteger::parse()` is now deprecated in favour of `fromBase()`
`BigInteger::fromBase()` works the same way as `parse()`, with 2 minor differences:
- the `$base` parameter is required, it does not default to `10`
- it throws a `NumberFormatException` instead of an `InvalidArgumentException` when the number is malformed
## [0.8.7](https://github.com/brick/math/releases/tag/0.8.7) - 2019-04-20
**Improvements**
- Safer conversion from `float` when using custom locales
- **Much faster** `NativeCalculator` implementation 🚀
You can expect **at least a 3x performance improvement** for common arithmetic operations when using the library on systems without GMP or BCMath; it gets exponentially faster on multiplications with a high number of digits. This is due to calculations now being performed on whole blocks of digits (the block size depending on the platform, 32-bit or 64-bit) instead of digit-by-digit as before.
## [0.8.6](https://github.com/brick/math/releases/tag/0.8.6) - 2019-04-11
**New method**
`BigNumber::sum()` returns the sum of one or more numbers.
## [0.8.5](https://github.com/brick/math/releases/tag/0.8.5) - 2019-02-12
**Bug fix**: `of()` factory methods could fail when passing a `float` in environments using a `LC_NUMERIC` locale with a decimal separator other than `'.'` (#20).
Thanks @manowark 👍
## [0.8.4](https://github.com/brick/math/releases/tag/0.8.4) - 2018-12-07
**New method**
`BigDecimal::sqrt()` calculates the square root of a decimal number, to a given scale.
## [0.8.3](https://github.com/brick/math/releases/tag/0.8.3) - 2018-12-06
**New method**
`BigInteger::sqrt()` calculates the square root of a number (thanks @peter279k).
**New exception**
`NegativeNumberException` is thrown when calling `sqrt()` on a negative number.
## [0.8.2](https://github.com/brick/math/releases/tag/0.8.2) - 2018-11-08
**Performance update**
- Further improvement of `toInt()` performance
- `NativeCalculator` can now perform some multiplications more efficiently
## [0.8.1](https://github.com/brick/math/releases/tag/0.8.1) - 2018-11-07
Performance optimization of `toInt()` methods.
## [0.8.0](https://github.com/brick/math/releases/tag/0.8.0) - 2018-10-13
**Breaking changes**
The following deprecated methods have been removed. Use the new method name instead:
| Method removed | Replacement method |
| --- | --- |
| `BigDecimal::getIntegral()` | `BigDecimal::getIntegralPart()` |
| `BigDecimal::getFraction()` | `BigDecimal::getFractionalPart()` |
---
**New features**
`BigInteger` has been augmented with 5 new methods for bitwise operations:
| New method | Description |
| --- | --- |
| `and()` | performs a bitwise `AND` operation on two numbers |
| `or()` | performs a bitwise `OR` operation on two numbers |
| `xor()` | performs a bitwise `XOR` operation on two numbers |
| `shiftedLeft()` | returns the number shifted left by a number of bits |
| `shiftedRight()` | returns the number shifted right by a number of bits |
Thanks to @DASPRiD 👍
## [0.7.3](https://github.com/brick/math/releases/tag/0.7.3) - 2018-08-20
**New method:** `BigDecimal::hasNonZeroFractionalPart()`
**Renamed/deprecated methods:**
- `BigDecimal::getIntegral()` has been renamed to `getIntegralPart()` and is now deprecated
- `BigDecimal::getFraction()` has been renamed to `getFractionalPart()` and is now deprecated
## [0.7.2](https://github.com/brick/math/releases/tag/0.7.2) - 2018-07-21
**Performance update**
`BigInteger::parse()` and `toBase()` now use GMP's built-in base conversion features when available.
## [0.7.1](https://github.com/brick/math/releases/tag/0.7.1) - 2018-03-01
This is a maintenance release, no code has been changed.
- When installed with `--no-dev`, the autoloader does not autoload tests anymore
- Tests and other files unnecessary for production are excluded from the dist package
This will help make installations more compact.
## [0.7.0](https://github.com/brick/math/releases/tag/0.7.0) - 2017-10-02
Methods renamed:
- `BigNumber:sign()` has been renamed to `getSign()`
- `BigDecimal::unscaledValue()` has been renamed to `getUnscaledValue()`
- `BigDecimal::scale()` has been renamed to `getScale()`
- `BigDecimal::integral()` has been renamed to `getIntegral()`
- `BigDecimal::fraction()` has been renamed to `getFraction()`
- `BigRational::numerator()` has been renamed to `getNumerator()`
- `BigRational::denominator()` has been renamed to `getDenominator()`
Classes renamed:
- `ArithmeticException` has been renamed to `MathException`
## [0.6.2](https://github.com/brick/math/releases/tag/0.6.2) - 2017-10-02
The base class for all exceptions is now `MathException`.
`ArithmeticException` has been deprecated, and will be removed in 0.7.0.
## [0.6.1](https://github.com/brick/math/releases/tag/0.6.1) - 2017-10-02
A number of methods have been renamed:
- `BigNumber:sign()` is deprecated; use `getSign()` instead
- `BigDecimal::unscaledValue()` is deprecated; use `getUnscaledValue()` instead
- `BigDecimal::scale()` is deprecated; use `getScale()` instead
- `BigDecimal::integral()` is deprecated; use `getIntegral()` instead
- `BigDecimal::fraction()` is deprecated; use `getFraction()` instead
- `BigRational::numerator()` is deprecated; use `getNumerator()` instead
- `BigRational::denominator()` is deprecated; use `getDenominator()` instead
The old methods will be removed in version 0.7.0.
## [0.6.0](https://github.com/brick/math/releases/tag/0.6.0) - 2017-08-25
- Minimum PHP version is now [7.1](https://gophp71.org/); for PHP 5.6 and PHP 7.0 support, use version `0.5`
- Deprecated method `BigDecimal::withScale()` has been removed; use `toScale()` instead
- Method `BigNumber::toInteger()` has been renamed to `toInt()`
## [0.5.4](https://github.com/brick/math/releases/tag/0.5.4) - 2016-10-17
`BigNumber` classes now implement [JsonSerializable](http://php.net/manual/en/class.jsonserializable.php).
The JSON output is always a string.
## [0.5.3](https://github.com/brick/math/releases/tag/0.5.3) - 2016-03-31
This is a bugfix release. Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
## [0.5.2](https://github.com/brick/math/releases/tag/0.5.2) - 2015-08-06
The `$scale` parameter of `BigDecimal::dividedBy()` is now optional again.
## [0.5.1](https://github.com/brick/math/releases/tag/0.5.1) - 2015-07-05
**New method: `BigNumber::toScale()`**
This allows to convert any `BigNumber` to a `BigDecimal` with a given scale, using rounding if necessary.
## [0.5.0](https://github.com/brick/math/releases/tag/0.5.0) - 2015-07-04
**New features**
- Common `BigNumber` interface for all classes, with the following methods:
- `sign()` and derived methods (`isZero()`, `isPositive()`, ...)
- `compareTo()` and derived methods (`isEqualTo()`, `isGreaterThan()`, ...) that work across different `BigNumber` types
- `toBigInteger()`, `toBigDecimal()`, `toBigRational`() conversion methods
- `toInteger()` and `toFloat()` conversion methods to native types
- Unified `of()` behaviour: every class now accepts any type of number, provided that it can be safely converted to the current type
- New method: `BigDecimal::exactlyDividedBy()`; this method automatically computes the scale of the result, provided that the division yields a finite number of digits
- New methods: `BigRational::quotient()` and `remainder()`
- Fine-grained exceptions: `DivisionByZeroException`, `RoundingNecessaryException`, `NumberFormatException`
- Factory methods `zero()`, `one()` and `ten()` available in all classes
- Rounding mode reintroduced in `BigInteger::dividedBy()`
This release also comes with many performance improvements.
---
**Breaking changes**
- `BigInteger`:
- `getSign()` is renamed to `sign()`
- `toString()` is renamed to `toBase()`
- `BigInteger::dividedBy()` now throws an exception by default if the remainder is not zero; use `quotient()` to get the previous behaviour
- `BigDecimal`:
- `getSign()` is renamed to `sign()`
- `getUnscaledValue()` is renamed to `unscaledValue()`
- `getScale()` is renamed to `scale()`
- `getIntegral()` is renamed to `integral()`
- `getFraction()` is renamed to `fraction()`
- `divideAndRemainder()` is renamed to `quotientAndRemainder()`
- `dividedBy()` now takes a **mandatory** `$scale` parameter **before** the rounding mode
- `toBigInteger()` does not accept a `$roundingMode` parameter anymore
- `toBigRational()` does not simplify the fraction anymore; explicitly add `->simplified()` to get the previous behaviour
- `BigRational`:
- `getSign()` is renamed to `sign()`
- `getNumerator()` is renamed to `numerator()`
- `getDenominator()` is renamed to `denominator()`
- `of()` is renamed to `nd()`, while `parse()` is renamed to `of()`
- Miscellaneous:
- `ArithmeticException` is moved to an `Exception\` sub-namespace
- `of()` factory methods now throw `NumberFormatException` instead of `InvalidArgumentException`
## [0.4.3](https://github.com/brick/math/releases/tag/0.4.3) - 2016-03-31
Backport of two bug fixes from the 0.5 branch:
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
## [0.4.2](https://github.com/brick/math/releases/tag/0.4.2) - 2015-06-16
New method: `BigDecimal::stripTrailingZeros()`
## [0.4.1](https://github.com/brick/math/releases/tag/0.4.1) - 2015-06-12
Introducing a `BigRational` class, to perform calculations on fractions of any size.
## [0.4.0](https://github.com/brick/math/releases/tag/0.4.0) - 2015-06-12
Rounding modes have been removed from `BigInteger`, and are now a concept specific to `BigDecimal`.
`BigInteger::dividedBy()` now always returns the quotient of the division.
## [0.3.5](https://github.com/brick/math/releases/tag/0.3.5) - 2016-03-31
Backport of two bug fixes from the 0.5 branch:
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
## [0.3.4](https://github.com/brick/math/releases/tag/0.3.4) - 2015-06-11
New methods:
- `BigInteger::remainder()` returns the remainder of a division only
- `BigInteger::gcd()` returns the greatest common divisor of two numbers
## [0.3.3](https://github.com/brick/math/releases/tag/0.3.3) - 2015-06-07
Fix `toString()` not handling negative numbers.
## [0.3.2](https://github.com/brick/math/releases/tag/0.3.2) - 2015-06-07
`BigInteger` and `BigDecimal` now have a `getSign()` method that returns:
- `-1` if the number is negative
- `0` if the number is zero
- `1` if the number is positive
## [0.3.1](https://github.com/brick/math/releases/tag/0.3.1) - 2015-06-05
Minor performance improvements
## [0.3.0](https://github.com/brick/math/releases/tag/0.3.0) - 2015-06-04
The `$roundingMode` and `$scale` parameters have been swapped in `BigDecimal::dividedBy()`.
## [0.2.2](https://github.com/brick/math/releases/tag/0.2.2) - 2015-06-04
Stronger immutability guarantee for `BigInteger` and `BigDecimal`.
So far, it would have been possible to break immutability of these classes by calling the `unserialize()` internal function. This release fixes that.
## [0.2.1](https://github.com/brick/math/releases/tag/0.2.1) - 2015-06-02
Added `BigDecimal::divideAndRemainder()`
## [0.2.0](https://github.com/brick/math/releases/tag/0.2.0) - 2015-05-22
- `min()` and `max()` do not accept an `array` anymore, but a variable number of parameters
- **minimum PHP version is now 5.6**
- continuous integration with PHP 7
## [0.1.1](https://github.com/brick/math/releases/tag/0.1.1) - 2014-09-01
- Added `BigInteger::power()`
- Added HHVM support
## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31
First beta release.

20
vendor/brick/math/LICENSE vendored Executable file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013-present Benjamin Morel
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

34
vendor/brick/math/composer.json vendored Executable file
View File

@@ -0,0 +1,34 @@
{
"name": "brick/math",
"description": "Arbitrary-precision arithmetic library",
"type": "library",
"keywords": [
"Brick",
"Math",
"Arbitrary-precision",
"Arithmetic",
"BigInteger",
"BigDecimal",
"BigRational",
"Bignum"
],
"license": "MIT",
"require": {
"php": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"php-coveralls/php-coveralls": "^2.2",
"vimeo/psalm": "5.0.0"
},
"autoload": {
"psr-4": {
"Brick\\Math\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Brick\\Math\\Tests\\": "tests/"
}
}
}

786
vendor/brick/math/src/BigDecimal.php vendored Executable file
View File

@@ -0,0 +1,786 @@
<?php
declare(strict_types=1);
namespace Brick\Math;
use Brick\Math\Exception\DivisionByZeroException;
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NegativeNumberException;
use Brick\Math\Internal\Calculator;
/**
* Immutable, arbitrary-precision signed decimal numbers.
*
* @psalm-immutable
*/
final class BigDecimal extends BigNumber
{
/**
* The unscaled value of this decimal number.
*
* This is a string of digits with an optional leading minus sign.
* No leading zero must be present.
* No leading minus sign must be present if the value is 0.
*/
private string $value;
/**
* The scale (number of digits after the decimal point) of this decimal number.
*
* This must be zero or more.
*/
private int $scale;
/**
* Protected constructor. Use a factory method to obtain an instance.
*
* @param string $value The unscaled value, validated.
* @param int $scale The scale, validated.
*/
protected function __construct(string $value, int $scale = 0)
{
$this->value = $value;
$this->scale = $scale;
}
/**
* Creates a BigDecimal of the given value.
*
* @throws MathException If the value cannot be converted to a BigDecimal.
*
* @psalm-pure
*/
public static function of(BigNumber|int|float|string $value) : BigDecimal
{
return parent::of($value)->toBigDecimal();
}
/**
* Creates a BigDecimal from an unscaled value and a scale.
*
* Example: `(12345, 3)` will result in the BigDecimal `12.345`.
*
* @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger.
* @param int $scale The scale of the number, positive or zero.
*
* @throws \InvalidArgumentException If the scale is negative.
*
* @psalm-pure
*/
public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0) : BigDecimal
{
if ($scale < 0) {
throw new \InvalidArgumentException('The scale cannot be negative.');
}
return new BigDecimal((string) BigInteger::of($value), $scale);
}
/**
* Returns a BigDecimal representing zero, with a scale of zero.
*
* @psalm-pure
*/
public static function zero() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $zero
*/
static $zero;
if ($zero === null) {
$zero = new BigDecimal('0');
}
return $zero;
}
/**
* Returns a BigDecimal representing one, with a scale of zero.
*
* @psalm-pure
*/
public static function one() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $one
*/
static $one;
if ($one === null) {
$one = new BigDecimal('1');
}
return $one;
}
/**
* Returns a BigDecimal representing ten, with a scale of zero.
*
* @psalm-pure
*/
public static function ten() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $ten
*/
static $ten;
if ($ten === null) {
$ten = new BigDecimal('10');
}
return $ten;
}
/**
* Returns the sum of this number and the given one.
*
* The result has a scale of `max($this->scale, $that->scale)`.
*
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal.
*
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
*/
public function plus(BigNumber|int|float|string $that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->value === '0' && $that->scale <= $this->scale) {
return $this;
}
if ($this->value === '0' && $this->scale <= $that->scale) {
return $that;
}
[$a, $b] = $this->scaleValues($this, $that);
$value = Calculator::get()->add($a, $b);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
return new BigDecimal($value, $scale);
}
/**
* Returns the difference of this number and the given one.
*
* The result has a scale of `max($this->scale, $that->scale)`.
*
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal.
*
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
*/
public function minus(BigNumber|int|float|string $that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->value === '0' && $that->scale <= $this->scale) {
return $this;
}
[$a, $b] = $this->scaleValues($this, $that);
$value = Calculator::get()->sub($a, $b);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
return new BigDecimal($value, $scale);
}
/**
* Returns the product of this number and the given one.
*
* The result has a scale of `$this->scale + $that->scale`.
*
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
*
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
*/
public function multipliedBy(BigNumber|int|float|string $that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->value === '1' && $that->scale === 0) {
return $this;
}
if ($this->value === '1' && $this->scale === 0) {
return $that;
}
$value = Calculator::get()->mul($this->value, $that->value);
$scale = $this->scale + $that->scale;
return new BigDecimal($value, $scale);
}
/**
* Returns the result of the division of this number by the given one, at the given scale.
*
* @param BigNumber|int|float|string $that The divisor.
* @param int|null $scale The desired scale, or null to use the scale of this number.
* @param int $roundingMode An optional rounding mode.
*
* @throws \InvalidArgumentException If the scale or rounding mode is invalid.
* @throws MathException If the number is invalid, is zero, or rounding was necessary.
*/
public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->isZero()) {
throw DivisionByZeroException::divisionByZero();
}
if ($scale === null) {
$scale = $this->scale;
} elseif ($scale < 0) {
throw new \InvalidArgumentException('Scale cannot be negative.');
}
if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
return $this;
}
$p = $this->valueWithMinScale($that->scale + $scale);
$q = $that->valueWithMinScale($this->scale - $scale);
$result = Calculator::get()->divRound($p, $q, $roundingMode);
return new BigDecimal($result, $scale);
}
/**
* Returns the exact result of the division of this number by the given one.
*
* The scale of the result is automatically calculated to fit all the fraction digits.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero,
* or the result yields an infinite number of digits.
*/
public function exactlyDividedBy(BigNumber|int|float|string $that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->value === '0') {
throw DivisionByZeroException::divisionByZero();
}
[, $b] = $this->scaleValues($this, $that);
$d = \rtrim($b, '0');
$scale = \strlen($b) - \strlen($d);
$calculator = Calculator::get();
foreach ([5, 2] as $prime) {
for (;;) {
$lastDigit = (int) $d[-1];
if ($lastDigit % $prime !== 0) {
break;
}
$d = $calculator->divQ($d, (string) $prime);
$scale++;
}
}
return $this->dividedBy($that, $scale)->stripTrailingZeros();
}
/**
* Returns this number exponentiated to the given value.
*
* The result has a scale of `$this->scale * $exponent`.
*
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
*/
public function power(int $exponent) : BigDecimal
{
if ($exponent === 0) {
return BigDecimal::one();
}
if ($exponent === 1) {
return $this;
}
if ($exponent < 0 || $exponent > Calculator::MAX_POWER) {
throw new \InvalidArgumentException(\sprintf(
'The exponent %d is not in the range 0 to %d.',
$exponent,
Calculator::MAX_POWER
));
}
return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent);
}
/**
* Returns the quotient of the division of this number by this given one.
*
* The quotient has a scale of `0`.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*/
public function quotient(BigNumber|int|float|string $that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->isZero()) {
throw DivisionByZeroException::divisionByZero();
}
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
$quotient = Calculator::get()->divQ($p, $q);
return new BigDecimal($quotient, 0);
}
/**
* Returns the remainder of the division of this number by this given one.
*
* The remainder has a scale of `max($this->scale, $that->scale)`.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*/
public function remainder(BigNumber|int|float|string $that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->isZero()) {
throw DivisionByZeroException::divisionByZero();
}
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
$remainder = Calculator::get()->divR($p, $q);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
return new BigDecimal($remainder, $scale);
}
/**
* Returns the quotient and remainder of the division of this number by the given one.
*
* The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @return BigDecimal[] An array containing the quotient and the remainder.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*/
public function quotientAndRemainder(BigNumber|int|float|string $that) : array
{
$that = BigDecimal::of($that);
if ($that->isZero()) {
throw DivisionByZeroException::divisionByZero();
}
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
[$quotient, $remainder] = Calculator::get()->divQR($p, $q);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
$quotient = new BigDecimal($quotient, 0);
$remainder = new BigDecimal($remainder, $scale);
return [$quotient, $remainder];
}
/**
* Returns the square root of this number, rounded down to the given number of decimals.
*
* @throws \InvalidArgumentException If the scale is negative.
* @throws NegativeNumberException If this number is negative.
*/
public function sqrt(int $scale) : BigDecimal
{
if ($scale < 0) {
throw new \InvalidArgumentException('Scale cannot be negative.');
}
if ($this->value === '0') {
return new BigDecimal('0', $scale);
}
if ($this->value[0] === '-') {
throw new NegativeNumberException('Cannot calculate the square root of a negative number.');
}
$value = $this->value;
$addDigits = 2 * $scale - $this->scale;
if ($addDigits > 0) {
// add zeros
$value .= \str_repeat('0', $addDigits);
} elseif ($addDigits < 0) {
// trim digits
if (-$addDigits >= \strlen($this->value)) {
// requesting a scale too low, will always yield a zero result
return new BigDecimal('0', $scale);
}
$value = \substr($value, 0, $addDigits);
}
$value = Calculator::get()->sqrt($value);
return new BigDecimal($value, $scale);
}
/**
* Returns a copy of this BigDecimal with the decimal point moved $n places to the left.
*/
public function withPointMovedLeft(int $n) : BigDecimal
{
if ($n === 0) {
return $this;
}
if ($n < 0) {
return $this->withPointMovedRight(-$n);
}
return new BigDecimal($this->value, $this->scale + $n);
}
/**
* Returns a copy of this BigDecimal with the decimal point moved $n places to the right.
*/
public function withPointMovedRight(int $n) : BigDecimal
{
if ($n === 0) {
return $this;
}
if ($n < 0) {
return $this->withPointMovedLeft(-$n);
}
$value = $this->value;
$scale = $this->scale - $n;
if ($scale < 0) {
if ($value !== '0') {
$value .= \str_repeat('0', -$scale);
}
$scale = 0;
}
return new BigDecimal($value, $scale);
}
/**
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
*/
public function stripTrailingZeros() : BigDecimal
{
if ($this->scale === 0) {
return $this;
}
$trimmedValue = \rtrim($this->value, '0');
if ($trimmedValue === '') {
return BigDecimal::zero();
}
$trimmableZeros = \strlen($this->value) - \strlen($trimmedValue);
if ($trimmableZeros === 0) {
return $this;
}
if ($trimmableZeros > $this->scale) {
$trimmableZeros = $this->scale;
}
$value = \substr($this->value, 0, -$trimmableZeros);
$scale = $this->scale - $trimmableZeros;
return new BigDecimal($value, $scale);
}
/**
* Returns the absolute value of this number.
*/
public function abs() : BigDecimal
{
return $this->isNegative() ? $this->negated() : $this;
}
/**
* Returns the negated value of this number.
*/
public function negated() : BigDecimal
{
return new BigDecimal(Calculator::get()->neg($this->value), $this->scale);
}
public function compareTo(BigNumber|int|float|string $that) : int
{
$that = BigNumber::of($that);
if ($that instanceof BigInteger) {
$that = $that->toBigDecimal();
}
if ($that instanceof BigDecimal) {
[$a, $b] = $this->scaleValues($this, $that);
return Calculator::get()->cmp($a, $b);
}
return - $that->compareTo($this);
}
public function getSign() : int
{
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
}
public function getUnscaledValue() : BigInteger
{
return self::newBigInteger($this->value);
}
public function getScale() : int
{
return $this->scale;
}
/**
* Returns a string representing the integral part of this decimal number.
*
* Example: `-123.456` => `-123`.
*/
public function getIntegralPart() : string
{
if ($this->scale === 0) {
return $this->value;
}
$value = $this->getUnscaledValueWithLeadingZeros();
return \substr($value, 0, -$this->scale);
}
/**
* Returns a string representing the fractional part of this decimal number.
*
* If the scale is zero, an empty string is returned.
*
* Examples: `-123.456` => '456', `123` => ''.
*/
public function getFractionalPart() : string
{
if ($this->scale === 0) {
return '';
}
$value = $this->getUnscaledValueWithLeadingZeros();
return \substr($value, -$this->scale);
}
/**
* Returns whether this decimal number has a non-zero fractional part.
*/
public function hasNonZeroFractionalPart() : bool
{
return $this->getFractionalPart() !== \str_repeat('0', $this->scale);
}
public function toBigInteger() : BigInteger
{
$zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0);
return self::newBigInteger($zeroScaleDecimal->value);
}
public function toBigDecimal() : BigDecimal
{
return $this;
}
public function toBigRational() : BigRational
{
$numerator = self::newBigInteger($this->value);
$denominator = self::newBigInteger('1' . \str_repeat('0', $this->scale));
return self::newBigRational($numerator, $denominator, false);
}
public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
{
if ($scale === $this->scale) {
return $this;
}
return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode);
}
public function toInt() : int
{
return $this->toBigInteger()->toInt();
}
public function toFloat() : float
{
return (float) (string) $this;
}
public function __toString() : string
{
if ($this->scale === 0) {
return $this->value;
}
$value = $this->getUnscaledValueWithLeadingZeros();
return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale);
}
/**
* This method is required for serializing the object and SHOULD NOT be accessed directly.
*
* @internal
*
* @return array{value: string, scale: int}
*/
public function __serialize(): array
{
return ['value' => $this->value, 'scale' => $this->scale];
}
/**
* This method is only here to allow unserializing the object and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @param array{value: string, scale: int} $data
*
* @throws \LogicException
*/
public function __unserialize(array $data): void
{
if (isset($this->value)) {
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
}
$this->value = $data['value'];
$this->scale = $data['scale'];
}
/**
* This method is required by interface Serializable and SHOULD NOT be accessed directly.
*
* @internal
*/
public function serialize() : string
{
return $this->value . ':' . $this->scale;
}
/**
* This method is only here to implement interface Serializable and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @throws \LogicException
*/
public function unserialize($value) : void
{
if (isset($this->value)) {
throw new \LogicException('unserialize() is an internal function, it must not be called directly.');
}
[$value, $scale] = \explode(':', $value);
$this->value = $value;
$this->scale = (int) $scale;
}
/**
* Puts the internal values of the given decimal numbers on the same scale.
*
* @return array{string, string} The scaled integer values of $x and $y.
*/
private function scaleValues(BigDecimal $x, BigDecimal $y) : array
{
$a = $x->value;
$b = $y->value;
if ($b !== '0' && $x->scale > $y->scale) {
$b .= \str_repeat('0', $x->scale - $y->scale);
} elseif ($a !== '0' && $x->scale < $y->scale) {
$a .= \str_repeat('0', $y->scale - $x->scale);
}
return [$a, $b];
}
private function valueWithMinScale(int $scale) : string
{
$value = $this->value;
if ($this->value !== '0' && $scale > $this->scale) {
$value .= \str_repeat('0', $scale - $this->scale);
}
return $value;
}
/**
* Adds leading zeros if necessary to the unscaled value to represent the full decimal number.
*/
private function getUnscaledValueWithLeadingZeros() : string
{
$value = $this->value;
$targetLength = $this->scale + 1;
$negative = ($value[0] === '-');
$length = \strlen($value);
if ($negative) {
$length--;
}
if ($length >= $targetLength) {
return $this->value;
}
if ($negative) {
$value = \substr($value, 1);
}
$value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT);
if ($negative) {
$value = '-' . $value;
}
return $value;
}
}

1079
vendor/brick/math/src/BigInteger.php vendored Executable file

File diff suppressed because it is too large Load Diff

512
vendor/brick/math/src/BigNumber.php vendored Executable file
View File

@@ -0,0 +1,512 @@
<?php
declare(strict_types=1);
namespace Brick\Math;
use Brick\Math\Exception\DivisionByZeroException;
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NumberFormatException;
use Brick\Math\Exception\RoundingNecessaryException;
/**
* Common interface for arbitrary-precision rational numbers.
*
* @psalm-immutable
*/
abstract class BigNumber implements \Serializable, \JsonSerializable
{
/**
* The regular expression used to parse integer, decimal and rational numbers.
*/
private const PARSE_REGEXP =
'/^' .
'(?<sign>[\-\+])?' .
'(?:' .
'(?:' .
'(?<integral>[0-9]+)?' .
'(?<point>\.)?' .
'(?<fractional>[0-9]+)?' .
'(?:[eE](?<exponent>[\-\+]?[0-9]+))?' .
')|(?:' .
'(?<numerator>[0-9]+)' .
'\/?' .
'(?<denominator>[0-9]+)' .
')' .
')' .
'$/';
/**
* Creates a BigNumber of the given value.
*
* The concrete return type is dependent on the given value, with the following rules:
*
* - BigNumber instances are returned as is
* - integer numbers are returned as BigInteger
* - floating point numbers are converted to a string then parsed as such
* - strings containing a `/` character are returned as BigRational
* - strings containing a `.` character or using an exponential notation are returned as BigDecimal
* - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger
*
* @throws NumberFormatException If the format of the number is not valid.
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
*
* @psalm-pure
*/
public static function of(BigNumber|int|float|string $value) : BigNumber
{
if ($value instanceof BigNumber) {
return $value;
}
if (\is_int($value)) {
return new BigInteger((string) $value);
}
$value = \is_float($value) ? self::floatToString($value) : $value;
$throw = static function() use ($value) : void {
throw new NumberFormatException(\sprintf(
'The given value "%s" does not represent a valid number.',
$value
));
};
if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) {
$throw();
}
$getMatch = static fn(string $value): ?string => (($matches[$value] ?? '') !== '') ? $matches[$value] : null;
$sign = $getMatch('sign');
$numerator = $getMatch('numerator');
$denominator = $getMatch('denominator');
if ($numerator !== null) {
assert($denominator !== null);
if ($sign !== null) {
$numerator = $sign . $numerator;
}
$numerator = self::cleanUp($numerator);
$denominator = self::cleanUp($denominator);
if ($denominator === '0') {
throw DivisionByZeroException::denominatorMustNotBeZero();
}
return new BigRational(
new BigInteger($numerator),
new BigInteger($denominator),
false
);
}
$point = $getMatch('point');
$integral = $getMatch('integral');
$fractional = $getMatch('fractional');
$exponent = $getMatch('exponent');
if ($integral === null && $fractional === null) {
$throw();
}
if ($integral === null) {
$integral = '0';
}
if ($point !== null || $exponent !== null) {
$fractional = ($fractional ?? '');
$exponent = ($exponent !== null) ? (int) $exponent : 0;
if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) {
throw new NumberFormatException('Exponent too large.');
}
$unscaledValue = self::cleanUp(($sign ?? ''). $integral . $fractional);
$scale = \strlen($fractional) - $exponent;
if ($scale < 0) {
if ($unscaledValue !== '0') {
$unscaledValue .= \str_repeat('0', - $scale);
}
$scale = 0;
}
return new BigDecimal($unscaledValue, $scale);
}
$integral = self::cleanUp(($sign ?? '') . $integral);
return new BigInteger($integral);
}
/**
* Safely converts float to string, avoiding locale-dependent issues.
*
* @see https://github.com/brick/math/pull/20
*
* @psalm-pure
* @psalm-suppress ImpureFunctionCall
*/
private static function floatToString(float $float) : string
{
$currentLocale = \setlocale(LC_NUMERIC, '0');
\setlocale(LC_NUMERIC, 'C');
$result = (string) $float;
\setlocale(LC_NUMERIC, $currentLocale);
return $result;
}
/**
* Proxy method to access BigInteger's protected constructor from sibling classes.
*
* @internal
* @psalm-pure
*/
protected function newBigInteger(string $value) : BigInteger
{
return new BigInteger($value);
}
/**
* Proxy method to access BigDecimal's protected constructor from sibling classes.
*
* @internal
* @psalm-pure
*/
protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal
{
return new BigDecimal($value, $scale);
}
/**
* Proxy method to access BigRational's protected constructor from sibling classes.
*
* @internal
* @psalm-pure
*/
protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational
{
return new BigRational($numerator, $denominator, $checkDenominator);
}
/**
* Returns the minimum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
* to an instance of the class this method is called on.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-suppress LessSpecificReturnStatement
* @psalm-suppress MoreSpecificReturnType
* @psalm-pure
*/
public static function min(BigNumber|int|float|string ...$values) : static
{
$min = null;
foreach ($values as $value) {
$value = static::of($value);
if ($min === null || $value->isLessThan($min)) {
$min = $value;
}
}
if ($min === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
return $min;
}
/**
* Returns the maximum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
* to an instance of the class this method is called on.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-suppress LessSpecificReturnStatement
* @psalm-suppress MoreSpecificReturnType
* @psalm-pure
*/
public static function max(BigNumber|int|float|string ...$values) : static
{
$max = null;
foreach ($values as $value) {
$value = static::of($value);
if ($max === null || $value->isGreaterThan($max)) {
$max = $value;
}
}
if ($max === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
return $max;
}
/**
* Returns the sum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible
* to an instance of the class this method is called on.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-pure
*/
public static function sum(BigNumber|int|float|string ...$values) : static
{
/** @var static|null $sum */
$sum = null;
foreach ($values as $value) {
$value = static::of($value);
$sum = $sum === null ? $value : self::add($sum, $value);
}
if ($sum === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
return $sum;
}
/**
* Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException.
*
* @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to
* concrete classes the responsibility to perform the addition themselves or delegate it to the given number,
* depending on their ability to perform the operation. This will also require a version bump because we're
* potentially breaking custom BigNumber implementations (if any...)
*
* @psalm-pure
*/
private static function add(BigNumber $a, BigNumber $b) : BigNumber
{
if ($a instanceof BigRational) {
return $a->plus($b);
}
if ($b instanceof BigRational) {
return $b->plus($a);
}
if ($a instanceof BigDecimal) {
return $a->plus($b);
}
if ($b instanceof BigDecimal) {
return $b->plus($a);
}
/** @var BigInteger $a */
return $a->plus($b);
}
/**
* Removes optional leading zeros and + sign from the given number.
*
* @param string $number The number, validated as a non-empty string of digits with optional leading sign.
*
* @psalm-pure
*/
private static function cleanUp(string $number) : string
{
$firstChar = $number[0];
if ($firstChar === '+' || $firstChar === '-') {
$number = \substr($number, 1);
}
$number = \ltrim($number, '0');
if ($number === '') {
return '0';
}
if ($firstChar === '-') {
return '-' . $number;
}
return $number;
}
/**
* Checks if this number is equal to the given one.
*/
public function isEqualTo(BigNumber|int|float|string $that) : bool
{
return $this->compareTo($that) === 0;
}
/**
* Checks if this number is strictly lower than the given one.
*/
public function isLessThan(BigNumber|int|float|string $that) : bool
{
return $this->compareTo($that) < 0;
}
/**
* Checks if this number is lower than or equal to the given one.
*/
public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool
{
return $this->compareTo($that) <= 0;
}
/**
* Checks if this number is strictly greater than the given one.
*/
public function isGreaterThan(BigNumber|int|float|string $that) : bool
{
return $this->compareTo($that) > 0;
}
/**
* Checks if this number is greater than or equal to the given one.
*/
public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool
{
return $this->compareTo($that) >= 0;
}
/**
* Checks if this number equals zero.
*/
public function isZero() : bool
{
return $this->getSign() === 0;
}
/**
* Checks if this number is strictly negative.
*/
public function isNegative() : bool
{
return $this->getSign() < 0;
}
/**
* Checks if this number is negative or zero.
*/
public function isNegativeOrZero() : bool
{
return $this->getSign() <= 0;
}
/**
* Checks if this number is strictly positive.
*/
public function isPositive() : bool
{
return $this->getSign() > 0;
}
/**
* Checks if this number is positive or zero.
*/
public function isPositiveOrZero() : bool
{
return $this->getSign() >= 0;
}
/**
* Returns the sign of this number.
*
* @return int -1 if the number is negative, 0 if zero, 1 if positive.
*/
abstract public function getSign() : int;
/**
* Compares this number to the given one.
*
* @return int [-1,0,1] If `$this` is lower than, equal to, or greater than `$that`.
*
* @throws MathException If the number is not valid.
*/
abstract public function compareTo(BigNumber|int|float|string $that) : int;
/**
* Converts this number to a BigInteger.
*
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
*/
abstract public function toBigInteger() : BigInteger;
/**
* Converts this number to a BigDecimal.
*
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
*/
abstract public function toBigDecimal() : BigDecimal;
/**
* Converts this number to a BigRational.
*/
abstract public function toBigRational() : BigRational;
/**
* Converts this number to a BigDecimal with the given scale, using rounding if necessary.
*
* @param int $scale The scale of the resulting `BigDecimal`.
* @param int $roundingMode A `RoundingMode` constant.
*
* @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding.
* This only applies when RoundingMode::UNNECESSARY is used.
*/
abstract public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal;
/**
* Returns the exact value of this number as a native integer.
*
* If this number cannot be converted to a native integer without losing precision, an exception is thrown.
* Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
*
* @throws MathException If this number cannot be exactly converted to a native integer.
*/
abstract public function toInt() : int;
/**
* Returns an approximation of this number as a floating-point value.
*
* Note that this method can discard information as the precision of a floating-point value
* is inherently limited.
*
* If the number is greater than the largest representable floating point number, positive infinity is returned.
* If the number is less than the smallest representable floating point number, negative infinity is returned.
*/
abstract public function toFloat() : float;
/**
* Returns a string representation of this number.
*
* The output of this method can be parsed by the `of()` factory method;
* this will yield an object equal to this one, without any information loss.
*/
abstract public function __toString() : string;
public function jsonSerialize() : string
{
return $this->__toString();
}
}

445
vendor/brick/math/src/BigRational.php vendored Executable file
View File

@@ -0,0 +1,445 @@
<?php
declare(strict_types=1);
namespace Brick\Math;
use Brick\Math\Exception\DivisionByZeroException;
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NumberFormatException;
use Brick\Math\Exception\RoundingNecessaryException;
/**
* An arbitrarily large rational number.
*
* This class is immutable.
*
* @psalm-immutable
*/
final class BigRational extends BigNumber
{
/**
* The numerator.
*/
private BigInteger $numerator;
/**
* The denominator. Always strictly positive.
*/
private BigInteger $denominator;
/**
* Protected constructor. Use a factory method to obtain an instance.
*
* @param BigInteger $numerator The numerator.
* @param BigInteger $denominator The denominator.
* @param bool $checkDenominator Whether to check the denominator for negative and zero.
*
* @throws DivisionByZeroException If the denominator is zero.
*/
protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
{
if ($checkDenominator) {
if ($denominator->isZero()) {
throw DivisionByZeroException::denominatorMustNotBeZero();
}
if ($denominator->isNegative()) {
$numerator = $numerator->negated();
$denominator = $denominator->negated();
}
}
$this->numerator = $numerator;
$this->denominator = $denominator;
}
/**
* Creates a BigRational of the given value.
*
* @throws MathException If the value cannot be converted to a BigRational.
*
* @psalm-pure
*/
public static function of(BigNumber|int|float|string $value) : BigRational
{
return parent::of($value)->toBigRational();
}
/**
* Creates a BigRational out of a numerator and a denominator.
*
* If the denominator is negative, the signs of both the numerator and the denominator
* will be inverted to ensure that the denominator is always positive.
*
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
*
* @throws NumberFormatException If an argument does not represent a valid number.
* @throws RoundingNecessaryException If an argument represents a non-integer number.
* @throws DivisionByZeroException If the denominator is zero.
*
* @psalm-pure
*/
public static function nd(
BigNumber|int|float|string $numerator,
BigNumber|int|float|string $denominator,
) : BigRational {
$numerator = BigInteger::of($numerator);
$denominator = BigInteger::of($denominator);
return new BigRational($numerator, $denominator, true);
}
/**
* Returns a BigRational representing zero.
*
* @psalm-pure
*/
public static function zero() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $zero
*/
static $zero;
if ($zero === null) {
$zero = new BigRational(BigInteger::zero(), BigInteger::one(), false);
}
return $zero;
}
/**
* Returns a BigRational representing one.
*
* @psalm-pure
*/
public static function one() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $one
*/
static $one;
if ($one === null) {
$one = new BigRational(BigInteger::one(), BigInteger::one(), false);
}
return $one;
}
/**
* Returns a BigRational representing ten.
*
* @psalm-pure
*/
public static function ten() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $ten
*/
static $ten;
if ($ten === null) {
$ten = new BigRational(BigInteger::ten(), BigInteger::one(), false);
}
return $ten;
}
public function getNumerator() : BigInteger
{
return $this->numerator;
}
public function getDenominator() : BigInteger
{
return $this->denominator;
}
/**
* Returns the quotient of the division of the numerator by the denominator.
*/
public function quotient() : BigInteger
{
return $this->numerator->quotient($this->denominator);
}
/**
* Returns the remainder of the division of the numerator by the denominator.
*/
public function remainder() : BigInteger
{
return $this->numerator->remainder($this->denominator);
}
/**
* Returns the quotient and remainder of the division of the numerator by the denominator.
*
* @return BigInteger[]
*/
public function quotientAndRemainder() : array
{
return $this->numerator->quotientAndRemainder($this->denominator);
}
/**
* Returns the sum of this number and the given one.
*
* @param BigNumber|int|float|string $that The number to add.
*
* @throws MathException If the number is not valid.
*/
public function plus(BigNumber|int|float|string $that) : BigRational
{
$that = BigRational::of($that);
$numerator = $this->numerator->multipliedBy($that->denominator);
$numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator));
$denominator = $this->denominator->multipliedBy($that->denominator);
return new BigRational($numerator, $denominator, false);
}
/**
* Returns the difference of this number and the given one.
*
* @param BigNumber|int|float|string $that The number to subtract.
*
* @throws MathException If the number is not valid.
*/
public function minus(BigNumber|int|float|string $that) : BigRational
{
$that = BigRational::of($that);
$numerator = $this->numerator->multipliedBy($that->denominator);
$numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator));
$denominator = $this->denominator->multipliedBy($that->denominator);
return new BigRational($numerator, $denominator, false);
}
/**
* Returns the product of this number and the given one.
*
* @param BigNumber|int|float|string $that The multiplier.
*
* @throws MathException If the multiplier is not a valid number.
*/
public function multipliedBy(BigNumber|int|float|string $that) : BigRational
{
$that = BigRational::of($that);
$numerator = $this->numerator->multipliedBy($that->numerator);
$denominator = $this->denominator->multipliedBy($that->denominator);
return new BigRational($numerator, $denominator, false);
}
/**
* Returns the result of the division of this number by the given one.
*
* @param BigNumber|int|float|string $that The divisor.
*
* @throws MathException If the divisor is not a valid number, or is zero.
*/
public function dividedBy(BigNumber|int|float|string $that) : BigRational
{
$that = BigRational::of($that);
$numerator = $this->numerator->multipliedBy($that->denominator);
$denominator = $this->denominator->multipliedBy($that->numerator);
return new BigRational($numerator, $denominator, true);
}
/**
* Returns this number exponentiated to the given value.
*
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
*/
public function power(int $exponent) : BigRational
{
if ($exponent === 0) {
$one = BigInteger::one();
return new BigRational($one, $one, false);
}
if ($exponent === 1) {
return $this;
}
return new BigRational(
$this->numerator->power($exponent),
$this->denominator->power($exponent),
false
);
}
/**
* Returns the reciprocal of this BigRational.
*
* The reciprocal has the numerator and denominator swapped.
*
* @throws DivisionByZeroException If the numerator is zero.
*/
public function reciprocal() : BigRational
{
return new BigRational($this->denominator, $this->numerator, true);
}
/**
* Returns the absolute value of this BigRational.
*/
public function abs() : BigRational
{
return new BigRational($this->numerator->abs(), $this->denominator, false);
}
/**
* Returns the negated value of this BigRational.
*/
public function negated() : BigRational
{
return new BigRational($this->numerator->negated(), $this->denominator, false);
}
/**
* Returns the simplified value of this BigRational.
*/
public function simplified() : BigRational
{
$gcd = $this->numerator->gcd($this->denominator);
$numerator = $this->numerator->quotient($gcd);
$denominator = $this->denominator->quotient($gcd);
return new BigRational($numerator, $denominator, false);
}
public function compareTo(BigNumber|int|float|string $that) : int
{
return $this->minus($that)->getSign();
}
public function getSign() : int
{
return $this->numerator->getSign();
}
public function toBigInteger() : BigInteger
{
$simplified = $this->simplified();
if (! $simplified->denominator->isEqualTo(1)) {
throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.');
}
return $simplified->numerator;
}
public function toBigDecimal() : BigDecimal
{
return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator);
}
public function toBigRational() : BigRational
{
return $this;
}
public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
{
return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
}
public function toInt() : int
{
return $this->toBigInteger()->toInt();
}
public function toFloat() : float
{
$simplified = $this->simplified();
return $simplified->numerator->toFloat() / $simplified->denominator->toFloat();
}
public function __toString() : string
{
$numerator = (string) $this->numerator;
$denominator = (string) $this->denominator;
if ($denominator === '1') {
return $numerator;
}
return $this->numerator . '/' . $this->denominator;
}
/**
* This method is required for serializing the object and SHOULD NOT be accessed directly.
*
* @internal
*
* @return array{numerator: BigInteger, denominator: BigInteger}
*/
public function __serialize(): array
{
return ['numerator' => $this->numerator, 'denominator' => $this->denominator];
}
/**
* This method is only here to allow unserializing the object and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @param array{numerator: BigInteger, denominator: BigInteger} $data
*
* @throws \LogicException
*/
public function __unserialize(array $data): void
{
if (isset($this->numerator)) {
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
}
$this->numerator = $data['numerator'];
$this->denominator = $data['denominator'];
}
/**
* This method is required by interface Serializable and SHOULD NOT be accessed directly.
*
* @internal
*/
public function serialize() : string
{
return $this->numerator . '/' . $this->denominator;
}
/**
* This method is only here to implement interface Serializable and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @throws \LogicException
*/
public function unserialize($value) : void
{
if (isset($this->numerator)) {
throw new \LogicException('unserialize() is an internal function, it must not be called directly.');
}
[$numerator, $denominator] = \explode('/', $value);
$this->numerator = BigInteger::of($numerator);
$this->denominator = BigInteger::of($denominator);
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
/**
* Exception thrown when a division by zero occurs.
*/
class DivisionByZeroException extends MathException
{
/**
* @psalm-pure
*/
public static function divisionByZero() : DivisionByZeroException
{
return new self('Division by zero.');
}
/**
* @psalm-pure
*/
public static function modulusMustNotBeZero() : DivisionByZeroException
{
return new self('The modulus must not be zero.');
}
/**
* @psalm-pure
*/
public static function denominatorMustNotBeZero() : DivisionByZeroException
{
return new self('The denominator of a rational number cannot be zero.');
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
use Brick\Math\BigInteger;
/**
* Exception thrown when an integer overflow occurs.
*/
class IntegerOverflowException extends MathException
{
/**
* @psalm-pure
*/
public static function toIntOverflow(BigInteger $value) : IntegerOverflowException
{
$message = '%s is out of range %d to %d and cannot be represented as an integer.';
return new self(\sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX));
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
/**
* Base class for all math exceptions.
*/
class MathException extends \Exception
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
/**
* Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number.
*/
class NegativeNumberException extends MathException
{
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
/**
* Exception thrown when attempting to create a number from a string with an invalid format.
*/
class NumberFormatException extends MathException
{
/**
* @param string $char The failing character.
*
* @psalm-pure
*/
public static function charNotInAlphabet(string $char) : self
{
$ord = \ord($char);
if ($ord < 32 || $ord > 126) {
$char = \strtoupper(\dechex($ord));
if ($ord < 10) {
$char = '0' . $char;
}
} else {
$char = '"' . $char . '"';
}
return new self(sprintf('Char %s is not a valid character in the given alphabet.', $char));
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
/**
* Exception thrown when a number cannot be represented at the requested scale without rounding.
*/
class RoundingNecessaryException extends MathException
{
/**
* @psalm-pure
*/
public static function roundingNecessary() : RoundingNecessaryException
{
return new self('Rounding is necessary to represent the result of the operation at this scale.');
}
}

676
vendor/brick/math/src/Internal/Calculator.php vendored Executable file
View File

@@ -0,0 +1,676 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Internal;
use Brick\Math\Exception\RoundingNecessaryException;
use Brick\Math\RoundingMode;
/**
* Performs basic operations on arbitrary size integers.
*
* Unless otherwise specified, all parameters must be validated as non-empty strings of digits,
* without leading zero, and with an optional leading minus sign if the number is not zero.
*
* Any other parameter format will lead to undefined behaviour.
* All methods must return strings respecting this format, unless specified otherwise.
*
* @internal
*
* @psalm-immutable
*/
abstract class Calculator
{
/**
* The maximum exponent value allowed for the pow() method.
*/
public const MAX_POWER = 1000000;
/**
* The alphabet for converting from and to base 2 to 36, lowercase.
*/
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
/**
* The Calculator instance in use.
*/
private static ?Calculator $instance = null;
/**
* Sets the Calculator instance to use.
*
* An instance is typically set only in unit tests: the autodetect is usually the best option.
*
* @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect.
*/
final public static function set(?Calculator $calculator) : void
{
self::$instance = $calculator;
}
/**
* Returns the Calculator instance to use.
*
* If none has been explicitly set, the fastest available implementation will be returned.
*
* @psalm-pure
* @psalm-suppress ImpureStaticProperty
*/
final public static function get() : Calculator
{
if (self::$instance === null) {
/** @psalm-suppress ImpureMethodCall */
self::$instance = self::detect();
}
return self::$instance;
}
/**
* Returns the fastest available Calculator implementation.
*
* @codeCoverageIgnore
*/
private static function detect() : Calculator
{
if (\extension_loaded('gmp')) {
return new Calculator\GmpCalculator();
}
if (\extension_loaded('bcmath')) {
return new Calculator\BcMathCalculator();
}
return new Calculator\NativeCalculator();
}
/**
* Extracts the sign & digits of the operands.
*
* @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
*/
final protected function init(string $a, string $b) : array
{
return [
$aNeg = ($a[0] === '-'),
$bNeg = ($b[0] === '-'),
$aNeg ? \substr($a, 1) : $a,
$bNeg ? \substr($b, 1) : $b,
];
}
/**
* Returns the absolute value of a number.
*/
final public function abs(string $n) : string
{
return ($n[0] === '-') ? \substr($n, 1) : $n;
}
/**
* Negates a number.
*/
final public function neg(string $n) : string
{
if ($n === '0') {
return '0';
}
if ($n[0] === '-') {
return \substr($n, 1);
}
return '-' . $n;
}
/**
* Compares two numbers.
*
* @return int [-1, 0, 1] If the first number is less than, equal to, or greater than the second number.
*/
final public function cmp(string $a, string $b) : int
{
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
if ($aNeg && ! $bNeg) {
return -1;
}
if ($bNeg && ! $aNeg) {
return 1;
}
$aLen = \strlen($aDig);
$bLen = \strlen($bDig);
if ($aLen < $bLen) {
$result = -1;
} elseif ($aLen > $bLen) {
$result = 1;
} else {
$result = $aDig <=> $bDig;
}
return $aNeg ? -$result : $result;
}
/**
* Adds two numbers.
*/
abstract public function add(string $a, string $b) : string;
/**
* Subtracts two numbers.
*/
abstract public function sub(string $a, string $b) : string;
/**
* Multiplies two numbers.
*/
abstract public function mul(string $a, string $b) : string;
/**
* Returns the quotient of the division of two numbers.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
*
* @return string The quotient.
*/
abstract public function divQ(string $a, string $b) : string;
/**
* Returns the remainder of the division of two numbers.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
*
* @return string The remainder.
*/
abstract public function divR(string $a, string $b) : string;
/**
* Returns the quotient and remainder of the division of two numbers.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
*
* @return array{string, string} An array containing the quotient and remainder.
*/
abstract public function divQR(string $a, string $b) : array;
/**
* Exponentiates a number.
*
* @param string $a The base number.
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER.
*
* @return string The power.
*/
abstract public function pow(string $a, int $e) : string;
/**
* @param string $b The modulus; must not be zero.
*/
public function mod(string $a, string $b) : string
{
return $this->divR($this->add($this->divR($a, $b), $b), $b);
}
/**
* Returns the modular multiplicative inverse of $x modulo $m.
*
* If $x has no multiplicative inverse mod m, this method must return null.
*
* This method can be overridden by the concrete implementation if the underlying library has built-in support.
*
* @param string $m The modulus; must not be negative or zero.
*/
public function modInverse(string $x, string $m) : ?string
{
if ($m === '1') {
return '0';
}
$modVal = $x;
if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) {
$modVal = $this->mod($x, $m);
}
[$g, $x] = $this->gcdExtended($modVal, $m);
if ($g !== '1') {
return null;
}
return $this->mod($this->add($this->mod($x, $m), $m), $m);
}
/**
* Raises a number into power with modulo.
*
* @param string $base The base number; must be positive or zero.
* @param string $exp The exponent; must be positive or zero.
* @param string $mod The modulus; must be strictly positive.
*/
abstract public function modPow(string $base, string $exp, string $mod) : string;
/**
* Returns the greatest common divisor of the two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for GCD calculations.
*
* @return string The GCD, always positive, or zero if both arguments are zero.
*/
public function gcd(string $a, string $b) : string
{
if ($a === '0') {
return $this->abs($b);
}
if ($b === '0') {
return $this->abs($a);
}
return $this->gcd($b, $this->divR($a, $b));
}
/**
* @return array{string, string, string} GCD, X, Y
*/
private function gcdExtended(string $a, string $b) : array
{
if ($a === '0') {
return [$b, '0', '1'];
}
[$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a);
$x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1));
$y = $x1;
return [$gcd, $x, $y];
}
/**
* Returns the square root of the given number, rounded down.
*
* The result is the largest x such that x² ≤ n.
* The input MUST NOT be negative.
*/
abstract public function sqrt(string $n) : string;
/**
* Converts a number from an arbitrary base.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for base conversion.
*
* @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base.
* @param int $base The base of the number, validated from 2 to 36.
*
* @return string The converted number, following the Calculator conventions.
*/
public function fromBase(string $number, int $base) : string
{
return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base);
}
/**
* Converts a number to an arbitrary base.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for base conversion.
*
* @param string $number The number to convert, following the Calculator conventions.
* @param int $base The base to convert to, validated from 2 to 36.
*
* @return string The converted number, lowercase.
*/
public function toBase(string $number, int $base) : string
{
$negative = ($number[0] === '-');
if ($negative) {
$number = \substr($number, 1);
}
$number = $this->toArbitraryBase($number, self::ALPHABET, $base);
if ($negative) {
return '-' . $number;
}
return $number;
}
/**
* Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10.
*
* @param string $number The number to convert, validated as a non-empty string,
* containing only chars in the given alphabet/base.
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
* @param int $base The base of the number, validated from 2 to alphabet length.
*
* @return string The number in base 10, following the Calculator conventions.
*/
final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string
{
// remove leading "zeros"
$number = \ltrim($number, $alphabet[0]);
if ($number === '') {
return '0';
}
// optimize for "one"
if ($number === $alphabet[1]) {
return '1';
}
$result = '0';
$power = '1';
$base = (string) $base;
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
$index = \strpos($alphabet, $number[$i]);
if ($index !== 0) {
$result = $this->add($result, ($index === 1)
? $power
: $this->mul($power, (string) $index)
);
}
if ($i !== 0) {
$power = $this->mul($power, $base);
}
}
return $result;
}
/**
* Converts a non-negative number to an arbitrary base using a custom alphabet.
*
* @param string $number The number to convert, positive or zero, following the Calculator conventions.
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
* @param int $base The base to convert to, validated from 2 to alphabet length.
*
* @return string The converted number in the given alphabet.
*/
final public function toArbitraryBase(string $number, string $alphabet, int $base) : string
{
if ($number === '0') {
return $alphabet[0];
}
$base = (string) $base;
$result = '';
while ($number !== '0') {
[$number, $remainder] = $this->divQR($number, $base);
$remainder = (int) $remainder;
$result .= $alphabet[$remainder];
}
return \strrev($result);
}
/**
* Performs a rounded division.
*
* Rounding is performed when the remainder of the division is not zero.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
* @param int $roundingMode The rounding mode.
*
* @throws \InvalidArgumentException If the rounding mode is invalid.
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
*
* @psalm-suppress ImpureFunctionCall
*/
final public function divRound(string $a, string $b, int $roundingMode) : string
{
[$quotient, $remainder] = $this->divQR($a, $b);
$hasDiscardedFraction = ($remainder !== '0');
$isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-');
$discardedFractionSign = function() use ($remainder, $b) : int {
$r = $this->abs($this->mul($remainder, '2'));
$b = $this->abs($b);
return $this->cmp($r, $b);
};
$increment = false;
switch ($roundingMode) {
case RoundingMode::UNNECESSARY:
if ($hasDiscardedFraction) {
throw RoundingNecessaryException::roundingNecessary();
}
break;
case RoundingMode::UP:
$increment = $hasDiscardedFraction;
break;
case RoundingMode::DOWN:
break;
case RoundingMode::CEILING:
$increment = $hasDiscardedFraction && $isPositiveOrZero;
break;
case RoundingMode::FLOOR:
$increment = $hasDiscardedFraction && ! $isPositiveOrZero;
break;
case RoundingMode::HALF_UP:
$increment = $discardedFractionSign() >= 0;
break;
case RoundingMode::HALF_DOWN:
$increment = $discardedFractionSign() > 0;
break;
case RoundingMode::HALF_CEILING:
$increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0;
break;
case RoundingMode::HALF_FLOOR:
$increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
break;
case RoundingMode::HALF_EVEN:
$lastDigit = (int) $quotient[-1];
$lastDigitIsEven = ($lastDigit % 2 === 0);
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
break;
default:
throw new \InvalidArgumentException('Invalid rounding mode.');
}
if ($increment) {
return $this->add($quotient, $isPositiveOrZero ? '1' : '-1');
}
return $quotient;
}
/**
* Calculates bitwise AND of two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*/
public function and(string $a, string $b) : string
{
return $this->bitwise('and', $a, $b);
}
/**
* Calculates bitwise OR of two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*/
public function or(string $a, string $b) : string
{
return $this->bitwise('or', $a, $b);
}
/**
* Calculates bitwise XOR of two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*/
public function xor(string $a, string $b) : string
{
return $this->bitwise('xor', $a, $b);
}
/**
* Performs a bitwise operation on a decimal number.
*
* @param 'and'|'or'|'xor' $operator The operator to use.
* @param string $a The left operand.
* @param string $b The right operand.
*/
private function bitwise(string $operator, string $a, string $b) : string
{
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
$aBin = $this->toBinary($aDig);
$bBin = $this->toBinary($bDig);
$aLen = \strlen($aBin);
$bLen = \strlen($bBin);
if ($aLen > $bLen) {
$bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin;
} elseif ($bLen > $aLen) {
$aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin;
}
if ($aNeg) {
$aBin = $this->twosComplement($aBin);
}
if ($bNeg) {
$bBin = $this->twosComplement($bBin);
}
switch ($operator) {
case 'and':
$value = $aBin & $bBin;
$negative = ($aNeg and $bNeg);
break;
case 'or':
$value = $aBin | $bBin;
$negative = ($aNeg or $bNeg);
break;
case 'xor':
$value = $aBin ^ $bBin;
$negative = ($aNeg xor $bNeg);
break;
// @codeCoverageIgnoreStart
default:
throw new \InvalidArgumentException('Invalid bitwise operator.');
// @codeCoverageIgnoreEnd
}
if ($negative) {
$value = $this->twosComplement($value);
}
$result = $this->toDecimal($value);
return $negative ? $this->neg($result) : $result;
}
/**
* @param string $number A positive, binary number.
*/
private function twosComplement(string $number) : string
{
$xor = \str_repeat("\xff", \strlen($number));
$number ^= $xor;
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
$byte = \ord($number[$i]);
if (++$byte !== 256) {
$number[$i] = \chr($byte);
break;
}
$number[$i] = "\x00";
if ($i === 0) {
$number = "\x01" . $number;
}
}
return $number;
}
/**
* Converts a decimal number to a binary string.
*
* @param string $number The number to convert, positive or zero, only digits.
*/
private function toBinary(string $number) : string
{
$result = '';
while ($number !== '0') {
[$number, $remainder] = $this->divQR($number, '256');
$result .= \chr((int) $remainder);
}
return \strrev($result);
}
/**
* Returns the positive decimal representation of a binary number.
*
* @param string $bytes The bytes representing the number.
*/
private function toDecimal(string $bytes) : string
{
$result = '0';
$power = '1';
for ($i = \strlen($bytes) - 1; $i >= 0; $i--) {
$index = \ord($bytes[$i]);
if ($index !== 0) {
$result = $this->add($result, ($index === 1)
? $power
: $this->mul($power, (string) $index)
);
}
if ($i !== 0) {
$power = $this->mul($power, '256');
}
}
return $result;
}
}

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Internal\Calculator;
use Brick\Math\Internal\Calculator;
/**
* Calculator implementation built around the bcmath library.
*
* @internal
*
* @psalm-immutable
*/
class BcMathCalculator extends Calculator
{
public function add(string $a, string $b) : string
{
return \bcadd($a, $b, 0);
}
public function sub(string $a, string $b) : string
{
return \bcsub($a, $b, 0);
}
public function mul(string $a, string $b) : string
{
return \bcmul($a, $b, 0);
}
public function divQ(string $a, string $b) : string
{
return \bcdiv($a, $b, 0);
}
/**
* @psalm-suppress InvalidNullableReturnType
* @psalm-suppress NullableReturnStatement
*/
public function divR(string $a, string $b) : string
{
return \bcmod($a, $b, 0);
}
public function divQR(string $a, string $b) : array
{
$q = \bcdiv($a, $b, 0);
$r = \bcmod($a, $b, 0);
assert($r !== null);
return [$q, $r];
}
public function pow(string $a, int $e) : string
{
return \bcpow($a, (string) $e, 0);
}
public function modPow(string $base, string $exp, string $mod) : string
{
return \bcpowmod($base, $exp, $mod, 0);
}
/**
* @psalm-suppress InvalidNullableReturnType
* @psalm-suppress NullableReturnStatement
*/
public function sqrt(string $n) : string
{
return \bcsqrt($n, 0);
}
}

View File

@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Internal\Calculator;
use Brick\Math\Internal\Calculator;
/**
* Calculator implementation built around the GMP library.
*
* @internal
*
* @psalm-immutable
*/
class GmpCalculator extends Calculator
{
public function add(string $a, string $b) : string
{
return \gmp_strval(\gmp_add($a, $b));
}
public function sub(string $a, string $b) : string
{
return \gmp_strval(\gmp_sub($a, $b));
}
public function mul(string $a, string $b) : string
{
return \gmp_strval(\gmp_mul($a, $b));
}
public function divQ(string $a, string $b) : string
{
return \gmp_strval(\gmp_div_q($a, $b));
}
public function divR(string $a, string $b) : string
{
return \gmp_strval(\gmp_div_r($a, $b));
}
public function divQR(string $a, string $b) : array
{
[$q, $r] = \gmp_div_qr($a, $b);
return [
\gmp_strval($q),
\gmp_strval($r)
];
}
public function pow(string $a, int $e) : string
{
return \gmp_strval(\gmp_pow($a, $e));
}
public function modInverse(string $x, string $m) : ?string
{
$result = \gmp_invert($x, $m);
if ($result === false) {
return null;
}
return \gmp_strval($result);
}
public function modPow(string $base, string $exp, string $mod) : string
{
return \gmp_strval(\gmp_powm($base, $exp, $mod));
}
public function gcd(string $a, string $b) : string
{
return \gmp_strval(\gmp_gcd($a, $b));
}
public function fromBase(string $number, int $base) : string
{
return \gmp_strval(\gmp_init($number, $base));
}
public function toBase(string $number, int $base) : string
{
return \gmp_strval($number, $base);
}
public function and(string $a, string $b) : string
{
return \gmp_strval(\gmp_and($a, $b));
}
public function or(string $a, string $b) : string
{
return \gmp_strval(\gmp_or($a, $b));
}
public function xor(string $a, string $b) : string
{
return \gmp_strval(\gmp_xor($a, $b));
}
public function sqrt(string $n) : string
{
return \gmp_strval(\gmp_sqrt($n));
}
}

View File

@@ -0,0 +1,581 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Internal\Calculator;
use Brick\Math\Internal\Calculator;
/**
* Calculator implementation using only native PHP code.
*
* @internal
*
* @psalm-immutable
*/
class NativeCalculator extends Calculator
{
/**
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
* For multiplication, this represents the max sum of the lengths of both operands.
*
* In addition, it is assumed that an extra digit can hold a carry (1) without overflowing.
* Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
* 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
*/
private int $maxDigits;
/**
* @codeCoverageIgnore
*/
public function __construct()
{
switch (PHP_INT_SIZE) {
case 4:
$this->maxDigits = 9;
break;
case 8:
$this->maxDigits = 18;
break;
default:
throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.');
}
}
public function add(string $a, string $b) : string
{
/**
* @psalm-var numeric-string $a
* @psalm-var numeric-string $b
*/
$result = $a + $b;
if (is_int($result)) {
return (string) $result;
}
if ($a === '0') {
return $b;
}
if ($b === '0') {
return $a;
}
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
$result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig);
if ($aNeg) {
$result = $this->neg($result);
}
return $result;
}
public function sub(string $a, string $b) : string
{
return $this->add($a, $this->neg($b));
}
public function mul(string $a, string $b) : string
{
/**
* @psalm-var numeric-string $a
* @psalm-var numeric-string $b
*/
$result = $a * $b;
if (is_int($result)) {
return (string) $result;
}
if ($a === '0' || $b === '0') {
return '0';
}
if ($a === '1') {
return $b;
}
if ($b === '1') {
return $a;
}
if ($a === '-1') {
return $this->neg($b);
}
if ($b === '-1') {
return $this->neg($a);
}
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
$result = $this->doMul($aDig, $bDig);
if ($aNeg !== $bNeg) {
$result = $this->neg($result);
}
return $result;
}
public function divQ(string $a, string $b) : string
{
return $this->divQR($a, $b)[0];
}
public function divR(string $a, string $b): string
{
return $this->divQR($a, $b)[1];
}
public function divQR(string $a, string $b) : array
{
if ($a === '0') {
return ['0', '0'];
}
if ($a === $b) {
return ['1', '0'];
}
if ($b === '1') {
return [$a, '0'];
}
if ($b === '-1') {
return [$this->neg($a), '0'];
}
/** @psalm-var numeric-string $a */
$na = $a * 1; // cast to number
if (is_int($na)) {
/** @psalm-var numeric-string $b */
$nb = $b * 1;
if (is_int($nb)) {
// the only division that may overflow is PHP_INT_MIN / -1,
// which cannot happen here as we've already handled a divisor of -1 above.
$r = $na % $nb;
$q = ($na - $r) / $nb;
assert(is_int($q));
return [
(string) $q,
(string) $r
];
}
}
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
[$q, $r] = $this->doDiv($aDig, $bDig);
if ($aNeg !== $bNeg) {
$q = $this->neg($q);
}
if ($aNeg) {
$r = $this->neg($r);
}
return [$q, $r];
}
public function pow(string $a, int $e) : string
{
if ($e === 0) {
return '1';
}
if ($e === 1) {
return $a;
}
$odd = $e % 2;
$e -= $odd;
$aa = $this->mul($a, $a);
/** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */
$result = $this->pow($aa, $e / 2);
if ($odd === 1) {
$result = $this->mul($result, $a);
}
return $result;
}
/**
* Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/
*/
public function modPow(string $base, string $exp, string $mod) : string
{
// special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0)
if ($base === '0' && $exp === '0' && $mod === '1') {
return '0';
}
// special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0)
if ($exp === '0' && $mod === '1') {
return '0';
}
$x = $base;
$res = '1';
// numbers are positive, so we can use remainder instead of modulo
$x = $this->divR($x, $mod);
while ($exp !== '0') {
if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd
$res = $this->divR($this->mul($res, $x), $mod);
}
$exp = $this->divQ($exp, '2');
$x = $this->divR($this->mul($x, $x), $mod);
}
return $res;
}
/**
* Adapted from https://cp-algorithms.com/num_methods/roots_newton.html
*/
public function sqrt(string $n) : string
{
if ($n === '0') {
return '0';
}
// initial approximation
$x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1);
$decreased = false;
for (;;) {
$nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2');
if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) {
break;
}
$decreased = $this->cmp($nx, $x) < 0;
$x = $nx;
}
return $x;
}
/**
* Performs the addition of two non-signed large integers.
*/
private function doAdd(string $a, string $b) : string
{
[$a, $b, $length] = $this->pad($a, $b);
$carry = 0;
$result = '';
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
$blockLength = $this->maxDigits;
if ($i < 0) {
$blockLength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
/** @psalm-var numeric-string $blockA */
$blockA = \substr($a, $i, $blockLength);
/** @psalm-var numeric-string $blockB */
$blockB = \substr($b, $i, $blockLength);
$sum = (string) ($blockA + $blockB + $carry);
$sumLength = \strlen($sum);
if ($sumLength > $blockLength) {
$sum = \substr($sum, 1);
$carry = 1;
} else {
if ($sumLength < $blockLength) {
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
}
$carry = 0;
}
$result = $sum . $result;
if ($i === 0) {
break;
}
}
if ($carry === 1) {
$result = '1' . $result;
}
return $result;
}
/**
* Performs the subtraction of two non-signed large integers.
*/
private function doSub(string $a, string $b) : string
{
if ($a === $b) {
return '0';
}
// Ensure that we always subtract to a positive result: biggest minus smallest.
$cmp = $this->doCmp($a, $b);
$invert = ($cmp === -1);
if ($invert) {
$c = $a;
$a = $b;
$b = $c;
}
[$a, $b, $length] = $this->pad($a, $b);
$carry = 0;
$result = '';
$complement = 10 ** $this->maxDigits;
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
$blockLength = $this->maxDigits;
if ($i < 0) {
$blockLength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
/** @psalm-var numeric-string $blockA */
$blockA = \substr($a, $i, $blockLength);
/** @psalm-var numeric-string $blockB */
$blockB = \substr($b, $i, $blockLength);
$sum = $blockA - $blockB - $carry;
if ($sum < 0) {
$sum += $complement;
$carry = 1;
} else {
$carry = 0;
}
$sum = (string) $sum;
$sumLength = \strlen($sum);
if ($sumLength < $blockLength) {
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
}
$result = $sum . $result;
if ($i === 0) {
break;
}
}
// Carry cannot be 1 when the loop ends, as a > b
assert($carry === 0);
$result = \ltrim($result, '0');
if ($invert) {
$result = $this->neg($result);
}
return $result;
}
/**
* Performs the multiplication of two non-signed large integers.
*/
private function doMul(string $a, string $b) : string
{
$x = \strlen($a);
$y = \strlen($b);
$maxDigits = \intdiv($this->maxDigits, 2);
$complement = 10 ** $maxDigits;
$result = '0';
for ($i = $x - $maxDigits;; $i -= $maxDigits) {
$blockALength = $maxDigits;
if ($i < 0) {
$blockALength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
$blockA = (int) \substr($a, $i, $blockALength);
$line = '';
$carry = 0;
for ($j = $y - $maxDigits;; $j -= $maxDigits) {
$blockBLength = $maxDigits;
if ($j < 0) {
$blockBLength += $j;
/** @psalm-suppress LoopInvalidation */
$j = 0;
}
$blockB = (int) \substr($b, $j, $blockBLength);
$mul = $blockA * $blockB + $carry;
$value = $mul % $complement;
$carry = ($mul - $value) / $complement;
$value = (string) $value;
$value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT);
$line = $value . $line;
if ($j === 0) {
break;
}
}
if ($carry !== 0) {
$line = $carry . $line;
}
$line = \ltrim($line, '0');
if ($line !== '') {
$line .= \str_repeat('0', $x - $blockALength - $i);
$result = $this->add($result, $line);
}
if ($i === 0) {
break;
}
}
return $result;
}
/**
* Performs the division of two non-signed large integers.
*
* @return string[] The quotient and remainder.
*/
private function doDiv(string $a, string $b) : array
{
$cmp = $this->doCmp($a, $b);
if ($cmp === -1) {
return ['0', $a];
}
$x = \strlen($a);
$y = \strlen($b);
// we now know that a >= b && x >= y
$q = '0'; // quotient
$r = $a; // remainder
$z = $y; // focus length, always $y or $y+1
for (;;) {
$focus = \substr($a, 0, $z);
$cmp = $this->doCmp($focus, $b);
if ($cmp === -1) {
if ($z === $x) { // remainder < dividend
break;
}
$z++;
}
$zeros = \str_repeat('0', $x - $z);
$q = $this->add($q, '1' . $zeros);
$a = $this->sub($a, $b . $zeros);
$r = $a;
if ($r === '0') { // remainder == 0
break;
}
$x = \strlen($a);
if ($x < $y) { // remainder < dividend
break;
}
$z = $y;
}
return [$q, $r];
}
/**
* Compares two non-signed large numbers.
*
* @return int [-1, 0, 1]
*/
private function doCmp(string $a, string $b) : int
{
$x = \strlen($a);
$y = \strlen($b);
$cmp = $x <=> $y;
if ($cmp !== 0) {
return $cmp;
}
return \strcmp($a, $b) <=> 0; // enforce [-1, 0, 1]
}
/**
* Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length.
*
* The numbers must only consist of digits, without leading minus sign.
*
* @return array{string, string, int}
*/
private function pad(string $a, string $b) : array
{
$x = \strlen($a);
$y = \strlen($b);
if ($x > $y) {
$b = \str_repeat('0', $x - $y) . $b;
return [$a, $b, $x];
}
if ($x < $y) {
$a = \str_repeat('0', $y - $x) . $a;
return [$a, $b, $y];
}
return [$a, $b, $x];
}
}

107
vendor/brick/math/src/RoundingMode.php vendored Executable file
View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace Brick\Math;
/**
* Specifies a rounding behavior for numerical operations capable of discarding precision.
*
* Each rounding mode indicates how the least significant returned digit of a rounded result
* is to be calculated. If fewer digits are returned than the digits needed to represent the
* exact numerical result, the discarded digits will be referred to as the discarded fraction
* regardless the digits' contribution to the value of the number. In other words, considered
* as a numerical value, the discarded fraction could have an absolute value greater than one.
*/
final class RoundingMode
{
/**
* Private constructor. This class is not instantiable.
*
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* Asserts that the requested operation has an exact result, hence no rounding is necessary.
*
* If this rounding mode is specified on an operation that yields a result that
* cannot be represented at the requested scale, a RoundingNecessaryException is thrown.
*/
public const UNNECESSARY = 0;
/**
* Rounds away from zero.
*
* Always increments the digit prior to a nonzero discarded fraction.
* Note that this rounding mode never decreases the magnitude of the calculated value.
*/
public const UP = 1;
/**
* Rounds towards zero.
*
* Never increments the digit prior to a discarded fraction (i.e., truncates).
* Note that this rounding mode never increases the magnitude of the calculated value.
*/
public const DOWN = 2;
/**
* Rounds towards positive infinity.
*
* If the result is positive, behaves as for UP; if negative, behaves as for DOWN.
* Note that this rounding mode never decreases the calculated value.
*/
public const CEILING = 3;
/**
* Rounds towards negative infinity.
*
* If the result is positive, behave as for DOWN; if negative, behave as for UP.
* Note that this rounding mode never increases the calculated value.
*/
public const FLOOR = 4;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
*
* Behaves as for UP if the discarded fraction is >= 0.5; otherwise, behaves as for DOWN.
* Note that this is the rounding mode commonly taught at school.
*/
public const HALF_UP = 5;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
*
* Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN.
*/
public const HALF_DOWN = 6;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity.
*
* If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN.
*/
public const HALF_CEILING = 7;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity.
*
* If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP.
*/
public const HALF_FLOOR = 8;
/**
* Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor.
*
* Behaves as for HALF_UP if the digit to the left of the discarded fraction is odd;
* behaves as for HALF_DOWN if it's even.
*
* Note that this is the rounding mode that statistically minimizes
* cumulative error when applied repeatedly over a sequence of calculations.
* It is sometimes known as "Banker's rounding", and is chiefly used in the USA.
*/
public const HALF_EVEN = 9;
}

14
vendor/claudecio/axiumphp/.env.example vendored Executable file
View File

@@ -0,0 +1,14 @@
SYSTEM_VERSION = 1.0
SYSTEM_TIMEZONE = 'America/Fortaleza'
SYSTEM_URL = 'https://system.localhost'
SYSTEM_FRONTEND_URL = 'https://app.system.localhost'
# 1 = Production, 2 = Development
SYSTEM_ENVIRONMENT_ID = 2
DEFAULT_DATABASE_HOST = 127.0.0.1
DEFAULT_DATABASE_PORT = 3306
DEFAULT_DATABASE_DRIVER = mysql
DEFAULT_DATABASE_SCHEMA = schema
DEFAULT_DATABASE_CHARSET = utf8mb4
DEFAULT_DATABASE_USERNAME = username
DEFAULT_DATABASE_PASSWORD = password

15
vendor/claudecio/axiumphp/composer.json vendored Executable file
View File

@@ -0,0 +1,15 @@
{
"name": "claudecio/axiumphp",
"description": "Meu framework PHP para sistemas MVC modulares.",
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"AxiumPHP\\": "src/"
}
},
"require": {
"php": ">=8.1",
"vlucas/phpdotenv": "v5.6.2"
}
}

482
vendor/claudecio/axiumphp/composer.lock generated vendored Executable file
View File

@@ -0,0 +1,482 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6d450325e9b77ca38da0bcec5503c990",
"packages": [
{
"name": "graham-campbell/result-type",
"version": "v1.1.3",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Result-Type.git",
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"phpoption/phpoption": "^1.9.3"
},
"require-dev": {
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
},
"type": "library",
"autoload": {
"psr-4": {
"GrahamCampbell\\ResultType\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "An Implementation Of The Result Type",
"keywords": [
"Graham Campbell",
"GrahamCampbell",
"Result Type",
"Result-Type",
"result"
],
"support": {
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
"type": "tidelift"
}
],
"time": "2024-07-20T21:45:45+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.3",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"psr-4": {
"PhpOption\\": "src/PhpOption/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Johannes M. Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh"
},
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "Option Type for PHP",
"keywords": [
"language",
"option",
"php",
"type"
],
"support": {
"issues": "https://github.com/schmittjoh/php-option/issues",
"source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
"type": "tidelift"
}
],
"time": "2024-07-20T21:41:07+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-12-23T08:48:59+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-01-02T08:10:11+00:00"
},
{
"name": "vlucas/phpdotenv",
"version": "v5.6.2",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
"shasum": ""
},
"require": {
"ext-pcre": "*",
"graham-campbell/result-type": "^1.1.3",
"php": "^7.2.5 || ^8.0",
"phpoption/phpoption": "^1.9.3",
"symfony/polyfill-ctype": "^1.24",
"symfony/polyfill-mbstring": "^1.24",
"symfony/polyfill-php80": "^1.24"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"ext-filter": "*",
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
},
"suggest": {
"ext-filter": "Required to use the boolean validator."
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "5.6-dev"
}
},
"autoload": {
"psr-4": {
"Dotenv\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Vance Lucas",
"email": "vance@vancelucas.com",
"homepage": "https://github.com/vlucas"
}
],
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
"keywords": [
"dotenv",
"env",
"environment"
],
"support": {
"issues": "https://github.com/vlucas/phpdotenv/issues",
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
"type": "tidelift"
}
],
"time": "2025-04-30T23:37:27+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=8.1"
},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

152
vendor/claudecio/axiumphp/guide.md vendored Executable file
View File

@@ -0,0 +1,152 @@
# AxiumPHP - Guia de Primeiro Uso
O **AxiumPHP** é um micro-framework PHP modular no padrão MVC, ideal para criar sistemas organizados e escaláveis sem depender de frameworks pesados.
Ele já vem pronto para trabalhar com módulos independentes, carregamento automático de rotas, tratamento global de erros e suporte a APIs (JSON) ou páginas HTML.
---
## 🚀 Instalação e Configuração
### 1. Clonar o repositório
```bash
git clone https://github.com/claudecio/AxiumPHP.git
```
### 2. Instalar as dependências via Composer
```bash
composer install
```
### 3. Configurar o ambiente
Copie o arquivo `.env.example` para `.env`:
```bash
cp .env.example .env
```
Depois, ajuste as variáveis conforme seu ambiente (conexão com banco, fuso horário, URL do frontend, etc.).
### 4. Configurar o servidor
**Usando PHP embutido:**
```bash
php -S localhost:8000 -t public
```
**Usando Apache:**
Aponte o `DocumentRoot` para a pasta `public/`.
---
## 📂 Estrutura do Projeto
```
app/
├── Common/ # Serviços e controladores compartilhados
├── Module/ # Módulos independentes (Controllers, Models, Views, Routes, bootstrap.php)
└── Core/ # Núcleo do framework
public/ # Arquivos públicos (index.php, assets)
vendor/ # Dependências do Composer
.env # Configurações do ambiente
```
---
## 📌 Entendendo o index.php
O arquivo `public/index.php` é o ponto de entrada da aplicação.
Nele, além do autoload do Composer, algumas constantes precisam ser definidas:
- **ROUTER_MODE** → define o modo de resposta do roteador (`JSON` para APIs, `VIEW` para views).
- **APP_SYS_MODE** → define o modo de sistema, PROD para produção ou DEV para desenvolvimento.
- **ROUTER_ALLOWED_ORIGINS** → array onde é definido os dominios permitidos para usar o CORS.
- **INI_SYSTEM_PATH** → caminho absoluto para a pasta `app` do projeto.
- **MODULE_PATH** → caminho absoluto para a pasta `Module`, onde ficam os módulos do sistema.
- **STORAGE_FOLDER_PATH** → caminho absoluto para a pasta `Storage`, utilizada para arquivos e logs.
**Exemplo:**
```php
const ROUTER_MODE = 'JSON';
const APP_SYS_MODE = 'DEV' # PROD = Production // DEV = Development
// Case Router Mode in JSON
const ROUTER_ALLOWED_ORIGINS = [
'*',
'http://app.localhost:8000',
];
define('INI_SYSTEM_PATH', realpath(__DIR__ . "/../app"));
define('MODULE_PATH', realpath(__DIR__ . "/../app/Module"));
define('STORAGE_FOLDER_PATH', realpath(__DIR__ . "/../app/Storage"));
```
Além disso, o `index.php`:
- Carrega configurações globais (`Config`);
- Inicializa o `LoggerService`;
- Ativa o `ErrorHandler`;
- Define o fuso horário (`SYSTEM_TIMEZONE` do `.env`);
- Inicia sessão, se necessário;
- Configura CORS;
- Carrega os módulos iniciais.
---
## 🛠 Criando e Registrando um Módulo
### 1. Criar a pasta do módulo
```bash
mkdir -p app/Module/Hello
```
### 2. Criar o Controller
Arquivo: `app/Module/Hello/Controllers/HelloController.php`
```php
<?php
namespace App\Module\Hello\Controllers;
class HelloController {
public function index() {
echo "Hello, AxiumPHP!";
}
}
```
### 3. Criar a rota
Arquivo: `app/Module/Hello/Routes/web.php`
```php
<?php
use AxiumPHP\Core\Router;
use App\Module\Hello\Controllers\HelloController;
Router::GET('/hello', [HelloController::class, 'index']);
Router::PUT('/hello/{id}', [HelloController::class, 'update']);
Router::POST('/createHello', [HelloController::class, 'create']);
Router::DELETE('/hello/{id}', [HelloController::class, 'delete']);
```
### 4. Criar o bootstrap do módulo
Arquivo: `app/Module/Hello/bootstrap.php`
```php
<?php
require_once __DIR__ . '/Routes/web.php';
```
### 5. Registrar o módulo no index.php
```php
<?php
require_once realpath(__DIR__ . "/../app/Module/Hello/bootstrap.php");
```
---
## 💡 Dicas Importantes
**LoggerService** → configure antes do `ErrorHandler`:
```php
LoggerService::init(
driver: LoggerService::DRIVER_FILE,
logDir: 'logs'
);
```
**CORS** → já configurado no `index.php` usando `SYSTEM_FRONTEND_URL` do `.env`.
**ROUTER_MODE** → altere para `VIEW` se quiser trabalhar com renderização de views.
**ErrorHandler** → use o `SYSTEM_ENVIRONMENT_ID` do `.env` para decidir se exibe ou oculta erros em produção.
---
## 📜 Licença
Este projeto está licenciado sob a **MIT License**.

10
vendor/claudecio/axiumphp/readme.md vendored Executable file
View File

@@ -0,0 +1,10 @@
# AxiumPHP
AxiumPHP é um framework simples e modular para aplicações PHP.
## Instalação
Com o Composer:
```bash
composer require claudecio/axiumphp

35
vendor/claudecio/axiumphp/src/AxiumPHP.php vendored Executable file
View 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.");
}
}
}
}

View 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()");
}
}

View 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()}");
}
}
}

View 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;
}
}

View 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");
}
}

View 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
);
}
}

View 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
View 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
View 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;
}
}
}

View 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&param2=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;
}
}

View 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;
}
}

22
vendor/claudecio/axiumphp/vendor/autoload.php vendored Executable file
View File

@@ -0,0 +1,22 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit6c176ae0681796ba87da0d8bfe73b645::getLoader();

View File

@@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

View File

@@ -0,0 +1,396 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
* @internal
*/
private static $selfDir = null;
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool
*/
private static $installedIsLocalDir;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
// so we have to assume it does not, and that may result in duplicate data being returned when listing
// all installed packages for example
self::$installedIsLocalDir = false;
}
/**
* @return string
*/
private static function getSelfDir()
{
if (self::$selfDir === null) {
self::$selfDir = strtr(__DIR__, '\\', '/');
}
return self::$selfDir;
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
$copiedLocalDir = false;
if (self::$canGetVendors) {
$selfDir = self::getSelfDir();
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
$vendorDir = strtr($vendorDir, '\\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
self::$installedByVendor[$vendorDir] = $required;
$installed[] = $required;
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
self::$installed = $required;
self::$installedIsLocalDir = true;
}
}
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
$copiedLocalDir = true;
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}
return $installed;
}
}

View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);

View File

@@ -0,0 +1,12 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,16 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
'GrahamCampbell\\ResultType\\' => array($vendorDir . '/graham-campbell/result-type/src'),
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
'AxiumPHP\\' => array($baseDir . '/src'),
);

View File

@@ -0,0 +1,50 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit6c176ae0681796ba87da0d8bfe73b645
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit6c176ae0681796ba87da0d8bfe73b645', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit6c176ae0681796ba87da0d8bfe73b645', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit6c176ae0681796ba87da0d8bfe73b645::getInitializer($loader));
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit6c176ae0681796ba87da0d8bfe73b645::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
}
return $loader;
}
}

View File

@@ -0,0 +1,89 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit6c176ae0681796ba87da0d8bfe73b645
{
public static $files = array (
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
);
public static $prefixLengthsPsr4 = array (
'S' =>
array (
'Symfony\\Polyfill\\Php80\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Ctype\\' => 23,
),
'P' =>
array (
'PhpOption\\' => 10,
),
'G' =>
array (
'GrahamCampbell\\ResultType\\' => 26,
),
'D' =>
array (
'Dotenv\\' => 7,
),
'A' =>
array (
'AxiumPHP\\' => 9,
),
);
public static $prefixDirsPsr4 = array (
'Symfony\\Polyfill\\Php80\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'PhpOption\\' =>
array (
0 => __DIR__ . '/..' . '/phpoption/phpoption/src/PhpOption',
),
'GrahamCampbell\\ResultType\\' =>
array (
0 => __DIR__ . '/..' . '/graham-campbell/result-type/src',
),
'Dotenv\\' =>
array (
0 => __DIR__ . '/..' . '/vlucas/phpdotenv/src',
),
'AxiumPHP\\' =>
array (
0 => __DIR__ . '/../..' . '/src',
),
);
public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit6c176ae0681796ba87da0d8bfe73b645::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit6c176ae0681796ba87da0d8bfe73b645::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit6c176ae0681796ba87da0d8bfe73b645::$classMap;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1,485 @@
{
"packages": [
{
"name": "graham-campbell/result-type",
"version": "v1.1.3",
"version_normalized": "1.1.3.0",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Result-Type.git",
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"phpoption/phpoption": "^1.9.3"
},
"require-dev": {
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
},
"time": "2024-07-20T21:45:45+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"GrahamCampbell\\ResultType\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "An Implementation Of The Result Type",
"keywords": [
"Graham Campbell",
"GrahamCampbell",
"Result Type",
"Result-Type",
"result"
],
"support": {
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
"type": "tidelift"
}
],
"install-path": "../graham-campbell/result-type"
},
{
"name": "phpoption/phpoption",
"version": "1.9.3",
"version_normalized": "1.9.3.0",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
},
"time": "2024-07-20T21:41:07+00:00",
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"PhpOption\\": "src/PhpOption/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Johannes M. Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh"
},
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "Option Type for PHP",
"keywords": [
"language",
"option",
"php",
"type"
],
"support": {
"issues": "https://github.com/schmittjoh/php-option/issues",
"source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
"type": "tidelift"
}
],
"install-path": "../phpoption/phpoption"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.32.0",
"version_normalized": "1.32.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"time": "2024-09-09T11:45:10+00:00",
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-ctype"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.32.0",
"version_normalized": "1.32.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"time": "2024-12-23T08:48:59+00:00",
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-mbstring"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.32.0",
"version_normalized": "1.32.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"time": "2025-01-02T08:10:11+00:00",
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-php80"
},
{
"name": "vlucas/phpdotenv",
"version": "v5.6.2",
"version_normalized": "5.6.2.0",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
"shasum": ""
},
"require": {
"ext-pcre": "*",
"graham-campbell/result-type": "^1.1.3",
"php": "^7.2.5 || ^8.0",
"phpoption/phpoption": "^1.9.3",
"symfony/polyfill-ctype": "^1.24",
"symfony/polyfill-mbstring": "^1.24",
"symfony/polyfill-php80": "^1.24"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"ext-filter": "*",
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
},
"suggest": {
"ext-filter": "Required to use the boolean validator."
},
"time": "2025-04-30T23:37:27+00:00",
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "5.6-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Dotenv\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Vance Lucas",
"email": "vance@vancelucas.com",
"homepage": "https://github.com/vlucas"
}
],
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
"keywords": [
"dotenv",
"env",
"environment"
],
"support": {
"issues": "https://github.com/vlucas/phpdotenv/issues",
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
"type": "tidelift"
}
],
"install-path": "../vlucas/phpdotenv"
}
],
"dev": true,
"dev-package-names": []
}

View File

@@ -0,0 +1,77 @@
<?php return array(
'root' => array(
'name' => 'claudecio/axiumphp',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'bff4a2e22b0ca200aee4d6a2c7619bcfdee139dc',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'claudecio/axiumphp' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'bff4a2e22b0ca200aee4d6a2c7619bcfdee139dc',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'graham-campbell/result-type' => array(
'pretty_version' => 'v1.1.3',
'version' => '1.1.3.0',
'reference' => '3ba905c11371512af9d9bdd27d99b782216b6945',
'type' => 'library',
'install_path' => __DIR__ . '/../graham-campbell/result-type',
'aliases' => array(),
'dev_requirement' => false,
),
'phpoption/phpoption' => array(
'pretty_version' => '1.9.3',
'version' => '1.9.3.0',
'reference' => 'e3fac8b24f56113f7cb96af14958c0dd16330f54',
'type' => 'library',
'install_path' => __DIR__ . '/../phpoption/phpoption',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.32.0',
'version' => '1.32.0.0',
'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.32.0',
'version' => '1.32.0.0',
'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.32.0',
'version' => '1.32.0.0',
'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(),
'dev_requirement' => false,
),
'vlucas/phpdotenv' => array(
'pretty_version' => 'v5.6.2',
'version' => '5.6.2.0',
'reference' => '24ac4c74f91ee2c193fa1aaa5c249cb0822809af',
'type' => 'library',
'install_path' => __DIR__ . '/../vlucas/phpdotenv',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 80100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020-2024 Graham Campbell <hello@gjcampbell.co.uk>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,33 @@
{
"name": "graham-campbell/result-type",
"description": "An Implementation Of The Result Type",
"keywords": ["result", "result-type", "Result", "Result Type", "Result-Type", "Graham Campbell", "GrahamCampbell"],
"license": "MIT",
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"require": {
"php": "^7.2.5 || ^8.0",
"phpoption/phpoption": "^1.9.3"
},
"require-dev": {
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
},
"autoload": {
"psr-4": {
"GrahamCampbell\\ResultType\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"GrahamCampbell\\Tests\\ResultType\\": "tests/"
}
},
"config": {
"preferred-install": "dist"
}
}

View File

@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
/*
* This file is part of Result Type.
*
* (c) Graham Campbell <hello@gjcampbell.co.uk>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace GrahamCampbell\ResultType;
use PhpOption\None;
use PhpOption\Some;
/**
* @template T
* @template E
*
* @extends \GrahamCampbell\ResultType\Result<T,E>
*/
final class Error extends Result
{
/**
* @var E
*/
private $value;
/**
* Internal constructor for an error value.
*
* @param E $value
*
* @return void
*/
private function __construct($value)
{
$this->value = $value;
}
/**
* Create a new error value.
*
* @template F
*
* @param F $value
*
* @return \GrahamCampbell\ResultType\Result<T,F>
*/
public static function create($value)
{
return new self($value);
}
/**
* Get the success option value.
*
* @return \PhpOption\Option<T>
*/
public function success()
{
return None::create();
}
/**
* Map over the success value.
*
* @template S
*
* @param callable(T):S $f
*
* @return \GrahamCampbell\ResultType\Result<S,E>
*/
public function map(callable $f)
{
return self::create($this->value);
}
/**
* Flat map over the success value.
*
* @template S
* @template F
*
* @param callable(T):\GrahamCampbell\ResultType\Result<S,F> $f
*
* @return \GrahamCampbell\ResultType\Result<S,F>
*/
public function flatMap(callable $f)
{
/** @var \GrahamCampbell\ResultType\Result<S,F> */
return self::create($this->value);
}
/**
* Get the error option value.
*
* @return \PhpOption\Option<E>
*/
public function error()
{
return Some::create($this->value);
}
/**
* Map over the error value.
*
* @template F
*
* @param callable(E):F $f
*
* @return \GrahamCampbell\ResultType\Result<T,F>
*/
public function mapError(callable $f)
{
return self::create($f($this->value));
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* This file is part of Result Type.
*
* (c) Graham Campbell <hello@gjcampbell.co.uk>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace GrahamCampbell\ResultType;
/**
* @template T
* @template E
*/
abstract class Result
{
/**
* Get the success option value.
*
* @return \PhpOption\Option<T>
*/
abstract public function success();
/**
* Map over the success value.
*
* @template S
*
* @param callable(T):S $f
*
* @return \GrahamCampbell\ResultType\Result<S,E>
*/
abstract public function map(callable $f);
/**
* Flat map over the success value.
*
* @template S
* @template F
*
* @param callable(T):\GrahamCampbell\ResultType\Result<S,F> $f
*
* @return \GrahamCampbell\ResultType\Result<S,F>
*/
abstract public function flatMap(callable $f);
/**
* Get the error option value.
*
* @return \PhpOption\Option<E>
*/
abstract public function error();
/**
* Map over the error value.
*
* @template F
*
* @param callable(E):F $f
*
* @return \GrahamCampbell\ResultType\Result<T,F>
*/
abstract public function mapError(callable $f);
}

View File

@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
/*
* This file is part of Result Type.
*
* (c) Graham Campbell <hello@gjcampbell.co.uk>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace GrahamCampbell\ResultType;
use PhpOption\None;
use PhpOption\Some;
/**
* @template T
* @template E
*
* @extends \GrahamCampbell\ResultType\Result<T,E>
*/
final class Success extends Result
{
/**
* @var T
*/
private $value;
/**
* Internal constructor for a success value.
*
* @param T $value
*
* @return void
*/
private function __construct($value)
{
$this->value = $value;
}
/**
* Create a new error value.
*
* @template S
*
* @param S $value
*
* @return \GrahamCampbell\ResultType\Result<S,E>
*/
public static function create($value)
{
return new self($value);
}
/**
* Get the success option value.
*
* @return \PhpOption\Option<T>
*/
public function success()
{
return Some::create($this->value);
}
/**
* Map over the success value.
*
* @template S
*
* @param callable(T):S $f
*
* @return \GrahamCampbell\ResultType\Result<S,E>
*/
public function map(callable $f)
{
return self::create($f($this->value));
}
/**
* Flat map over the success value.
*
* @template S
* @template F
*
* @param callable(T):\GrahamCampbell\ResultType\Result<S,F> $f
*
* @return \GrahamCampbell\ResultType\Result<S,F>
*/
public function flatMap(callable $f)
{
return $f($this->value);
}
/**
* Get the error option value.
*
* @return \PhpOption\Option<E>
*/
public function error()
{
return None::create();
}
/**
* Map over the error value.
*
* @template F
*
* @param callable(E):F $f
*
* @return \GrahamCampbell\ResultType\Result<T,F>
*/
public function mapError(callable $f)
{
return self::create($this->value);
}
}

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,50 @@
{
"name": "phpoption/phpoption",
"description": "Option Type for PHP",
"keywords": ["php", "option", "language", "type"],
"license": "Apache-2.0",
"authors": [
{
"name": "Johannes M. Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh"
},
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
},
"autoload": {
"psr-4": {
"PhpOption\\": "src/PhpOption/"
}
},
"autoload-dev": {
"psr-4": {
"PhpOption\\Tests\\": "tests/PhpOption/Tests/"
}
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
},
"preferred-install": "dist"
},
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "1.9-dev"
}
}
}

View File

@@ -0,0 +1,175 @@
<?php
/*
* Copyright 2012 Johannes M. Schmitt <schmittjoh@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace PhpOption;
use Traversable;
/**
* @template T
*
* @extends Option<T>
*/
final class LazyOption extends Option
{
/** @var callable(mixed...):(Option<T>) */
private $callback;
/** @var array<int, mixed> */
private $arguments;
/** @var Option<T>|null */
private $option;
/**
* @template S
* @param callable(mixed...):(Option<S>) $callback
* @param array<int, mixed> $arguments
*
* @return LazyOption<S>
*/
public static function create($callback, array $arguments = []): self
{
return new self($callback, $arguments);
}
/**
* @param callable(mixed...):(Option<T>) $callback
* @param array<int, mixed> $arguments
*/
public function __construct($callback, array $arguments = [])
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException('Invalid callback given');
}
$this->callback = $callback;
$this->arguments = $arguments;
}
public function isDefined(): bool
{
return $this->option()->isDefined();
}
public function isEmpty(): bool
{
return $this->option()->isEmpty();
}
public function get()
{
return $this->option()->get();
}
public function getOrElse($default)
{
return $this->option()->getOrElse($default);
}
public function getOrCall($callable)
{
return $this->option()->getOrCall($callable);
}
public function getOrThrow(\Exception $ex)
{
return $this->option()->getOrThrow($ex);
}
public function orElse(Option $else)
{
return $this->option()->orElse($else);
}
public function ifDefined($callable)
{
$this->option()->forAll($callable);
}
public function forAll($callable)
{
return $this->option()->forAll($callable);
}
public function map($callable)
{
return $this->option()->map($callable);
}
public function flatMap($callable)
{
return $this->option()->flatMap($callable);
}
public function filter($callable)
{
return $this->option()->filter($callable);
}
public function filterNot($callable)
{
return $this->option()->filterNot($callable);
}
public function select($value)
{
return $this->option()->select($value);
}
public function reject($value)
{
return $this->option()->reject($value);
}
/**
* @return Traversable<T>
*/
public function getIterator(): Traversable
{
return $this->option()->getIterator();
}
public function foldLeft($initialValue, $callable)
{
return $this->option()->foldLeft($initialValue, $callable);
}
public function foldRight($initialValue, $callable)
{
return $this->option()->foldRight($initialValue, $callable);
}
/**
* @return Option<T>
*/
private function option(): Option
{
if (null === $this->option) {
/** @var mixed */
$option = call_user_func_array($this->callback, $this->arguments);
if ($option instanceof Option) {
$this->option = $option;
} else {
throw new \RuntimeException(sprintf('Expected instance of %s', Option::class));
}
}
return $this->option;
}
}

View File

@@ -0,0 +1,136 @@
<?php
/*
* Copyright 2012 Johannes M. Schmitt <schmittjoh@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace PhpOption;
use EmptyIterator;
/**
* @extends Option<mixed>
*/
final class None extends Option
{
/** @var None|null */
private static $instance;
/**
* @return None
*/
public static function create(): self
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function get()
{
throw new \RuntimeException('None has no value.');
}
public function getOrCall($callable)
{
return $callable();
}
public function getOrElse($default)
{
return $default;
}
public function getOrThrow(\Exception $ex)
{
throw $ex;
}
public function isEmpty(): bool
{
return true;
}
public function isDefined(): bool
{
return false;
}
public function orElse(Option $else)
{
return $else;
}
public function ifDefined($callable)
{
// Just do nothing in that case.
}
public function forAll($callable)
{
return $this;
}
public function map($callable)
{
return $this;
}
public function flatMap($callable)
{
return $this;
}
public function filter($callable)
{
return $this;
}
public function filterNot($callable)
{
return $this;
}
public function select($value)
{
return $this;
}
public function reject($value)
{
return $this;
}
public function getIterator(): EmptyIterator
{
return new EmptyIterator();
}
public function foldLeft($initialValue, $callable)
{
return $initialValue;
}
public function foldRight($initialValue, $callable)
{
return $initialValue;
}
private function __construct()
{
}
}

Some files were not shown because too many files have changed in this diff Show More