Search by

piecesphp / database

vmorantes

Clases que interactuan con bases de datos.

Package info

bitbucket.org/piecesphp/database

pkg:composer/piecesphp/database

Statistics

Installs: 1 034

Dependents: 0

Suggesters: 0

v5.3.0 2026-10-08 22:14 UTC

README

Acceso a datos para PHP sobre PDO: la conexión, un constructor de consultas encadenable, mappers de entidad declarativos y la generación del CREATE TABLE a partir de ellos.

Es la capa de datos del framework PiecesPHP, pero no depende de él.

Requisitos

  • PHP >=8.5 <8.6.
  • Extensiones: pdo con pdo_mysql (o pdo_sqlite), mbstring, json, ctype, pcre, date, fileinfo, mysqli, sqlite3, zip.
  • Sin dependencias de Composer en ejecución.

Instalación

composer require piecesphp/database

Qué trae

Todo bajo PiecesPHP\Core\Database:

ClaseQué es
DatabaseLa conexión. Extiende \PDO, siempre en modo excepción
ORM\ActiveRecordEl constructor de consultas: select, insert, update, delete, where, join, having, groupBy, orderBy
ActiveRecordModelActiveRecord configurado con un array de opciones y un almacén de conexiones compartido
EntityMapperUna fila como objeto: declara $table y $fields, y carga, guarda y actualiza
ORM\Statements\WhereSegment, Critery\WhereItem, WhereItemGroupCriterios con marcadores, incluidos grupos (a OR b) AND c
ORM\Statements\HavingSegment, Critery\HavingItem, HavingItemGroupLo mismo para HAVING
SchemeCreatorGenera (y opcionalmente ejecuta) el SQL de la tabla de un EntityMapper
Exceptions\DatabaseClassesExceptionsLa excepción de la biblioteca

⚠ Seguridad: la forma de texto va tal cual al SQL

  • where('texto'), having('texto'), join($tabla, 'texto'), groupBy() y orderBy() insertan el texto literalmente. Nunca metas ahí un valor que venga del usuario.
  • Un valor del usuario va en la forma de array (where(['columna' => $valor])) o en un WhereSegment: viaja por marcador.
  • Desde la 5.0.0 el texto se guarda y se lee tal cual, sin addslashes/stripslashes. Si vienes de la 4.x, lee el CHANGELOG.
  • getCompiledSQL(true) es para registrar y depurar, nunca para ejecutar. Para envolver una consulta en otra —un recuento—, usa getPreparedSQL(), que da el texto con sus marcadores, y ejecútalo con los valores enlazados (getReplaceWhereAndHavingValues() en un SELECT). Ejemplo en el CHANGELOG, versión 5.3.0.

Uso

Conectar

use PiecesPHP\Core\Database\Database;

$db = Database::instanceMySQL('mi_base', 'usuario', 'clave', 'localhost');
// o SQLite en memoria:
$db = Database::instance();

Consultar

use PiecesPHP\Core\Database\ORM\ActiveRecord;

$posts = new ActiveRecord($db, 'posts');
$posts->select(['id', 'title'])
    ->where([
        'status' => 1,                                  // status = :marcador
        'createdAt' => ['>=' => '2026-01-01'],          // createdAt >= :marcador
    ])
    ->orderBy('createdAt DESC');

if ($posts->execute(true, 1, 20)) {   // asociativo, página 1, 20 por página
    $filas = $posts->result();        // array|null
}
  • where() recibe un solo argumento. where('status', 1) no filtra por 1: el segundo argumento se ignora.
  • execute() devuelve bool, no el resultado: se pide después con result(). execute() limpia la consulta al terminar.
  • row() da la primera fila; get($valor, 'columna') busca por una columna.

Insertar, actualizar, borrar

$posts->insert(['title' => 'Hola'])->execute();
$id = $posts->lastInsertId();

$posts->update(['title' => 'Otro'])->where(['id' => $id])->execute();
$posts->delete(['id' => $id])->execute();

update() sin where() falla, salvo que se pida explícitamente con update($datos, true).

Un mapper

use PiecesPHP\Core\Database\EntityMapper;

class PostMapper extends EntityMapper
{
    const TABLE = 'posts';
    protected $table = self::TABLE;
    protected $fields = [
        'id' => ['type' => 'int', 'primary_key' => true],
        'title' => ['type' => 'varchar', 'length' => 255],
        'author' => [
            'type' => 'int',
            'reference_table' => 'users',
            'reference_field' => 'id',
            'human_readable_reference_field' => 'name',
        ],
    ];
}

PostMapper::setOptions(['driver' => 'mysql', 'database' => 'mi_base', 'user' => 'usuario', 'password' => 'clave']);

$post = new PostMapper(1);   // carga la fila con id 1
$post->title = 'Editado';
$post->update();

$nuevo = new PostMapper();
$nuevo->title = 'Nuevo';
$nuevo->author = 1;   // obligatorio: el campo no declara 'null' => true
$nuevo->save();

Tras save(), $nuevo->id ya tiene el id que generó la base (desde la 5.2.0), y un segundo save() del mismo objeto actualiza esa fila. Se asigna solo a claves int o bigint que venían vacías. En SQLite, declara la clave como INTEGER PRIMARY KEY.

Un campo es obligatorio salvo que declare 'null' => true: save() lanza DatabaseClassesExceptions si falta. Opciones de un campo (con su valor por defecto): type (text), length, null, empty, default, raw, primary_key, auto_increment, reference_table, reference_field, reference_primary_key, human_readable_reference_field, has_many, mapper, representation_on_select_statement y meta.

Generar la tabla

use PiecesPHP\Core\Database\SchemeCreator;

$sql = (new SchemeCreator(new PostMapper()))->getSQL();   // solo el SQL de una tabla

// Varias tablas: createScript() y dropScript() reciben SchemeCreator, no mappers.
$creadores = [new SchemeCreator(new PostMapper())];
$sqlDeVarios = SchemeCreator::createScript($creadores);   // padres antes que hijas
$bajada = SchemeCreator::dropScript($creadores);          // el inverso

Si les pasas mappers en vez de SchemeCreator, lanzan InvalidArgumentException (desde la 5.1.0).

Serializar un mapper

Un mapper se puede serializar (serialize(), caché, sesión, cola) sin llevar las credenciales de la base: desde la 5.1.0 no se incluyen. Al deserializarlo toma la conexión de EntityMapper::setOptions(). Si usaba una conexión propia, vuelve a dársela después de unserialize().

Demos y pruebas

  • demos/: ejemplos por componente (active-record/, entity-mapper/, orm/, scheme-creator/), con las tablas de apoyo en demos/db/. Usan MySQL.
  • unit-tests/: suites propias (no PHPUnit). Usan MySQL si conectan y, si no, SQLite en memoria.
composer install
php examples/readme.php            # ejecuta los ejemplos de este README; sale con 1 si alguno no da lo que dice
bin/cli unit-tests --test="all"

Cambios

Ver CHANGELOG.md. La 5.0.0 rompe compatibilidad con los datos guardados por la 4.x.

Licencia

MIT. Ver LICENSE.