Skip to content

Repository files navigation

prado-sqlmap

SqlMap Data Mapper extension for the PRADO PHP Framework.

SqlMap maps SQL statements to PHP objects using XML configuration files. Queries, inserts, updates, and deletes are defined in XML; the gateway executes them and maps result rows to objects or arrays automatically. prado-sqlmap implements the iBATIS SQL Maps 2 specification; MyBATIS is the actively maintained successor with extensive reference documentation.

Requirements

  • PHP 8.1 or later
  • PRADO 4.3.3 or later (pradosoft/prado)
  • PDO extension and a PDO driver for your database

Installation

Within a PRADO Application:

composer require pradosoft/prado-sqlmap

Supported Databases

Any database with a PDO driver. The test suite covers:

DriverPDO DSN prefix
MySQL / MariaDBmysql:
PostgreSQLpgsql:
SQLitesqlite:
Firebirdfirebird:
SQL Serversqlsrv:
Oracleoci:
IBM DB2ibm:

Configuration

Register pradosoft/prado-sqlmap or TSqlMapConfig as an application module in your PRADO application.xml (or application.php).

Pattern 1 — Shared connection (recommended)

Reference a TDataSourceConfig module by its id:

<modules>
<moduleid="db"class="System.Data.TDataSourceConfig">
<databaseConnectionString="mysql:host=localhost;dbname=myapp"Username="user"Password="secret"/>
</module>
<moduleid="sqlmap"class="Prado\Data\SqlMap\TSqlMapConfig"ConnectionID="db"ConfigFile="Application.SqlMap.sqlmap"/>
</modules>

Pattern 2 — Inline connection

Embed the <database> element directly inside TSqlMapConfig:

<modules>
<moduleid="sqlmap"class="Prado\Data\SqlMap\TSqlMapConfig"ConfigFile="Application.SqlMap.sqlmap">
<databaseConnectionString="sqlite:/path/to/app.db"/>
</module>
</modules>

TSqlMapConfig properties

PropertyTypeDescription
ConfigFilestringDot-notation path to the SqlMap XML config file
ConnectionIDstringID of a TDataSourceConfig module to share its connection
EnableCacheboolCache the parsed manager in the PRADO application cache (default false)

SqlMap XML Configuration

For the upstream specification see the MyBATIS 3 documentation.

Two kinds of XML files drive SqlMap: a config file (sqlmap.xml) that wires everything together, and one or more map files that declare the SQL statements and their mappings.


Config file (sqlmap.xml)

The root element is <sqlMapConfig>. It accepts three child sections in any order.

<?xml version="1.0" encoding="utf-8"?>
<sqlMapConfig>
<!-- 1. Global properties — substituted as ${name} in map files -->
<properties>
<propertyname="selectKey"value="SELECT LAST_INSERT_ID()"/>
<propertyname="schema"value="myapp"/>
</properties>
<!-- 2. Custom PHP type handlers -->
<typeHandlers>
<typeHandlerclass="App\SqlMap\BoolHandler"dbType="TINYINT"/>
<typeHandlerclass="App\SqlMap\MoneyHandler"dbType="DECIMAL"type="float"/>
</typeHandlers>
<!-- 3. Map files — paths resolved relative to this config file -->
<sqlMaps>
<sqlMapresource="Account.xml"/>
<sqlMapresource="Order.xml"/>
</sqlMaps>
</sqlMapConfig>

<typeHandler> attributes

AttributeRequiredDescription
classYesFully-qualified PHP class name implementing TSqlMapTypeHandler
dbTypeNoDatabase column type this handler applies to (e.g. TINYINT, VARCHAR)
typeNoPHP type this handler applies to (e.g. bool, float)

<sqlMap> attributes

AttributeDescription
resourcePath to a map file, relative to the config file directory

Map file root: <sqlMap>

<sqlMapnamespace="Account">
<!-- cacheModel, alias, resultMap, parameterMap, and statement elements -->
</sqlMap>
AttributeDescription
namespaceOptional prefix for all statement IDs in this file. Cross-file references use Namespace.StatementId.

All top-level elements may appear in any order and any number of times. The <statements> element is an optional grouping wrapper with no semantic effect — its children are equivalent to top-level elements.


<alias> — type aliases

Declares short names for PHP class names. Used mainly when porting from .NET iBATIS (which required fully-qualified assembly names). In PHP, this is rarely needed.

<alias>
<typeAliasalias="Account"type="App\Model\Account"/>
</alias>
AttributeDescription
aliasShort name usable in class, resultClass, parameterClass, and type attributes
typeFully-qualified PHP class name

<cacheModel> — result caching

Declares a named cache that <select> statements can reference.

<cacheModelid="account-cache"implementation="LRU"readOnly="true"serialize="false">
<flushIntervalhours="1"minutes="30"seconds="0"/>
<flushOnExecutestatement="UpdateAccount"/>
<flushOnExecutestatement="DeleteAccount"/>
<propertyname="size"value="100"/>
</cacheModel>

<cacheModel> attributes

AttributeDefaultDescription
idUnique name for this cache model within the file
implementationCache type: LRU, FIFO, MEMORY, or a fully-qualified class name
readOnlytrueWhen true, all callers receive the same cached object (no defensive copy). Set false to let callers mutate their copy safely.
serializefalseSerialize objects before storing. Useful when readOnly="false" to guarantee isolation.

Cache model child elements

ElementAttributesDescription
<flushInterval>hours, minutes, secondsInvalidate the cache on a time interval. All three attributes are optional and additive.
<flushOnExecute>statementFlush this cache whenever the named statement executes (insert/update/delete). Multiple elements are allowed.
<property>name, valueImplementation-specific setting. LRU and FIFO accept size (maximum entry count).

Cache implementation types

TypeEvictionsize property
LRULeast-recently-usedRequired
FIFOFirst-in, first-outRequired
MEMORYNone (unbounded)Ignored

<resultMap> — column-to-object mapping

Maps result-set columns to PHP object properties or array keys.

<resultMapid="account-result"class="App\Model\Account"extends="base-result"groupBy="id">
<resultproperty="id"column="account_id"/>
<resultproperty="firstName"column="account_first_name"/>
<resultproperty="emailAddress"column="account_email"nullValue=""/>
<resultproperty="role"column="account_role"type="int"/>
<resultproperty="active"column="account_active"typeHandler="BoolHandler"/>
<resultproperty="lineItems"column="order_id"select="GetLineItemsForOrder"lazyLoad="true"/>
<resultproperty="address"resultMapping="address-result"/>
<discriminatorcolumn="doc_type"type="string">
<subMapvalue="Book"resultMapping="book-result"/>
<subMapvalue="Newspaper"resultMapping="newspaper-result"/>
</discriminator>
</resultMap>

<resultMap> attributes

AttributeDescription
idUnique name within the namespace
classPHP class name (or alias) to instantiate per row. Use array for associative arrays or string/int/float for scalar results.
extendsID of another <resultMap> whose <result> elements are inherited. Allows sharing a base mapping.
groupByProperty name (or comma-separated list) used to collapse repeated parent rows into one object with a child list. See GroupBy.

<result> attributes

AttributeDescription
propertyPHP object property or array key to set. Supports dotted paths: favouriteItem.id sets $obj->getFavouriteItem()->setId(...).
columnResult-set column name to read. Required unless resultMapping is used.
columnIndexZero-based column index. Use instead of column for positional reading (faster, but order-sensitive).
typePHP type to coerce the value to: string, int, float, bool, date, or a class alias.
dbTypeDatabase type hint used during result reading.
nullValueValue to substitute when the column is NULL. The substituted value is set on the property; the property is not set to null.
typeHandlerName of a registered type handler class to use for this column.
selectID of a statement to execute for this property using the column value as the parameter (N+1 / association select).
column (multi)For N+1 selects with multiple key columns: "FK1=Alias1,FK2=Alias2". The alias names are passed as the parameter map to the sub-select.
resultMappingID of another <resultMap> to use for mapping a nested object from the same joined row (no extra query).
lazyLoadtrue to defer the sub-select (select attribute) until the property is first accessed. Default false.

<discriminator> attributes

AttributeDescription
columnColumn whose value determines which subMap to apply
typePHP type to coerce the discriminator value to before comparison
typeHandlerType handler to apply to the discriminator column value

<subMap> attributes

AttributeDescription
valueDiscriminator value that triggers this sub-map
resultMappingID of the <resultMap> to use when the discriminator matches

<parameterMap> — explicit parameter binding

Maps named positions in a ?-placeholder statement to object properties.

<parameterMapid="account-insert-params"class="App\Model\Account"extends="base-params">
<parameterproperty="firstName"dbType="VARCHAR"/>
<parameterproperty="lastName"dbType="VARCHAR"/>
<parameterproperty="emailAddress"dbType="VARCHAR"nullValue="no_email@provided.com"/>
<parameterproperty="active"dbType="TINYINT"type="bool"typeHandler="BoolHandler"/>
<parameterproperty="id"dbType="INTEGER"/>
</parameterMap>

<parameterMap> attributes

AttributeDescription
idUnique name within the namespace
classExpected PHP class of the parameter object (informational; not enforced)
extendsID of another <parameterMap> whose <parameter> elements are prepended

<parameter> attributes

AttributeDescription
propertyProperty name on the parameter object. Supports dotted paths: account.id.
columnColumn name hint (used for stored-procedure output mapping)
dbTypeDatabase type to bind as (e.g. VARCHAR, INTEGER, TINYINT)
typePHP type to coerce the property value to before binding
nullValueIf the property equals this value, bind NULL to the parameter instead
typeHandlerName of a registered type handler to convert the value
modeIN (default), OUT, or INOUT — for stored-procedure output parameters

Statement elements

Six element types declare SQL statements. All share a common set of attributes; some elements add their own.

Common statement attributes

AttributeDescription
idUnique statement ID within the namespace
parameterClassPHP class (or alias) of the parameter. Shortcuts: int, string, array, list, map, Hashtable.
parameterMapID of a <parameterMap> for ?-placeholder binding. Mutually exclusive with parameterClass inline parameters.
resultClassPHP class to instantiate per row without a <resultMap>. Alias columns in the SQL to match property names.
resultMapID of a <resultMap> for explicit column-to-property mapping
listClassPHP collection class to populate (default TList). Any class with an add($item) method works.
cacheModelID of a <cacheModel> to cache results (select statements only)
extendsID of another statement whose SQL is prepended to this one. The child adds clauses (e.g. WHERE, ORDER BY).

<select>

Executes a SELECT query. Returns one object, a list, a map, or a paged list depending on the gateway method called.

<selectid="GetAccount"parameterClass="int"resultMap="account-result"cacheModel="account-cache">
SELECT * FROM accounts WHERE account_id = #value#
</select>

<insert>

Executes an INSERT. Returns the generated key when <selectKey> is present, otherwise null.

<insertid="InsertAccount"parameterClass="App\Model\Account">
<selectKeyproperty="id"type="post"resultClass="int">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO accounts (first_name, last_name, email_address)
VALUES (#firstName#, #lastName#, #emailAddress#)
</insert>
<selectKey> attributes
AttributeDescription
propertyProperty on the parameter object to set with the generated key
typepre — execute the key query before the insert (e.g. sequence NEXTVAL). post — execute after the insert (e.g. LAST_INSERT_ID()).
resultClassPHP type of the returned key value (int, string, etc.)

<update>

Executes an UPDATE. Returns the number of affected rows.

<updateid="UpdateAccount"parameterClass="App\Model\Account">
UPDATE accounts
SET first_name = #firstName#, last_name = #lastName#
WHERE account_id = #id#
</update>

<delete>

Executes a DELETE. Returns the number of affected rows.

<deleteid="DeleteAccount"parameterClass="int">
DELETE FROM accounts WHERE account_id = #value#
</delete>

<statement>

Generic statement element — accepts any SQL and any result type. Use when the DML type does not fit <select>/<insert>/<update>/<delete>, or for legacy compatibility. Supports all common attributes.

<statementid="GetOrders"resultMap="order-result">
SELECT * FROM orders ORDER BY order_date DESC
</statement>

<procedure>

Calls a stored procedure. Requires a <parameterMap> for parameter binding. OUT and INOUT parameters are written back to the parameter object after execution.

<procedureid="SwapEmailAddresses"parameterMap="swap-params">
ps_swap_email_address
</procedure>

SQL substitution syntax

Three distinct substitution forms appear inside statement bodies.

${name} — global property substitution

Replaces ${name} with the value of a <property> declared in <properties> of the config file. Substitution happens at parse time (startup), not at query time.

<!-- Config file: <property name="schema" value="myapp"/> -->
SELECT * FROM ${schema}.accounts WHERE account_id = #value#

#property# — prepared-statement parameter

Replaces #property# with a ? placeholder and binds the value via PDO. This is the safe, injection-proof form for user-supplied values.

WHERE account_id =#id#

An inline parameter may carry modifiers separated by commas:

WHERE email =#emailAddress, dbType=VARCHAR, nullValue=no_email@provided.com#
ModifierDescription
dbType=XPDO type hint for binding
type=XPHP type to coerce the value to before binding
nullValue=XBind NULL when the property equals this value
typeHandler=XRegistered type handler class name to convert the value

For a list or array parameter, use #[]# (positional) or #propertyName[]# (named) inside <iterate>.

When the parameter is a scalar (int, string, etc.) rather than an object, use the special name value: #value#.

$property$ — literal string substitution

Replaces $property$ with the raw string value of the parameter property at query time, with no quoting or escaping. Use only for trusted values such as column names or SQL fragments.

ORDER BY $sortColumn$ $sortDirection$

Warning:$property$ is vulnerable to SQL injection when the value comes from user input. Never use it with untrusted data.

CDATA

Wrap SQL containing <, >, or & in a CDATA section to prevent XML parsing errors:

<selectid="GetFewAccounts"resultMap="account-result">
<![CDATA[ SELECT * FROM accounts WHERE account_id < #maxId# ]]>
</select>

extends — statement inheritance

A statement may extend another statement in the same namespace. The child's SQL is appended to the parent's SQL.

<selectid="GetAllAccounts"resultMap="account-result">
SELECT account_id, first_name, last_name, email_address
FROM accounts
</select>
<!-- Adds an ORDER BY clause to the base select -->
<selectid="GetAllAccountsByName"extends="GetAllAccounts"resultMap="account-result">
ORDER BY first_name
</select>
<!-- Adds a WHERE clause -->
<selectid="GetOneAccount"extends="GetAllAccounts"resultMap="account-result">
WHERE account_id = #value#
</select>

extends also works on <resultMap> (inherits <result> elements) and <parameterMap> (inherits <parameter> elements).


Dynamic SQL

Dynamic SQL tags conditionally include SQL fragments based on the parameter value at query time. They nest freely.

<selectid="SearchAccounts"resultMap="account-result"parameterClass="App\Model\Account">
SELECT account_id, first_name, last_name, email_address
FROM accounts
<dynamicprepend="WHERE">
<isGreaterThanprepend="AND"property="id"compareValue="0">
account_id = #id#
</isGreaterThan>
<isNotEmptyprepend="AND"property="firstName">
first_name = #firstName#
</isNotEmpty>
<isNotEmptyprepend="AND"property="lastName">
last_name = #lastName#
</isNotEmpty>
<isNotNullprepend="AND"property="ids">
account_id IN
<iterateproperty="ids"open="("close=")"conjunction=",">
#ids[]#
</iterate>
</isNotNull>
</dynamic>
ORDER BY last_name
</select>

<dynamic>

A wrapper that emits its prepend string only when at least one child emits content. Without <dynamic>, each child's prepend is always emitted (even when that child is the first to emit content).

AttributeDescription
prependSQL text prepended when any child emits content (e.g. WHERE, AND)

Conditional tags

All conditional tags share these attributes:

AttributeApplies toDescription
prependAllSQL text prepended to this tag's content when it emits (stripped from the first tag that emits inside a <dynamic>)
propertyMostProperty name on the parameter object to test. Omit when the parameter itself is the value being tested.
compareValueComparison tagsLiteral value to compare against
TagCondition
<isNull>Property is null
<isNotNull>Property is not null
<isEmpty>Property is null, empty string, or empty collection
<isNotEmpty>Property is not null and not empty
<isEqual>Property equals compareValue
<isNotEqual>Property does not equal compareValue
<isGreaterThan>Property is greater than compareValue (numeric)
<isGreaterEqual>Property is greater than or equal to compareValue (numeric)
<isLessThan>Property is less than compareValue (numeric)
<isLessEqual>Property is less than or equal to compareValue (numeric)
<isParameterPresent>A parameter was passed (not null)
<isPropertyAvailable>The named property exists on the parameter object

<iterate>

Iterates over an array or list property, emitting the body once per element.

<iterateproperty="ids"open="("close=")"conjunction=",">
#ids[]#
</iterate>

For a list parameter (not a property of an object), omit property and use #[]#:

<iterateopen="("close=")"conjunction=",">
#[]#
</iterate>
AttributeDescription
propertyProperty name holding the list/array. Omit when the parameter itself is the list.
openSQL text emitted once before the first element (e.g. ()
closeSQL text emitted once after the last element (e.g. ))
conjunctionSQL text emitted between elements (e.g. ,, OR)

N+1 select — loading associations

Use select on a <result> to load an associated object or collection with a second query.

<resultMapid="order-result"class="App\Model\Order">
<resultproperty="id"column="order_id"/>
<resultproperty="lineItems"column="order_id"select="GetLineItemsForOrder"lazyLoad="true"/>
</resultMap>
<selectid="GetLineItemsForOrder"parameterClass="int"resultMap="line-item-result">
SELECT * FROM line_items WHERE order_id = #value#
</select>

When the association requires multiple key columns, list them as "ColumnName=ParameterAlias" pairs:

<resultproperty="item"column="order_id=Order_ID,fav_item_id=LineItem_ID"select="GetSpecificLineItem"/>

The sub-select receives a parameter map with keys Order_ID and LineItem_ID.

Set lazyLoad="true" to defer the sub-select until the property is first accessed. The property is then a TLazyLoadList proxy.


Joined result mapping

Use resultMapping on a <result> to map a nested object from columns already present in the same joined row — no additional query.

<resultMapid="order-with-address"class="App\Model\Order">
<resultproperty="id"column="order_id"/>
<resultproperty="address"resultMapping="address-result"/>
</resultMap>
<resultMapid="address-result"class="App\Model\Address">
<resultproperty="street"column="addr_street"/>
<resultproperty="city"column="addr_city"/>
</resultMap>

Alternatively, use dotted property paths in <result property="..."> to write directly into nested objects without a separate result map:

<resultMapid="order-result"class="App\Model\Order">
<resultproperty="id"column="order_id"/>
<resultproperty="favouriteItem.id"column="line_item_id"/>
<resultproperty="favouriteItem.code"column="line_item_code"/>
<resultproperty="favouriteItem.price"column="line_item_price"/>
</resultMap>

GroupBy — nested object trees

groupBy collapses repeated parent rows from a JOIN into a single parent instance with a list child property. The value is the parent result property (or comma-separated list of properties) that identifies a unique parent row.

<resultMapid="account-with-orders"class="App\Model\Account"groupBy="id">
<resultproperty="id"column="account_id"/>
<resultproperty="firstName"column="account_first_name"/>
<resultproperty="orders"resultMapping="order-result"/>
</resultMap>
<resultMapid="order-result"class="App\Model\Order">
<resultproperty="id"column="order_id"/>
<resultproperty="date"column="order_date"type="date"/>
</resultMap>
<selectid="GetAccountWithOrders"resultMap="account-with-orders">
SELECT a.account_id, a.account_first_name, o.order_id, o.order_date
FROM accounts a
LEFT JOIN orders o ON a.account_id = o.account_id
</select>

Each distinct account_id value produces one Account object; all matching Order rows are collected into its orders property.


Polymorphic result maps

<discriminator> selects a different result map based on a column value. The discriminator and its <subMap> elements appear inside the base <resultMap>.

<resultMapid="document-result"class="App\Model\Document">
<resultproperty="id"column="doc_id"/>
<resultproperty="title"column="doc_title"/>
<discriminatorcolumn="doc_type"type="string">
<subMapvalue="Book"resultMapping="book-result"/>
<subMapvalue="Newspaper"resultMapping="newspaper-result"/>
</discriminator>
</resultMap>
<resultMapid="book-result"class="App\Model\Book"extends="document-result">
<resultproperty="pageCount"column="doc_page_count"/>
</resultMap>
<resultMapid="newspaper-result"class="App\Model\Newspaper"extends="document-result">
<resultproperty="city"column="doc_city"/>
</resultMap>

The sub-result maps extend the base to inherit its <result> elements. A custom typeHandler on <discriminator> lets the handler translate the raw column value to the string that matches a <subMap value="...">.


Gateway API

Retrieve the gateway from the module:

$sqlmap = Prado::getApplication()->getModule('sqlmap')->getClient();

Query methods

// Single object — returns null if not found$account = $sqlmap->queryForObject('GetAccount', $id);
// Single object into a pre-created instance$account = $sqlmap->queryForObject('GetAccount', $id, newAccount());
// List (TList)$accounts = $sqlmap->queryForList('GetAllAccounts');
// List with offset/limit$accounts = $sqlmap->queryForList('GetAllAccounts', null, null, $skip, $max);
// Paged list (TSqlMapPagedList)$paged = $sqlmap->queryForPagedList('GetAllAccounts', null, $pageSize);
$paged->gotoPage(2);
// Map keyed by a property$map = $sqlmap->queryForMap('GetAllAccounts', null, 'id');
// Map keyed by one property, values from another$map = $sqlmap->queryForMap('GetAllAccounts', null, 'id', 'emailAddress');
// Row delegate — callback fired per row; use to build custom collections$sqlmap->queryWithRowDelegate(
'GetAllAccounts',
function ($sqlmap, $object, &$list) {
$list[] = $object;
}
);

Mutation methods

// Insert — returns the generated key (from <selectKey>) or null$newId = $sqlmap->insert('InsertAccount', $account);
// Update — returns affected row count$count = $sqlmap->update('UpdateAccount', $account);
// Delete — returns affected row count$count = $sqlmap->delete('DeleteAccount', $id);

Cache

// Flush all cache models declared in the SqlMap config$sqlmap->flushCaches();

Type Handlers

A type handler controls how a PHP value is converted to and from a database column value.

Implement TSqlMapTypeHandler:

usePrado\Data\SqlMap\DataMapper\TSqlMapTypeHandler;
class BoolHandler extends TSqlMapTypeHandler
{
publicfunctiongetResult($value): bool { return (bool)(int)$value; }
publicfunctiongetParameter($value): int { return$value ? 1 : 0; }
publicfunctioncreateNewInstance($data = null): bool { returnfalse; }
}

Register in PHP:

$sqlmap->registerTypeHandler(newBoolHandler());

Or in sqlmap.xml:

<typeHandlers>
<typeHandlerclass="App\SqlMap\BoolHandler"dbType="TINYINT"/>
</typeHandlers>

Development

Setup

composer install

Commands

CommandDescription
composer fixApply cs-fixer style fixes to src/
composer stanPHPStan static analysis
composer testPHPUnit unit suite (no database required)
composer unittestAlias for composer test
composer dbtestUnit + SQLite, MySQL, PostgreSQL, and Firebird in one PHPUnit run
composer fulltestPre-commit check: php -l + cs-fixer dry-run + phpstan + unit tests

Run composer fix before composer fulltest when the style check fails — fulltest runs cs-fixer in dry-run (check-only) mode.

Running a single driver suite

vendor/bin/phpunit --testsuite db-sqlite
vendor/bin/phpunit --testsuite db-mysql
vendor/bin/phpunit --testsuite db-pgsql
vendor/bin/phpunit --testsuite db-firebird
vendor/bin/phpunit --testsuite db-sqlsrv
vendor/bin/phpunit --testsuite db-oracle
vendor/bin/phpunit --testsuite db-ibm

Filtering to one test class or method

vendor/bin/phpunit --testsuite unit --filter TInlineParameterMapParserTest
vendor/bin/phpunit --testsuite db-sqlite --filter testQueryForObject

Database test setup

Each driver suite reads its connection from tests/unit/Data/SqlMap/common.php. Set the DSN, username, and password for your driver in the matching *BaseTestConfig class. SQLite requires no setup — it uses the bundled database files in tests/unit/Data/SqlMap/sqlite/.

The schema init scripts for each driver are in tests/unit/Data/SqlMap/scripts/.

Pre-commit checklist

Run composer fulltest — all four steps must pass before committing:

  1. find src -name '*.php' | xargs php -l — syntax check
  2. vendor/bin/php-cs-fixer fix --dry-run src/ — style check
  3. vendor/bin/phpstan analyse --memory-limit=512M — static analysis
  4. vendor/bin/phpunit --testsuite unit — unit tests

Adding a new database driver

  1. Add a <Driver>BaseTestConfig class to tests/unit/Data/SqlMap/common.php following the existing pattern (getConnection(), getSqlMapConfigFile(), getScriptDir(), getScriptRunner(), hasFeature()).
  2. Create tests/unit/Data/SqlMap/DbSpecific/<Driver>/.
  3. For each abstract base class in tests/unit/Data/SqlMap/, create a concrete wrapper:
    <?phprequire_once(__DIR__ . '/../../StatementTest.php');
    class <Driver>StatementTest extends StatementTest
    {
    protectedstaticstring$configClass = '<Driver>BaseTestConfig';
    }
  4. Add a <testsuite name="db-<driver>"> entry to phpunit.xml.
  5. Add SQL schema scripts to tests/unit/Data/SqlMap/scripts/<driver>/.
  6. Add a SqlMap config file at tests/unit/Data/SqlMap/maps/<driver>/sqlmap.xml.

Adding a new DB-dependent test method

Add the abstract test method to the relevant base class in tests/unit/Data/SqlMap/ (e.g., StatementTest.php). All driver wrappers under DbSpecific/ inherit it automatically.

Architecture

src/Data/SqlMap/
├── TSqlMapConfig.php — PRADO module; bootstraps the extension
├── TSqlMapGateway.php — public API: queryForObject, insert, update, delete, …
├── TSqlMapManager.php — holds parsed config, connection, type handlers
├── Configuration/ — XML parsing and in-memory model
│ ├── TSqlMapXmlConfiguration.php
│ ├── TSqlMapXmlConfigBuilder.php
│ ├── TSqlMapXmlMappingConfiguration.php
│ ├── TSqlMapStatement.php / TSqlMapSelect.php / TSqlMapInsert.php / …
│ ├── TParameterMap.php / TParameterProperty.php
│ ├── TResultMap.php / TResultProperty.php
│ ├── TDiscriminator.php / TSubMap.php
│ ├── TSqlMapCacheModel.php / TSqlMapCacheKey.php / TSqlMapCacheTypes.php
│ ├── TSqlMapSelectKey.php
│ ├── TInlineParameterMapParser.php
│ └── TSimpleDynamicParser.php
├── DataMapper/ — runtime helpers, type system, cache implementations
│ ├── TSqlMapTypeHandler.php / TSqlMapTypeHandlerRegistry.php
│ ├── TSqlMapFifoCache.php / TSqlMapLruCache.php / TSqlMapApplicationCache.php
│ ├── TSqlMapCache.php / TSqlMapPagedList.php
│ ├── TPropertyAccess.php / TObjectProxy.php / TLazyLoadList.php
│ └── T*Exception.php
└── Statements/ — statement execution engine
├── TMappedStatement.php
├── TSelectMappedStatement.php / TInsertMappedStatement.php / …
├── TCachingStatement.php
├── TPreparedCommand.php / TPreparedStatement.php / TPreparedStatementFactory.php
├── TSimpleDynamicSql.php / TStaticSql.php
├── TSqlMapObjectCollectionTree.php
└── TPostSelectBinding.php / TResultSet*.php

Execution flow

TSqlMapGateway::queryForObject($id, $param)TSqlMapManager::getMappedStatement($id)TMappedStatement::executeQueryForObject($conn, $param)TPreparedCommand::create($conn, $statement, $param) → PDO execute → result row → TResultMap property mapping → returned object

License

BSD-3-Clause. See LICENSE.

Authors

  • Wei Zhuo — original SqlMap implementation
  • Fabio Bas — PRADO 4 maintenance
  • Brad Anderson — extension extraction and maintenance

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages