API Documentation
Full API documentation for the codesaur/dataobject package.
Table of Contents
Column
Namespace: codesaur\DataObject\Column
Description: Class for defining the structure of a single SQL table column.
This includes all configurations for table creation:
- column name
- data type
- length/size
- NULL or NOT NULL
- default value
- PRIMARY KEY, UNIQUE
- AUTO INCREMENT
Constructor
public function __construct(string $name, string $type, int|string|null $length = null)
Parameters:
$name(string) - Column name$type(string) - Data type$length(int|string|null) - Type length/size
Methods
default(string|int|float|bool|null $default): Column
Set default value.
Parameters:
$default(string|int|float|bool|null) - Default value
Returns: $this (fluent interface)
auto(bool $auto = true): Column
Set AUTO_INCREMENT.
Parameters:
$auto(bool) - Whether AUTO_INCREMENT
Returns: Column
unique(bool $unique = true): Column
Make column UNIQUE.
Parameters:
$unique(bool) - Whether UNIQUE
Returns: Column
primary(bool $primary = true): Column
Make PRIMARY KEY.
Parameters:
$primary(bool) - Whether PRIMARY KEY
Returns: Column
notNull(bool $not_null = true): Column
Set NOT NULL.
Parameters:
$not_null(bool) - Whether NOT NULL
Returns: Column
getName(): string
Get column name.
Returns: Column name
getType(): string
Get column data type.
Returns: Data type (varchar, int, bigint, etc.)
getDataType(): int
Determine data type for PDO.
Returns: PDO::PARAM_* type
getLength(): int|string|null
Get type length.
Returns: Length/size (255 for VARCHAR(255)), or null
getDefault(): string|int|float|bool|null
Get default value.
Returns: Default value, or null
isAuto(): bool
Whether AUTO_INCREMENT.
Returns: Whether AUTO_INCREMENT is set
isString(): bool
Whether text type.
Returns: Whether text type like varchar, text, blob
Supported types: varchar, text, blob, binary, varbinary, char, tinytext, mediumtext, longtext, tinyblob, mediumblob, longblob, enum, set
isInt(): bool
Whether integer type.
Returns: Whether numeric type like int, bigint, smallint, tinyint
Supported types: int, bigint, integer, smallint, int8, bigserial, serial, tinyint, mediumint, bool, boolean
isDecimal(): bool
Decimal numbers.
Returns: Whether decimal type like decimal, float, double
Supported types: decimal, numeric, float, double, real
isDateTime(): bool
Date/time types.
Returns: Whether time type like datetime, date, timestamp
Supported types: datetime, date, timestamp, time, timestamptz, year
isBit(): bool
Whether BIT type.
Returns: Whether BIT type
isNumeric(): bool
Whether numeric values.
Returns: Whether includes integer, decimal, bit types
isNull(): bool
Whether NULL is allowed.
Returns: Whether NULL value is allowed (true=allows, false=NOT NULL)
isPrimary(): bool
Whether PRIMARY KEY.
Returns: Whether PRIMARY KEY column
isUnique(): bool
Whether UNIQUE.
Returns: Whether UNIQUE constraint exists
Model
Namespace: codesaur\DataObject\Model
Description: Base class for models targeting a single table in the DataObject ecosystem.
This class uses TableTrait to:
- manage table structure
- create/check tables and columns
- add data (insert)
- update data (updateById)
- get rows (getRow, getRows)
- get rows by WHERE condition (getRowWhere)
and fully implements basic CRUD operations.
All single-language (non-localized) table models extend this class.
Abstract Class - Must be extended.
Uses: TableTrait
Methods
insert(array $record): array
Add data (INSERT).
MySQL/SQLite -> uses lastInsertId()
PostgreSQL -> uses RETURNING *
Parameters:
$record(array) - Key -> value pairs for the row to add
Returns: Full information of the new row (array with all columns). Throws Exception on error
Exception: Exception
updateById(int $id, array $record): array
Update row by ID (UPDATE).
UPDATE table SET field=:value WHERE id=X
PostgreSQL -> RETURNING *
MySQL/SQLite -> SELECT * WHERE id=...
Parameters:
$id(int) - ID to update$record(array) - Fields to update ['column' => value, ...]
Returns: Full information of updated row (array with all columns). Throws Exception on error
Exception: Exception - If table doesn't have primary auto increment id column or update data is empty
existsById(int $id): bool
Check if row exists by ID. Lightweight query using SELECT 1 ... LIMIT 1.
Parameters:
$id(int) - ID to check
Returns: Whether row exists
getById(int $id): array|null
Get row by ID. Shortcut for getRowWhere(['id' => $id]).
Parameters:
$id(int) - ID to retrieve
Returns: Row data (array), or null (if not found)
countRows(array $condition = []): int
Count rows matching condition. Uses COUNT(*) query.
Parameters:
$condition(array) - WHERE, JOIN conditions (LIMIT, OFFSET, ORDER BY not needed)
Returns: Number of matching rows
getRows(array $condition = []): array
Get multiple rows.
Parameters:
$condition(array) - Conditions for SELECTStatement (JOIN, WHERE, ORDER, LIMIT...)
Returns: Array indexed by Primary ID (if ID exists), or simple array (if no ID)
getRow(array $condition = []): array|null
Get a single row.
Parameters:
$condition(array) - SELECT conditions
Returns: Row information (array), or null (if not found)
getRowWhere(array $with_values): array|null
Get row using WHERE key=:value syntax.
Parameters:
$with_values(array) - key => value array
Returns: Row information (array), or null (if not found)
LocalizedModel
Namespace: codesaur\DataObject\LocalizedModel
Description: Base class for 2-table model pattern for storing localized content in multiple languages.
Architecture:
- Primary table -> id, is_active, sort, etc. universally shared columns
- Content table (table_content) -> fields to store in multiple languages
Content table structure:
id(primary)parent_id(FK -> primary table.id)code(language code, e.g., 'en', 'mn', 'jp')- other fields to store in that language
By using this class:
- fully automates localized insert/update
- merges localized rows and returns as unified array
- no code duplication needed
Abstract Class - Must be extended.
Uses: TableTrait
Properties
protected readonly array $contentColumns
Content table columns.
Methods
setTable(string $name): void
Set table name and create primary and content tables.
Parameters:
$name(string) - Table name
Exception: Exception - If columns are not defined or id column doesn't exist
getContentName(): string
Automatically generated content table name.
Returns: {table_name}_content
getContentColumns(): array
Get content table columns.
Returns: Column[] array
getContentColumn(string $name): Column
Get specific content table column.
Parameters:
$name(string) - Column name
Returns: Column object
Exception: Exception - If column not found
setContentColumns(array $columns): void
Set content table columns.
Parameters:
$columns(Column[]) - Array of Column objects
Exception: Exception - If object is not Column or invalid column is set
hasContentColumn(string $name): bool
Whether the content table has a column with that name (includes the base columns id, parent_id, code).
assertColumn(string $name): string
Override of TableTrait::assertColumn(): returns the name if it is declared on either
the primary or the content table.
Exception: InvalidArgumentException - if declared on neither table
orderBy(string $column, string $direction = 'ASC'): string
Override of TableTrait::orderBy(). select()/getRows()/getRow()/getRowsByCode() join
the primary table as p and the content table as c, so the column is returned with the
matching alias. A column present in both tables (id) resolves to the primary table.
| Column | Result (PostgreSQL/SQLite) | Result (MySQL) |
|---|---|---|
primary (slug) | p."slug" ASC | p.`slug` ASC |
content (title) | c."title" ASC | c.`title` ASC |
$rows = $model->getRowsByCode('mn', [
'WHERE' => 'p.is_active=1',
'ORDER BY' => $model->orderBy($_GET['sort'] ?? 'id', $_GET['dir'] ?? 'DESC')
]);
countRows() queries the primary table without an alias, so orderBy() output is not meant for it (ORDER BY has no effect on COUNT anyway).
insert(array $record, array $content): array
Add row with multi-language content.
Parameters:
$record(array) - Primary table data: ['column' => value, ...]- Example:
['name' => 'product', 'status' => 'active']
- Example:
$content(array) - Content grouped by language: ['mn' => [col => val], 'en' => [...], ...]- Example:
['en' => ['title' => 'English', 'description' => '...'], 'mn' => [...]]
- Example:
Returns: Returns new row. Throws Exception on failure
Exception: Exception, InvalidArgumentException - If content is empty or error occurs
Return value structure: Same as getRow() return value structure
updateById(int $id, array $record, array $content): array
Update row with multi-language content by id column.
Parameters:
$id(int) - ID to update$record(array) - Primary table update: ['column' => value, ...]- Example:
['read_count' => 10]
- Example:
$content(array) - Content update grouped by language: ['mn' => [col => val], 'en' => [...], ...]- Example:
['en' => ['title' => 'New title'], 'mn' => ['description' => 'Шинэ тайлбар']]
- Example:
Returns: Returns updated row. Throws Exception on failure
Exception: Exception - If update data is empty or error occurs
Return value structure: Same as getRow() return value structure
existsById(int $id): bool
Check if row exists by ID. Lightweight query using SELECT 1 ... LIMIT 1 on primary table.
Parameters:
$id(int) - ID to check
Returns: Whether row exists
getById(int $id): array|null
Get row (with all languages) by ID. Shortcut for getRowWhere(['p.id' => $id]).
Parameters:
$id(int) - ID to retrieve
Returns: Row data with localized content (array), or null (if not found)
countRows(array $condition = []): int
Count rows matching condition on primary table. Uses COUNT(*) query. No content JOIN needed, no p. prefix needed in WHERE.
Parameters:
$condition(array) - WHERE conditions
Returns: Number of matching rows
select(string $selection = '*', array $condition = []): \PDOStatement
Select from dual tables (primary + content) with JOIN.
Parameters:
$selection(string) - Columns to select ('*' or 'col1, col2, ...')$condition(array) - SELECT conditions
Returns: PDOStatement
getRows(array $condition = []): array
Get multiple rows (with multiple languages).
Parameters:
$condition(array) - SELECT conditions
Returns: Array [primary_id => rowStructure], rowStructure has same structure as getRow() return value
Example:
[
1 => [
'id' => 1,
'name' => 'product_name',
'status' => 'active',
'localized' => [
'en' => [
'title' => 'English Title',
'description' => 'English Description'
],
'mn' => [
'title' => 'Монгол гарчиг',
'description' => 'Монгол тайлбар'
]
]
],
...
]
getRow(array $condition): array|null
Get a single row with multiple languages.
Parameters:
$condition(array) - SELECT conditions (WHERE, JOIN, ORDER, LIMIT, etc.)
Returns: On success, array with following structure, null if not found:
- All primary table column values at direct level (e.g., 'id', 'name', 'status', etc.)
- Multi-language contents under 'localized' key:
- First level: language code (e.g., 'en', 'mn', 'ru', etc.)
- Second level: content column name (e.g., 'title', 'description', etc.)
- Value: content value in that language
Example:
[
'id' => 1,
'name' => 'product_name',
'status' => 'active',
'localized' => [
'en' => [
'title' => 'English Title',
'description' => 'English Description'
],
'mn' => [
'title' => 'Монгол гарчиг',
'description' => 'Монгол тайлбар'
]
]
]
getRowWhere(array $with_values): array|null
Get row (with multiple languages) by WHERE key=value condition.
Parameters:
$with_values(array) - key => value array
Returns: Same structure as getRow() return value; null if not found
Note: This function calls getRow()
getRowsByCode(string $code, array $condition = []): array
Get multiple rows by specific language code.
This function returns only the specified language's content when given a language code.
Parameters:
$code(string) - Language code (e.g., 'en', 'mn', 'ru')$condition(array) - SELECT conditions (WHERE, JOIN, ORDER, LIMIT, etc., code condition is automatically added)
Returns: Array [primary_id => rowStructure], rowStructure has following structure:
- All primary table column values at direct level
- Only that language's content under 'localized' key (no language code level)
Example: When code='en':
[
1 => [
'id' => 1,
'name' => 'product_name',
'status' => 'active',
'localized' => [
'title' => 'English Title',
'description' => 'English Description'
]
],
2 => [
'id' => 2,
'name' => 'another_product',
'status' => 'draft',
'localized' => [
'title' => 'Another Title',
'description' => 'Another Description'
]
]
]
Note: Function to get all languages' content - getRows(), Function to get a single row - getRow()
PDOTrait
Namespace: codesaur\DataObject\PDOTrait
Description: This trait centralizes general database operations (prepare, query, exec, quote, get driver type) based on PDO into a single standard interface.
All Model and Table-related classes in the codesaur ecosystem use this trait to:
- share PDO instance
- reliably prepare SQL statements
- throw errors in standard format
- automatically detect MySQL/PostgreSQL/SQLite driver
- manage configurations like FOREIGN KEY CHECKS
Trait
Properties
protected \PDO $pdo
PDO instance.
private ?string $driver
Driver name in use (mysql | pgsql | sqlite).
Methods
setInstance(\PDO $pdo): void
Set PDO instance to model.
Parameters:
$pdo(\PDO) - PDO instance
getDriverName(): string|null
Return PDO driver name in use.
Returns: Driver name (mysql, pgsql, sqlite)
throwPdoError(string $message, \PDO|\PDOStatement $source): never
Throw Exception with PDO/PDOStatement error information.
Parameters:
$message(string) - Error description$source(\PDO|\PDOStatement) - Error source
Exception: Exception - Always thrown with driver-specific error code and message
quote(string $string, int $parameter_type = \PDO::PARAM_STR): string|false
Escape SQL string in driver-appropriate format.
Parameters:
$string(string) - Text to use in SQL$parameter_type(int) - PDO::PARAM_* type
Returns: Escaped string, or false
prepare(string $statement, array $driver_options = []): \PDOStatement
Prepare SQL statement and return PDOStatement. Throws standard Exception on failure.
Parameters:
$statement(string) - SQL to prepare$driver_options(array) - PDO driver options
Returns: PDOStatement
Exception: Exception - If PDO error occurs
exec(string $statement): int|false
Execute SQL command directly (DDL/DML).
Parameters:
$statement(string) - SQL command (CREATE, DROP, UPDATE, etc.)
Returns: Number of affected rows or false
query(string $statement): \PDOStatement
Execute query directly without preparation.
Parameters:
$statement(string) - SQL SELECT
Returns: PDOStatement
Exception: Exception - If PDO error occurs
hasTable(string $table): bool
Check if table exists in database.
Parameters:
$table(string) - Table name
Returns: Whether table exists
Exception: RuntimeException - If unsupported driver
Supported drivers:
- MySQL:
SHOW TABLES LIKE ... - PostgreSQL:
SELECT ... FROM pg_tables ... - SQLite:
SELECT ... FROM sqlite_master ...
TableTrait
Namespace: codesaur\DataObject\TableTrait
Description: This trait fully contains basic capabilities for working with tables in the database.
Including:
- Table name and column definitions
- Automatic table creation logic (adapted for MySQL/PostgreSQL/SQLite)
- PRIMARY, UNIQUE column validation
- CRUD helper operations (deleteById, deactivateById)
- SELECT statement builder (JOIN, WHERE, LIMIT...)
- Check if table exists
This trait is the base foundation for Model and LocalizedModel.
Trait
Uses: PDOTrait
Properties
protected readonly string $name
SQL table name.
protected readonly array $columns
SQL table column definitions. Array of Column objects.
Abstract Methods
public abstract function __construct(\PDO $pdo)
Model constructor - PDO must be passed.
Parameters:
$pdo(\PDO) - PDO instance
protected abstract function __initial(): void
Initial configuration after table is first created on actual database with CREATE.
Methods
__destruct(): void
Destructor - releases PDO.
getName(): string
Get table name.
Returns: Table name
Exception: Exception - If table name is not set
setTable(string $name): void
Set table name and create table.
Filters name with allowed characters.
Parameters:
$name(string) - Table name
Exception: Exception - If columns are not defined or error occurs creating table
getColumns(): array
Return all table columns.
Returns: Column[] array
Exception: Exception - If columns are not defined
setColumns(array $columns): void
Set table columns validated as Column objects.
Parameters:
$columns(Column[]) - Array of Column objects
Exception: Exception - If object is not Column
getColumn(string $name): Column
Return column by name.
Parameters:
$name(string) - Column name
Returns: Column object
Exception: Exception - If column not found
hasColumn(string $name): bool
Whether column with name exists.
Parameters:
$name(string) - Column name
Returns: Whether column exists
assertColumn(string $name): string
Checks that the column is declared on this model and returns its name. User input
($_GET['sort'] etc.) placed in an SQL identifier position (ORDER BY, GROUP BY,
$selection) cannot be parameterized through PARAM, so this whitelist is the guard.
Parameters:
$name(string) - Column name to check (case-sensitive)
Returns: The declared column name (unchanged)
Exception: InvalidArgumentException (code Constants::ERR_COLUMN_NOT_FOUND) - if the column is not declared
$column = $model->assertColumn($_GET['group'] ?? 'category');
$rows = $model->getRows(['GROUP BY' => $column]);
assertDirection(string $direction): string
Validates an ORDER BY direction. Case-insensitive, surrounding whitespace is trimmed.
Parameters:
$direction(string) -'ASC','asc','DESC','desc'
Returns: 'ASC' or 'DESC'
Exception: InvalidArgumentException - for any other value
quoteIdentifier(string $identifier): string
Wraps an identifier (column or table name) in the driver's quoting characters. Quote characters inside the identifier are doubled.
| Driver | Result |
|---|---|
| MySQL | `name` |
| PostgreSQL | "name" |
| SQLite | "name" |
Note: quoting handles reserved words (order, desc, ...) and special characters, it
is not a whitelist. Always run user input through assertColumn() before quoting it.
orderBy(string $column, string $direction = 'ASC'): string
Safe ORDER BY clause builder: assertColumn() + quoteIdentifier() + assertDirection().
Parameters:
$column(string) - A declared column name$direction(string) -'ASC'(default) or'DESC'
Returns: `title` DESC (MySQL), "title" DESC (PostgreSQL, SQLite)
Exception: InvalidArgumentException - undeclared column or invalid direction
Example:
// User input can be passed directly
$rows = $model->getRows([
'WHERE' => 'is_active=:active',
'ORDER BY' => $model->orderBy($_GET['sort'] ?? 'id', $_GET['dir'] ?? 'DESC'),
'LIMIT' => 20,
'PARAM' => [':active' => 1]
]);
// Multiple columns
'ORDER BY' => $model->orderBy('category') . ', ' . $model->orderBy('id', 'DESC')
LocalizedModel overrides this method with a JOIN-alias aware version (p., c.).
deleteById(int $id): bool
Delete row by ID.
Parameters:
$id(int) - ID to delete
Returns: Whether successful
Exception: Exception - If id column is not primary auto increment
deactivateById(int $id, array $record = []): bool
Deactivate row by ID (soft delete). Sets is_active to 0. UNIQUE columns remain unchanged.
Parameters:
$id(int) - ID to deactivate$record(array) - Additional update fields
Returns: Whether successful
Exception: Exception - If id column is not primary auto increment or is_active column doesn't exist
protected final function createTable(string $table, array $columns): void
Create SQL table (adapted for MySQL/PostgreSQL/SQLite).
Parameters:
$table(string) - Table name$columns(Column[]) - Array of columns
Exception: Exception - If error occurs creating table
selectStatement(string $fromTable, string $selection = '*', array $condition = []): \PDOStatement
Flexible SELECT statement builder.
Parameters:
$fromTable(string) - FROM table name$selection(string) - Columns to select ('*' or 'col1, col2, ...')$condition(array) - Conditions:'JOIN'/'INNER JOIN'/'LEFT JOIN'/'RIGHT JOIN'/'CROSS JOIN'- JOIN clause'WHERE'- WHERE clause (example: 'field=:param AND field2>:param2')'GROUP BY'- GROUP BY clause'HAVING'- HAVING clause'ORDER BY'- ORDER BY clause'LIMIT'- LIMIT clause'OFFSET'- OFFSET clause'PARAM'- Parameter array [':param' => value, ...]
Returns: Prepared statement (PDOStatement)
Exception: Exception - If error occurs executing SELECT
Security: clauses are concatenated into the SQL verbatim. Always pass values through
PARAM; never concatenate user input into WHERE/ORDER BY/$selection/$fromTable.
When user input is needed in an identifier position, use assertColumn() / orderBy().
Example:
$stmt = $model->selectStatement('users', '*', [
'WHERE' => 'is_active = :active AND age > :age',
'ORDER BY' => 'created_at DESC',
'LIMIT' => 10,
'PARAM' => [
':active' => 1,
':age' => 18
]
]);
protected function getSyntax(Column $column): string
Generate SQL syntax for Column object. Converts types adapted for MySQL/PGSQL/SQLite.
Parameters:
$column(Column) - Column object
Returns: SQL format (example: id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY)
Type conversions:
- PostgreSQL: int8->bigint, integer->int, tinyint->smallint, datetime->timestamp, tinytext/mediumtext/longtext->text, double->double precision, float->real, blob/tinyblob/mediumblob/longblob/binary/varbinary->bytea, bigint+auto->bigserial, int+auto->serial, smallint+auto->smallserial
- SQLite: All integer types->INTEGER, decimal/float/double/double precision->REAL, blob/binary/bytea->BLOB, others->TEXT
- MySQL: bigserial->bigint, serial->int, smallserial->smallint, timestamptz->timestamp, jsonb->json, uuid->char(36), inet/cidr->varchar(45), bytea->longblob, double precision->double
Database Support
Supported Databases
- MySQL - Full support
- PostgreSQL - Full support
- SQLite - Full support
Driver-Specific Features
MySQL
AUTO_INCREMENTfor auto-increment columnsSHOW TABLESfor table existence check- Collation support
PostgreSQL
RETURNING *clause for INSERT/UPDATEserial,bigserialfor auto-incrementpg_tablesfor table existence check
SQLite
AUTOINCREMENTfor auto-increment primary keyssqlite_masterfor table existence check- Simplified type system (INTEGER, REAL, TEXT, BLOB)