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.
- PHP 8.1 or later
- PRADO 4.3.3 or later (
pradosoft/prado) - PDO extension and a PDO driver for your database
Within a PRADO Application:
composer require pradosoft/prado-sqlmapAny database with a PDO driver. The test suite covers:
| Driver | PDO DSN prefix |
|---|---|
| MySQL / MariaDB | mysql: |
| PostgreSQL | pgsql: |
| SQLite | sqlite: |
| Firebird | firebird: |
| SQL Server | sqlsrv: |
| Oracle | oci: |
| IBM DB2 | ibm: |
Register pradosoft/prado-sqlmap or TSqlMapConfig as an application module in your PRADO application.xml (or application.php).
Reference a TDataSourceConfig module by its id:
<modules>
<module id="db" class="System.Data.TDataSourceConfig">
<database ConnectionString="mysql:host=localhost;dbname=myapp"
Username="user" Password="secret"/>
</module>
<module id="sqlmap" class="Prado\Data\SqlMap\TSqlMapConfig"
ConnectionID="db"
ConfigFile="Application.SqlMap.sqlmap"/>
</modules>Embed the <database> element directly inside TSqlMapConfig:
<modules>
<module id="sqlmap" class="Prado\Data\SqlMap\TSqlMapConfig"
ConfigFile="Application.SqlMap.sqlmap">
<database ConnectionString="sqlite:/path/to/app.db"/>
</module>
</modules>| Property | Type | Description |
|---|---|---|
ConfigFile |
string | Dot-notation path to the SqlMap XML config file |
ConnectionID |
string | ID of a TDataSourceConfig module to share its connection |
EnableCache |
bool | Cache the parsed manager in the PRADO application cache (default false) |
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.
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>
<property name="selectKey" value="SELECT LAST_INSERT_ID()"/>
<property name="schema" value="myapp"/>
</properties>
<!-- 2. Custom PHP type handlers -->
<typeHandlers>
<typeHandler class="App\SqlMap\BoolHandler" dbType="TINYINT"/>
<typeHandler class="App\SqlMap\MoneyHandler" dbType="DECIMAL" type="float"/>
</typeHandlers>
<!-- 3. Map files — paths resolved relative to this config file -->
<sqlMaps>
<sqlMap resource="Account.xml"/>
<sqlMap resource="Order.xml"/>
</sqlMaps>
</sqlMapConfig>| Attribute | Required | Description |
|---|---|---|
class |
Yes | Fully-qualified PHP class name implementing TSqlMapTypeHandler |
dbType |
No | Database column type this handler applies to (e.g. TINYINT, VARCHAR) |
type |
No | PHP type this handler applies to (e.g. bool, float) |
| Attribute | Description |
|---|---|
resource |
Path to a map file, relative to the config file directory |
<sqlMap namespace="Account">
<!-- cacheModel, alias, resultMap, parameterMap, and statement elements -->
</sqlMap>| Attribute | Description |
|---|---|
namespace |
Optional 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.
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>
<typeAlias alias="Account" type="App\Model\Account"/>
</alias>| Attribute | Description |
|---|---|
alias |
Short name usable in class, resultClass, parameterClass, and type attributes |
type |
Fully-qualified PHP class name |
Declares a named cache that <select> statements can reference.
<cacheModel id="account-cache" implementation="LRU" readOnly="true" serialize="false">
<flushInterval hours="1" minutes="30" seconds="0"/>
<flushOnExecute statement="UpdateAccount"/>
<flushOnExecute statement="DeleteAccount"/>
<property name="size" value="100"/>
</cacheModel>| Attribute | Default | Description |
|---|---|---|
id |
— | Unique name for this cache model within the file |
implementation |
— | Cache type: LRU, FIFO, MEMORY, or a fully-qualified class name |
readOnly |
true |
When true, all callers receive the same cached object (no defensive copy). Set false to let callers mutate their copy safely. |
serialize |
false |
Serialize objects before storing. Useful when readOnly="false" to guarantee isolation. |
| Element | Attributes | Description |
|---|---|---|
<flushInterval> |
hours, minutes, seconds |
Invalidate the cache on a time interval. All three attributes are optional and additive. |
<flushOnExecute> |
statement |
Flush this cache whenever the named statement executes (insert/update/delete). Multiple elements are allowed. |
<property> |
name, value |
Implementation-specific setting. LRU and FIFO accept size (maximum entry count). |
| Type | Eviction | size property |
|---|---|---|
LRU |
Least-recently-used | Required |
FIFO |
First-in, first-out | Required |
MEMORY |
None (unbounded) | Ignored |
Maps result-set columns to PHP object properties or array keys.
<resultMap id="account-result" class="App\Model\Account" extends="base-result" groupBy="id">
<result property="id" column="account_id"/>
<result property="firstName" column="account_first_name"/>
<result property="emailAddress" column="account_email" nullValue=""/>
<result property="role" column="account_role" type="int"/>
<result property="active" column="account_active" typeHandler="BoolHandler"/>
<result property="lineItems" column="order_id" select="GetLineItemsForOrder" lazyLoad="true"/>
<result property="address" resultMapping="address-result"/>
<discriminator column="doc_type" type="string">
<subMap value="Book" resultMapping="book-result"/>
<subMap value="Newspaper" resultMapping="newspaper-result"/>
</discriminator>
</resultMap>| Attribute | Description |
|---|---|
id |
Unique name within the namespace |
class |
PHP class name (or alias) to instantiate per row. Use array for associative arrays or string/int/float for scalar results. |
extends |
ID of another <resultMap> whose <result> elements are inherited. Allows sharing a base mapping. |
groupBy |
Property name (or comma-separated list) used to collapse repeated parent rows into one object with a child list. See GroupBy. |
| Attribute | Description |
|---|---|
property |
PHP object property or array key to set. Supports dotted paths: favouriteItem.id sets $obj->getFavouriteItem()->setId(...). |
column |
Result-set column name to read. Required unless resultMapping is used. |
columnIndex |
Zero-based column index. Use instead of column for positional reading (faster, but order-sensitive). |
type |
PHP type to coerce the value to: string, int, float, bool, date, or a class alias. |
dbType |
Database type hint used during result reading. |
nullValue |
Value to substitute when the column is NULL. The substituted value is set on the property; the property is not set to null. |
typeHandler |
Name of a registered type handler class to use for this column. |
select |
ID 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. |
resultMapping |
ID of another <resultMap> to use for mapping a nested object from the same joined row (no extra query). |
lazyLoad |
true to defer the sub-select (select attribute) until the property is first accessed. Default false. |
| Attribute | Description |
|---|---|
column |
Column whose value determines which subMap to apply |
type |
PHP type to coerce the discriminator value to before comparison |
typeHandler |
Type handler to apply to the discriminator column value |
| Attribute | Description |
|---|---|
value |
Discriminator value that triggers this sub-map |
resultMapping |
ID of the <resultMap> to use when the discriminator matches |
Maps named positions in a ?-placeholder statement to object properties.
<parameterMap id="account-insert-params" class="App\Model\Account" extends="base-params">
<parameter property="firstName" dbType="VARCHAR"/>
<parameter property="lastName" dbType="VARCHAR"/>
<parameter property="emailAddress" dbType="VARCHAR" nullValue="no_email@provided.com"/>
<parameter property="active" dbType="TINYINT" type="bool" typeHandler="BoolHandler"/>
<parameter property="id" dbType="INTEGER"/>
</parameterMap>| Attribute | Description |
|---|---|
id |
Unique name within the namespace |
class |
Expected PHP class of the parameter object (informational; not enforced) |
extends |
ID of another <parameterMap> whose <parameter> elements are prepended |
| Attribute | Description |
|---|---|
property |
Property name on the parameter object. Supports dotted paths: account.id. |
column |
Column name hint (used for stored-procedure output mapping) |
dbType |
Database type to bind as (e.g. VARCHAR, INTEGER, TINYINT) |
type |
PHP type to coerce the property value to before binding |
nullValue |
If the property equals this value, bind NULL to the parameter instead |
typeHandler |
Name of a registered type handler to convert the value |
mode |
IN (default), OUT, or INOUT — for stored-procedure output parameters |
Six element types declare SQL statements. All share a common set of attributes; some elements add their own.
| Attribute | Description |
|---|---|
id |
Unique statement ID within the namespace |
parameterClass |
PHP class (or alias) of the parameter. Shortcuts: int, string, array, list, map, Hashtable. |
parameterMap |
ID of a <parameterMap> for ?-placeholder binding. Mutually exclusive with parameterClass inline parameters. |
resultClass |
PHP class to instantiate per row without a <resultMap>. Alias columns in the SQL to match property names. |
resultMap |
ID of a <resultMap> for explicit column-to-property mapping |
listClass |
PHP collection class to populate (default TList). Any class with an add($item) method works. |
cacheModel |
ID of a <cacheModel> to cache results (select statements only) |
extends |
ID of another statement whose SQL is prepended to this one. The child adds clauses (e.g. WHERE, ORDER BY). |
Executes a SELECT query. Returns one object, a list, a map, or a paged list depending on the gateway method called.
<select id="GetAccount"
parameterClass="int"
resultMap="account-result"
cacheModel="account-cache">
SELECT * FROM accounts WHERE account_id = #value#
</select>Executes an INSERT. Returns the generated key when <selectKey> is present, otherwise null.
<insert id="InsertAccount" parameterClass="App\Model\Account">
<selectKey property="id" type="post" resultClass="int">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO accounts (first_name, last_name, email_address)
VALUES (#firstName#, #lastName#, #emailAddress#)
</insert>| Attribute | Description |
|---|---|
property |
Property on the parameter object to set with the generated key |
type |
pre — execute the key query before the insert (e.g. sequence NEXTVAL). post — execute after the insert (e.g. LAST_INSERT_ID()). |
resultClass |
PHP type of the returned key value (int, string, etc.) |
Executes an UPDATE. Returns the number of affected rows.
<update id="UpdateAccount" parameterClass="App\Model\Account">
UPDATE accounts
SET first_name = #firstName#, last_name = #lastName#
WHERE account_id = #id#
</update>Executes a DELETE. Returns the number of affected rows.
<delete id="DeleteAccount" parameterClass="int">
DELETE FROM accounts WHERE account_id = #value#
</delete>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.
<statement id="GetOrders" resultMap="order-result">
SELECT * FROM orders ORDER BY order_date DESC
</statement>Calls a stored procedure. Requires a <parameterMap> for parameter binding. OUT and INOUT parameters are written back to the parameter object after execution.
<procedure id="SwapEmailAddresses" parameterMap="swap-params">
ps_swap_email_address
</procedure>Three distinct substitution forms appear inside statement bodies.
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#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#| Modifier | Description |
|---|---|
dbType=X |
PDO type hint for binding |
type=X |
PHP type to coerce the value to before binding |
nullValue=X |
Bind NULL when the property equals this value |
typeHandler=X |
Registered 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#.
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.
Wrap SQL containing <, >, or & in a CDATA section to prevent XML parsing errors:
<select id="GetFewAccounts" resultMap="account-result">
<![CDATA[ SELECT * FROM accounts WHERE account_id < #maxId# ]]>
</select>A statement may extend another statement in the same namespace. The child's SQL is appended to the parent's SQL.
<select id="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 -->
<select id="GetAllAccountsByName" extends="GetAllAccounts" resultMap="account-result">
ORDER BY first_name
</select>
<!-- Adds a WHERE clause -->
<select id="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 tags conditionally include SQL fragments based on the parameter value at query time. They nest freely.
<select id="SearchAccounts" resultMap="account-result" parameterClass="App\Model\Account">
SELECT account_id, first_name, last_name, email_address
FROM accounts
<dynamic prepend="WHERE">
<isGreaterThan prepend="AND" property="id" compareValue="0">
account_id = #id#
</isGreaterThan>
<isNotEmpty prepend="AND" property="firstName">
first_name = #firstName#
</isNotEmpty>
<isNotEmpty prepend="AND" property="lastName">
last_name = #lastName#
</isNotEmpty>
<isNotNull prepend="AND" property="ids">
account_id IN
<iterate property="ids" open="(" close=")" conjunction=",">
#ids[]#
</iterate>
</isNotNull>
</dynamic>
ORDER BY last_name
</select>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).
| Attribute | Description |
|---|---|
prepend |
SQL text prepended when any child emits content (e.g. WHERE, AND) |
All conditional tags share these attributes:
| Attribute | Applies to | Description |
|---|---|---|
prepend |
All | SQL text prepended to this tag's content when it emits (stripped from the first tag that emits inside a <dynamic>) |
property |
Most | Property name on the parameter object to test. Omit when the parameter itself is the value being tested. |
compareValue |
Comparison tags | Literal value to compare against |
| Tag | Condition |
|---|---|
<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 |
Iterates over an array or list property, emitting the body once per element.
<iterate property="ids" open="(" close=")" conjunction=",">
#ids[]#
</iterate>For a list parameter (not a property of an object), omit property and use #[]#:
<iterate open="(" close=")" conjunction=",">
#[]#
</iterate>| Attribute | Description |
|---|---|
property |
Property name holding the list/array. Omit when the parameter itself is the list. |
open |
SQL text emitted once before the first element (e.g. () |
close |
SQL text emitted once after the last element (e.g. )) |
conjunction |
SQL text emitted between elements (e.g. ,, OR) |
Use select on a <result> to load an associated object or collection with a second query.
<resultMap id="order-result" class="App\Model\Order">
<result property="id" column="order_id"/>
<result property="lineItems" column="order_id"
select="GetLineItemsForOrder" lazyLoad="true"/>
</resultMap>
<select id="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:
<result property="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.
Use resultMapping on a <result> to map a nested object from columns already present in the same joined row — no additional query.
<resultMap id="order-with-address" class="App\Model\Order">
<result property="id" column="order_id"/>
<result property="address" resultMapping="address-result"/>
</resultMap>
<resultMap id="address-result" class="App\Model\Address">
<result property="street" column="addr_street"/>
<result property="city" column="addr_city"/>
</resultMap>Alternatively, use dotted property paths in <result property="..."> to write directly into nested objects without a separate result map:
<resultMap id="order-result" class="App\Model\Order">
<result property="id" column="order_id"/>
<result property="favouriteItem.id" column="line_item_id"/>
<result property="favouriteItem.code" column="line_item_code"/>
<result property="favouriteItem.price" column="line_item_price"/>
</resultMap>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.
<resultMap id="account-with-orders" class="App\Model\Account" groupBy="id">
<result property="id" column="account_id"/>
<result property="firstName" column="account_first_name"/>
<result property="orders" resultMapping="order-result"/>
</resultMap>
<resultMap id="order-result" class="App\Model\Order">
<result property="id" column="order_id"/>
<result property="date" column="order_date" type="date"/>
</resultMap>
<select id="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.
<discriminator> selects a different result map based on a column value. The discriminator and its <subMap> elements appear inside the base <resultMap>.
<resultMap id="document-result" class="App\Model\Document">
<result property="id" column="doc_id"/>
<result property="title" column="doc_title"/>
<discriminator column="doc_type" type="string">
<subMap value="Book" resultMapping="book-result"/>
<subMap value="Newspaper" resultMapping="newspaper-result"/>
</discriminator>
</resultMap>
<resultMap id="book-result" class="App\Model\Book" extends="document-result">
<result property="pageCount" column="doc_page_count"/>
</resultMap>
<resultMap id="newspaper-result" class="App\Model\Newspaper" extends="document-result">
<result property="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="...">.
Retrieve the gateway from the module:
$sqlmap = Prado::getApplication()->getModule('sqlmap')->getClient();// Single object — returns null if not found
$account = $sqlmap->queryForObject('GetAccount', $id);
// Single object into a pre-created instance
$account = $sqlmap->queryForObject('GetAccount', $id, new Account());
// 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;
}
);// 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);// Flush all cache models declared in the SqlMap config
$sqlmap->flushCaches();A type handler controls how a PHP value is converted to and from a database column value.
Implement TSqlMapTypeHandler:
use Prado\Data\SqlMap\DataMapper\TSqlMapTypeHandler;
class BoolHandler extends TSqlMapTypeHandler
{
public function getResult($value): bool { return (bool)(int)$value; }
public function getParameter($value): int { return $value ? 1 : 0; }
public function createNewInstance($data = null): bool { return false; }
}Register in PHP:
$sqlmap->registerTypeHandler(new BoolHandler());Or in sqlmap.xml:
<typeHandlers>
<typeHandler class="App\SqlMap\BoolHandler" dbType="TINYINT"/>
</typeHandlers>composer install| Command | Description |
|---|---|
composer fix |
Apply cs-fixer style fixes to src/ |
composer stan |
PHPStan static analysis |
composer test |
PHPUnit unit suite (no database required) |
composer unittest |
Alias for composer test |
composer dbtest |
Unit + SQLite, MySQL, PostgreSQL, and Firebird in one PHPUnit run |
composer fulltest |
Pre-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.
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-ibmvendor/bin/phpunit --testsuite unit --filter TInlineParameterMapParserTest
vendor/bin/phpunit --testsuite db-sqlite --filter testQueryForObjectEach 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/.
Run composer fulltest — all four steps must pass before committing:
find src -name '*.php' | xargs php -l— syntax checkvendor/bin/php-cs-fixer fix --dry-run src/— style checkvendor/bin/phpstan analyse --memory-limit=512M— static analysisvendor/bin/phpunit --testsuite unit— unit tests
- Add a
<Driver>BaseTestConfigclass totests/unit/Data/SqlMap/common.phpfollowing the existing pattern (getConnection(),getSqlMapConfigFile(),getScriptDir(),getScriptRunner(),hasFeature()). - Create
tests/unit/Data/SqlMap/DbSpecific/<Driver>/. - For each abstract base class in
tests/unit/Data/SqlMap/, create a concrete wrapper:<?php require_once(__DIR__ . '/../../StatementTest.php'); class <Driver>StatementTest extends StatementTest { protected static string $configClass = '<Driver>BaseTestConfig'; }
- Add a
<testsuite name="db-<driver>">entry tophpunit.xml. - Add SQL schema scripts to
tests/unit/Data/SqlMap/scripts/<driver>/. - Add a SqlMap config file at
tests/unit/Data/SqlMap/maps/<driver>/sqlmap.xml.
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.
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
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
BSD-3-Clause. See LICENSE.
- Wei Zhuo — original SqlMap implementation
- Fabio Bas — PRADO 4 maintenance
- Brad Anderson — extension extraction and maintenance