Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
cde64bb
Fixes PradoUnit::processException to show the first Db Error Message.
belisoful Apr 23, 2026
0ab3f76
Oracle Fix
belisoful Apr 23, 2026
9310aac
PradoUnit correction and PDO Driver Names.
belisoful Apr 23, 2026
6805cd6
Github CI php matrix removes firebird and sqlsrv as default on.
belisoful Apr 23, 2026
94ffff0
using Firebird Casting for column names.
belisoful Apr 23, 2026
4e4bfc6
fixed the string search for specifics
belisoful Apr 23, 2026
6e65cc8
Encoding Firebird Transaction rules.
belisoful Apr 23, 2026
984569d
Centralized PDO Database Capabilities.
belisoful Apr 24, 2026
27105d6
Adds Data.Common interfaces.
belisoful Apr 30, 2026
2942072
Merge branch 'pradosoft:master' into db-modern-exec
belisoful Apr 30, 2026
657b6c0
Made the conditions of serial transactions more accurate.
belisoful Apr 30, 2026
d20dcef
Updated TDbDriverCapabilities.
belisoful Apr 30, 2026
ec593c3
removed serial transactions, replaced by TDbTransaction::beginTransac…
belisoful May 1, 2026
d1fb0ca
Updated DbSpecific integration tests.
belisoful May 1, 2026
48e6187
Updated unit tests for firebird, ibm, oracle, and sqlite.
belisoful May 2, 2026
43c5b20
sqlite updated integration tests
belisoful May 2, 2026
700524f
updated oracle and sqlite code.
belisoful May 2, 2026
870e060
Oracle unit test update
belisoful May 2, 2026
6b7c604
Oracle DB Fixes
belisoful May 3, 2026
020b851
Better PHP class doc blocks for Data.Common.*
belisoful May 4, 2026
4ce7d14
TDbCommand refactoring, corrects TOracleDbCommand
belisoful May 4, 2026
92f03b4
TDbConnection::_getZappableSleepProps update
belisoful May 4, 2026
21811f1
sqlite fix
belisoful May 4, 2026
8eecac2
removed unnecessary method
belisoful May 4, 2026
3f98922
TSqlCriteria docblock space reversion
belisoful May 4, 2026
fde43d7
removed getDbMetaData from TDbTransacion, it was a prototype
belisoful May 4, 2026
15dffb7
integrates fxActiveRecordCreateScaffoldInput into TDbDriverCapabiliti…
belisoful May 4, 2026
49da2f3
fxActiveRecordCreateScaffoldInput changed to returning a class, not a…
belisoful May 4, 2026
1d713a3
Adding missing Exception messages.
belisoful May 4, 2026
604d6c5
TDbCommand and TDbConnection adding at-since to _getZappableSleepProps.
belisoful May 4, 2026
6e1ebda
TDbDataReader using accessors for properties.
belisoful May 4, 2026
f796cc3
fxActiveRecordCreateScaffoldInput => fxActiveRecordScaffoldInputClass
belisoful May 4, 2026
5bec299
removes protected resolveCharsetForDriver
belisoful May 4, 2026
b785c3f
TDbCommandBuilder::requireActiveTransaction moved to assertActiveTran…
belisoful May 4, 2026
b8c094d
Fix for Windows retaining sqlite test files
belisoful May 5, 2026
72e4215
Mssql is converted to the more appropriate SqlSrv; matching the MS ‘S…
belisoful May 5, 2026
92be36a
Update TTableGateway::getTableExists uses proper Exception.
belisoful May 5, 2026
f03f938
correcting yml for sqlsrv
belisoful May 5, 2026
d375f01
Merge branch 'pradosoft:master' into db-modern-exec
belisoful May 5, 2026
c0e64c9
Merge commit 'd375f01e3c1067fd6c8eae9d986a3bef73ce0107'
belisoful May 5, 2026
8239ebc
Fixing Windows pgsql user password due to Windows default config bein…
belisoful May 5, 2026
26c6d61
removed original TSqliteCommandBuilder that was used for comparison d…
belisoful May 5, 2026
57a7799
TDbTransaction reorganization
belisoful May 5, 2026
b1d46a7
standardized the php class doc blocks.
belisoful May 5, 2026
d36d177
more standardization of php class doc blocks.
belisoful May 5, 2026
c1dbf1e
Doc Block update
belisoful May 5, 2026
c6dbf0f
Adds TDbDriver::EXTENSION_MYSQLI and EXTENSION_MSSQL for driver speci…
belisoful May 6, 2026
0b5d6b8
Using the Interfaces rather than the TDb classes for abstraction thro…
belisoful May 6, 2026
ffd3c66
Restrictions on dsn charsets (sqlsrv)
belisoful May 6, 2026
d7e1636
upgrade Data classes __sleep to _getZappableSleepProps
belisoful May 6, 2026
6da956c
Differentiating IDataColumn from IDbColumn for PDO.
belisoful May 8, 2026
e63f678
TDataCharset is IANA compliant
belisoful May 8, 2026
4b1ce3a
Standardized the two ‘fx’ global events for custom Data classes
belisoful May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/prado.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php
with:
php-version: ${{ matrix.php-versions }}
extensions: ctype, dom, intl, json, mbstring, memcached, pdo_mysql, pdo_pgsql, pdo_sqlite, openssl, pcre, spl, zlib
extensions: ctype, dom, intl, json, mbstring, memcached, pdo_mysql, pdo_pgsql, pdo_sqlite, openssl, pcre, spl, zlib, :pdo_firebird, :pdo_sqlsrv
tools: php-cs-fixer, phpstan, cs2pr

- name: Validate composer.json and composer.lock
Expand Down Expand Up @@ -219,11 +219,14 @@ jobs:
& "$env:PGBIN\pg_ctl.exe" start -D $env:PGDATA -w
& "$env:PGBIN\createdb.exe" -h 127.0.0.1 -U postgres prado_unitest
& "$env:PGBIN\psql.exe" -h 127.0.0.1 -U postgres prado_unitest -f .\tests\initdb_pgsql.sql
# Windows PostgreSQL uses scram-sha-256 (no trust); set the password so
# setupPgsqlConnection('prado_unitest', 'prado_unitest') can authenticate.
& "$env:PGBIN\psql.exe" -h 127.0.0.1 -U postgres -c "ALTER ROLE prado_unitest WITH PASSWORD 'prado_unitest';"

- name: Run Unit Tests
run: composer unittest

db-mssql:
db-sqlsrv:
name: Microsoft SQL Server
runs-on: ubuntu-latest
services:
Expand Down Expand Up @@ -274,10 +277,10 @@ jobs:
run: >-
/opt/mssql-tools18/bin/sqlcmd -C
-U sa -P Prado_Unitest1
-i ./tests/initdb_mssql.sql
-i ./tests/initdb_sqlsrv.sql

- name: Run SQL Server tests
run: php vendor/bin/phpunit tests/unit/Data/DbSpecific/Mssql/
run: php vendor/bin/phpunit tests/unit/Data/DbSpecific/SqlSrv/

db-firebird:
name: Firebird
Expand Down
18 changes: 11 additions & 7 deletions framework/Caching/TDbCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Prado\Prado;
use Prado\Data\TDataSourceConfig;
use Prado\Data\TDbConnection;
use Prado\Data\TDbDriver;
use Prado\Data\TDbPropertiesTrait;
use Prado\Exceptions\TConfigurationException;
use Prado\TPropertyValue;
Expand Down Expand Up @@ -129,8 +130,11 @@ class TDbCache extends TCache implements \Prado\Util\IDbModule
*/
public function init($config)
{
$this->getApplication()->attachEventHandler('OnLoadStateComplete', [$this, 'doInitializeCache']);
$this->getApplication()->attachEventHandler('OnSaveState', [$this, 'doFlushCacheExpired']);
$app = $this->getApplication();
if ($app) {
$app->attachEventHandler('OnLoadStateComplete', [$this, 'doInitializeCache']);
$app->attachEventHandler('OnSaveState', [$this, 'doFlushCacheExpired']);
}
parent::init($config);
}

Expand Down Expand Up @@ -194,9 +198,9 @@ protected function initializeCache($force = false)
Prado::trace('Autocreate: ' . $this->_cacheTable, TDbCache::class);

$driver = $db->getDriverName();
if ($driver === 'mysql') {
if (in_array($driver, [TDbDriver::DRIVER_MYSQL, TDbDriver::EXTENSION_MYSQLI])) {
$blob = 'LONGBLOB';
} elseif ($driver === 'pgsql') {
} elseif ($driver === TDbDriver::DRIVER_PGSQL) {
$blob = 'BYTEA';
} else {
$blob = 'BLOB';
Expand Down Expand Up @@ -456,11 +460,11 @@ protected function setValue($key, $value, $expire)
}
$db = $this->getDbConnection();
$driver = $db->getDriverName();
if (in_array($driver, ['mysql', 'mysqli', 'sqlite', 'ibm', 'oci', 'sqlsrv', 'mssql', 'dblib', 'pgsql'])) {
if (in_array($driver, [TDbDriver::DRIVER_MYSQL, TDbDriver::EXTENSION_MYSQLI, TDbDriver::DRIVER_PGSQL, TDbDriver::DRIVER_SQLITE, TDbDriver::DRIVER_SQLSRV, TDbDriver::DRIVER_DBLIB, TDbDriver::DRIVER_OCI, TDbDriver::DRIVER_IBM])) {
$expire = ($expire <= 0) ? 0 : time() + $expire;
if (in_array($driver, ['mysql', 'mysqli', 'sqlite'])) {
if (in_array($driver, [TDbDriver::DRIVER_MYSQL, TDbDriver::EXTENSION_MYSQLI, TDbDriver::DRIVER_SQLITE])) {
$sql = "REPLACE INTO {$this->_cacheTable} (itemkey,value,expire) VALUES (:key,:value,$expire)";
} elseif ($driver === 'pgsql') {
} elseif ($driver === TDbDriver::DRIVER_PGSQL) {
$sql = "INSERT INTO {$this->_cacheTable} (itemkey, value, expire) VALUES (:key, :value, :expire) " .
"ON CONFLICT (itemkey) DO UPDATE SET value = EXCLUDED.value, expire = EXCLUDED.expire";
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace Prado\Data\ActiveRecord\Exceptions;

/**
* TActiveRecordConfigurationException class.
* TActiveRecordConfigurationException class
*
* @author Wei Zhuo <weizho[at]gmail[dot]com>
* @since 3.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Prado\Prado;

/**
* TActiveRecordException class
*
* Base exception class for Active Records.
*
* @author Wei Zhuo <weizho[at]gmail[dot]com>
Expand Down
3 changes: 3 additions & 0 deletions framework/Data/ActiveRecord/Exceptions/messages.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ ar_relations_undefined = Unable to determine Active Record relationships be
ar_undefined_relation_prop = Unable to find {1}::${2}['{0}'], Active Record relationship definition for property "{0}" not found in entries of {1}::${2}.
ar_invalid_relationship = Invalid active record relationship.
ar_relations_missing_fk = Unable to find foreign key relationships in table '{0}' that corresponds to table '{1}'.
ar_belongs_to_multiple_result = BelongsTo/HasOne relationship returned more than one result but exactly one was expected.
scaffold_unable_to_find_edit_view = Unable to find scaffold edit view control with ID '{0}'.
scaffold_unable_to_find_list_view = Unable to find scaffold list view control with ID '{0}'.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Prado\Data\ActiveRecord\Exceptions\TActiveRecordException;

/**
* TActiveRecordBelongsTo class
*
* Implements the foreign key relationship (TActiveRecord::BELONGS_TO) between
* the source objects and the related foreign object. Consider the
* <b>entity</b> relationship between a Team and a Player.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
namespace Prado\Data\ActiveRecord\Relations;

/**
* TActiveRecordHasMany class
*
* Implements TActiveRecord::HAS_MANY relationship between the source object having zero or
* more foreign objects. Consider the <b>entity</b> relationship between a Team and a Player.
* ```php
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use Prado\Prado;

/**
* TActiveRecordHasManyAssociation class
*
* Implements the M-N (many to many) relationship via association table.
* Consider the <b>entity</b> relationship between Articles and Categories
* via the association table <tt>Article_Category</tt>.
Expand Down
2 changes: 2 additions & 0 deletions framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use Prado\Prado;

/**
* TActiveRecordHasOne class
*
* TActiveRecordHasOne models the object relationship that a record (the source object)
* property is an instance of foreign record object having a foreign key
* related to the source object. The HAS_ONE relation is very similar to the
Expand Down
62 changes: 61 additions & 1 deletion framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,67 @@
use Prado\Prado;

/**
* Base class for active record relationships.
* TActiveRecordRelation class
*
* Abstract base class for all Active Record relationship handlers.
*
* Active Record relationships are declared via the static `$RELATIONS` array on
* each {@see TActiveRecord} subclass. Each entry maps a property name to an
* array whose first element is one of the four relationship constants and whose
* second element is the foreign record class name:
*
* ```php
* public static $RELATIONS = [
* 'profile' => [self::HAS_ONE, 'ProfileRecord'],
* 'orders' => [self::HAS_MANY, 'OrderRecord'],
* 'team' => [self::BELONGS_TO, 'TeamRecord'],
* 'categories' => [self::HAS_MANY_ASSOC, 'CategoryRecord', 'Article_Category'],
* ];
* ```
*
* The four relationship types are:
*
* - **HAS_ONE** ({@see TActiveRecordHasOne}) — the foreign table carries a foreign key
* that points back to this record's primary key. The related property is a single
* object (or `null`).
* - **HAS_MANY** ({@see TActiveRecordHasMany}) — same foreign-key direction as HAS_ONE,
* but the related property is a collection (array) of foreign objects.
* - **BELONGS_TO** ({@see TActiveRecordBelongsTo}) — this record's table carries the
* foreign key that points to the related record's primary key. The related
* property is a single object (or `null`).
* - **HAS_MANY_ASSOC** ({@see TActiveRecordHasManyAssociation}) — a many-to-many
* relationship resolved through an intermediate association table. A third
* element in the `$RELATIONS` entry names the association table.
*
* ## Fetching related objects
*
* Related objects are fetched lazily by chaining a relationship call onto a
* finder method call:
*
* ```php
* // Fetch all teams with their players eagerly loaded.
* $teams = TeamRecord::finder()->withPlayers()->findAll();
*
* // Chain multiple relationships.
* $articles = ArticleRecord::finder()->withCategories()->withAuthor()->findAll();
* ```
*
* The {@see __call()} method intercepts `with<Property>()` calls, delegates the
* underlying finder call to the source record, then calls
* {@see collectForeignObjects()} to populate each result's relationship property.
* Multiple chained `with*()` calls are queued via a static stack so that all
* relationships are applied to the same result set.
*
* ## Implementing a new relationship type
*
* Subclasses must implement:
* - {@see collectForeignObjects()} — given the source results, fetch and assign
* the corresponding foreign objects to each source record's relationship
* property.
* - {@see getRelationForeignKeys()} — return the foreign key mapping (FK field
* names as keys, source property names as values) used by this relationship.
* - {@see updateAssociatedRecords()} — persist any changes to the associated
* foreign objects (e.g. insert/delete rows in an association table).
*
* @author Wei Zhuo <weizho[at]gmail[dot]com>
* @since 3.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use Prado\Prado;

/**
* TActiveRecordRelationContext class
*
* TActiveRecordRelationContext holds information regarding record relationships
* such as record relation property name, query criteria and foreign object record
* class names.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

/**
* IScaffoldInput interface file.
*
* @author Brad Anderson <belisoful@icloud.com>
* @link https://github.com/pradosoft/prado
* @license https://github.com/pradosoft/prado/blob/master/LICENSE
*/

namespace Prado\Data\ActiveRecord\Scaffold\InputBuilder;

/**
* IScaffoldInput interface.
*
* IScaffoldInput defines the public contract for database-specific scaffold
* input builders. Implementations map database column types to appropriate
* Prado web controls (TTextBox, TCheckBox, TDropDownList, TDatePicker, etc.)
* and read the submitted value back into the active record.
*
* The built-in driver-specific classes (`TMysqlScaffoldInput`,
* `TSqliteScaffoldInput`, etc.) all implement this interface by inheriting
* from {@see TScaffoldInputBase}.
*
* Custom implementations for unsupported drivers may be registered by
* handling the **`fxActiveRecordScaffoldInputClass`** global event raised by
* {@see \Prado\Data\TDbDriverCapabilities::createScaffoldInput}. The sender
* is the connection and the parameter is the driver name string. Handlers
* must return the **fully-qualified class name** of a class that implements
* this interface; the first returned value is used.
*
* @author Brad Anderson <belisoful@icloud.com>
* @since 4.3.3
*/
interface IScaffoldInput
{
/**
* Default ID assigned to the primary input control within each scaffold item.
*
* Implementations must honour this constant so that
* {@see TScaffoldInputBase::createScaffoldInput} can locate the control
* when generating the associated label.
*/
public const DEFAULT_ID = 'scaffold_input';

/**
* Creates the appropriate input control(s) for a column and attaches them
* to the scaffold item container.
*
* Implementations should call `createControl` to build the control and
* `createControlLabel` (via the base class) when the primary control with
* {@see DEFAULT_ID} is found inside the item.
*
* @param mixed $parent the parent scaffold configuration.
* @param mixed $item the scaffold input item container.
* @param \Prado\Data\Common\TDbTableColumn $column the column metadata.
* @param \Prado\Data\ActiveRecord\TActiveRecord $record the active record instance.
*/
public function createScaffoldInput($parent, $item, $column, $record);

/**
* Reads the submitted input control value and stores it back into the
* active record column.
*
* Called during post-back to transfer user input into the record before
* save. Implementations should skip read-only columns (primary keys with
* sequences) via `getIsEnabled`.
*
* @param mixed $parent the parent scaffold configuration.
* @param mixed $item the scaffold input item container.
* @param \Prado\Data\Common\TDbTableColumn $column the column metadata.
* @param \Prado\Data\ActiveRecord\TActiveRecord $record the active record instance.
*/
public function loadScaffoldInput($parent, $item, $column, $record);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,11 @@
/**
* TMssqlScaffoldInput class.
*
*
* @link https://github.com/pradosoft/prado
* @todo v4.4 remove, replaced by TSqlSrvScaffoldInput
* @deprecated
*/
class TMssqlScaffoldInput extends TScaffoldInputCommon
class TMssqlScaffoldInput extends TSqlSrvScaffoldInput
{
protected function createControl($container, $column, $record)
{
switch (strtolower($column->getDbType())) {
case 'bit':
return $this->createBooleanControl($container, $column, $record);
case 'text':
return $this->createMultiLineControl($container, $column, $record);
case 'smallint': case 'int': case 'bigint': case 'tinyint':
return $this->createIntegerControl($container, $column, $record);
case 'decimal': case 'float': case 'money': case 'numeric': case 'real': case 'smallmoney':
return $this->createFloatControl($container, $column, $record);
case 'datetime': case 'smalldatetime':
return $this->createDateTimeControl($container, $column, $record);
default:
$control = $this->createDefaultControl($container, $column, $record);
if ($column->getIsExcluded()) {
$control->setEnabled(false);
}
return $control;
}
}

protected function getControlValue($container, $column, $record)
{
switch (strtolower($column->getDbType())) {
case 'boolean':
return $container->findControl(self::DEFAULT_ID)->getChecked();
case 'datetime': case 'smalldatetime':
return $this->getDateTimeValue($container, $column, $record);
default:
$value = $this->getDefaultControlValue($container, $column, $record);
if (trim($value) === '' && $column->getAllowNull()) {
return null;
} else {
return $value;
}
}
}
}
Loading
Loading