Search by

market-media / datalayer

joseluisaldmarket-media

O datalayer é um componente de abstração persistente do seu banco de dados que utiliza PDO.

Package info

github.com/market-media/datalayer

pkg:composer/market-media/datalayer

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v2.1.0 2026-05-08 20:12 UTC

This package is auto-updated.

Last update: 2026-10-22 15:41:55 UTC


README

O datalayer é um componente para abstração de persistência no seu banco de dados que usa PDO com prepared statements para executar rotinas comuns como cadastrar, ler, editar e remover dados.

PHP 8.5 e migração de segurança

Requer PHP 8.5 ou superior (>=8.5), PDO e o driver do banco utilizado. Execute o Composer com PHP 8.5 ou superior para atualizar o autoload e a verificação de plataforma. As mudanças incompatíveis desta revisão devem ser consideradas antes de atualizar aplicações:

  • Declare os campos graváveis no argumento writable do construtor. Quando omitido, somente os campos de required são graváveis. Campos desconhecidos, chave primária, arrays e objetos são rejeitados. Não inclua atributos de privilégio nessa lista.
  • Cadastre propriedades calculadas em protected array $computed = ['full_name' => 'fullName'];. Apenas getters explicitamente registrados são chamados por leitura de propriedade.
  • Prefira parâmetros em array: find('name = :name', ['name' => $name]). Strings geradas com http_build_query() continuam aceitas; não monte essas strings por concatenação.
  • Condições aceitam comparações entre identificadores ou parâmetros, AND, OR, NOT, parênteses, IS NULL, LIKE, IN e BETWEEN. Valores literais, comentários, funções, subconsultas e múltiplos comandos são rejeitados. Use um placeholder distinto por valor.
  • Projeção, agrupamento e ordenação aceitam listas de identificadores; projeção também aceita *, tabela.* e AS. Ordenação aceita ASC e DESC. RAND() não é aceito.
  • Estruturas SQL devem continuar definidas pelo código da aplicação. Nunca repasse a requisição como condição SQL. A validação sintática não verifica autorização nem quais colunas um usuário pode consultar; use mapas de opções permitidas na aplicação.
  • find() inicia uma nova consulta e limpa JOIN, filtros e paginação anteriores. in() vazio não retorna registros e filtros são agrupados para preservar condições com OR.
  • data() retorna uma cópia dos dados. A chave primária é carregada pelo banco e não pode ser alterada por atribuição. IDs continuam sendo inteiros positivos.
  • Sobrescreva protected function validate(array $data): void para validar formatos, limites e regras do modelo. Lance InvalidArgumentException para valores inválidos. Os dados são preservados para persistência; não há uma sanitização genérica de HTML.
  • Connect::getInstance() lança PDOException quando a conexão falha. Erros de SQL em save(), fetch() e delete() mantêm os retornos de falha e ficam em fail(). Argumentos inválidos lançam InvalidArgumentException. Não exiba exceções ao cliente. As mensagens da biblioteca são em PT-BR. Falhas do driver são encapsuladas em DatabaseException (subclasse de PDOException), preservando SQLSTATE, errorInfo e a exceção original em getPrevious() para diagnóstico interno.
  • As conexões distinguem credenciais, porta e opções. Configuração é exclusivamente do servidor. Nomes de bancos de rede aceitam letras ASCII, números, _, - e .; caminhos SQLite são configurados separadamente. MySQL usa utf8mb4 e prepares nativos.

Os exemplos executam somente em CLI e usam DB_USERNAME, DB_PASSWORD, DB_HOST, DB_PORT e DB_DATABASE do ambiente. O exemplo de múltiplos bancos usa também as variáveis equivalentes com prefixo SECONDARY_DB_. Não publique example/, vendor/ ou arquivos de configuração dentro da raiz pública. Os exemplos podem alterar e excluir registros: execute-os apenas em um banco de desenvolvimento.

Controle de acesso, autorização por registro/tenant e proteção CSRF nas rotas com autenticação por cookies pertencem à aplicação consumidora. Nunca devolva data() inteiro em respostas públicas; selecione campos permitidos. Para texto em HTML, use htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') na saída. Use credenciais com privilégios mínimos e logs restritos, sem senhas ou dados pessoais.

Para verificar a migração, consulte o guia oficial do PHP 8.5. Os testes de regressão usam SQLite em memória, sem acessar o banco da aplicação:

composer install
composer validate --strict
composer test

O comando de teste requer a extensão pdo_sqlite, além do PHP 8.5. A integração contínua também valida a sintaxe dos arquivos PHP.

Destaques

  • Fácil de configurar (Fácil de configurar)
  • Asbtração total do CRUD
  • Criar modelos seguros (Crie de modelos seguros)
  • Compositor pronto (Pronto para o compositor)
  • Compatível com PSR-2 (Compatível com PSR-2)

Instalação

A datalayer está disponível via Composer:

"market-media/datalayer": "2.0.*"

ou rode

composer require market-media/datalayer

Documentação

Para mais detalhes sobre como usar o Data Layer, veja a pasta de exemplo com detalhes no diretório do componente

Conexão

Para começar a usar o Data Layer precisamos de uma conexão com o seu banco de dados. Para ver as conexões possíveis acesse o manual de conexões do PDO em PHP.net

define('DATA_LAYER_CONFIG', [
    "driver" => "mysql",
    "host" => "localhost",
    "port" => "3306",
    "dbname" => "datalayer_example",
    "username" => getenv("DB_USERNAME"),
    "passwd" => getenv("DB_PASSWORD"),
    "charset" => "utf8mb4",
    "options" => [
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
        PDO::ATTR_CASE => PDO::CASE_NATURAL
    ]
]);

Seu modelo

O Data Layer é baseado em uma estrutura MVC com os padrões de projeto Layer Super Type e Active Record. Logo para consumir é necessário criar o modelo de sua tabela e herdar o Data Layer.

<?php

class User extends DataLayer
{
    /**
     * User constructor.
     */
    public function __construct()
    {
        //string "TABLE_NAME", array ["REQUIRED_FIELD_1", "REQUIRED_FIELD_2"], string "PRIMARY_KEY", bool "TIMESTAMPS"
        parent::__construct("users", ["first_name", "last_name"]);
    }
}

find

<?php

use Example\Models\User;

$model = new User();

//find all users
$users = $model->find()->fetch(true);

//find all users limit 2
$users = $model->find()->limit(2)->fetch(true);

//find all users limit 2 offset 2
$users = $model->find()->limit(2)->offset(2)->fetch(true);

//find all users limit 2 offset 2 order by field ASC
$users = $model->find()->limit(2)->offset(2)->order("first_name ASC")->fetch(true);

// find all users with in operator
$users = $model->find()->in("id", [1, 2, 3])->fetch(true);

//looping users
foreach ($users as $user) {
    echo htmlspecialchars((string) $user->first_name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

//find one user by condition
$user = $model->find("first_name = :name", "name=Robson")->fetch();
echo htmlspecialchars((string) $user->first_name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

//find one user by two conditions
$user = $model->find("first_name = :name AND last_name = :last", "name=Robson&last=Leite")->fetch();
echo htmlspecialchars((string) $user->full_name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

//find one user by condition and with in operator
$user = $model->find("first_name = :name", "name=Robson")->in("last_name",["Menezes", "Sampaio"])->fetch(true);

foreach ($users as $user) {
    echo htmlspecialchars((string) $user->full_name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

findById

<?php

use Example\Models\User;

$model = new User();
$user = $model->findById(2);
echo htmlspecialchars((string) $user->first_name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

secure params

Consulte exemplo find_example.php e classes modelo

<?php

$params = http_build_query(["name" => "UpInside & Associated"]);
$company = (new Company())->find("name = :name", $params);
var_dump($company, $company->fetch());

join method

Consulte exemplo find_example.php e classes modelo

<?php

$addresses = new Address();
$address = $addresses->findById(22);
//get user data to this->user->[all data]
$address->getUser();
var_dump($address);

count

<?php

use Example\Models\User;

$model = new User();
$count = $model->find()->count();

save create

<?php

use Example\Models\User;

$user = new User();
$user->first_name = "Robson";
$user->last_name = "Leite";
$userId = $user->save();

save update

<?php

use Example\Models\User;

$user = (new User())->findById(2);
$user->first_name = "Robson";
$userId = $user->save();

destroy

<?php

use Example\Models\User;

$user = (new User())->findById(2);
$user->destroy();

fail

<?php

use Example\Models\User;

$user = (new User())->findById(2);
if($user->fail()){
    echo "Não foi possível concluir a operação.";
}

custom data method

<?php

class User extends DataLayer {

    protected array $computed = ['full_name' => 'fullName', 'document' => 'document'];

    public function fullName(): string
    {
        return "{$this->first_name} {$this->last_name}";
    }

    public function document(): string
    {
        return "Restrict";
    }
}

echo htmlspecialchars((string) $this->full_name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); //Robson V. Leite
echo $this->document; //Restrict

join method

<?php

$user = new User();

// Exemplo usando inner JOIN
$results = $user->find("users.status = :s", "s=active", "users.id, users.name, roles.title")
    ->join("roles", "roles.id = users.role_id")
    ->fetch(true);

// Exemplo encadeando múltiplos JOINS (INNER e LEFT)
$complexResult = $user->find(null, null, "users.*, posts.title, logs.created_at")
    ->join("posts", "posts.user_id = users.id")
    ->join("logs", "logs.user_id = users.id", "LEFT")
    ->order("users.name ASC")
    ->fetch(true);

transaction

<?php

$user = new User();
$address = new Address();

// Inicia a transação
$user->beginTransaction();

// Preenche os dados do usuário
$user->first_name = "João";
$user->last_name = "Silva";

if (!$user->save()) {
    // Se falhar, desfaz a transação e exibe o erro
    $user->rollBack();
    echo "Não foi possível salvar o usuário.";
    exit;
}

// Preenche os dados do endereço com o ID do usuário recém-criado
$address->user_id = $user->id; // Aqui o Active Record já populou o ID
$address->address = "Rua das Flores, 123";

if (!$address->save()) {
    // Se o endereço falhar, o usuário criado acima TAMBÉM SERÁ APAGADO (Rollback)
    $address->rollBack();
    echo "Não foi possível salvar o endereço.";
    exit;
}

// Se tudo deu certo, consolida as alterações no banco de dados
$user->commit();

echo "Usuário e endereço cadastrados com sucesso!";

License

The MIT License (MIT). Please see License File for more information.