Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ First standalone release, planned as 1.0.0, for PRADO 4.4. SqlMap moves out of t

### Added

- `TSqlMapManager::VERSION` holds the package version and `TSqlMapManager::getVersion()` (the `Version` property) returns the version that built the manager, in the form of `Prado::getVersion()`. The version is serialized with the manager. `TSqlMapConfig` discards a cached manager whose version is not `VERSION` and rebuilds it, so the application cache does not need to be cleared after an upgrade.
- An `<insert>`, `<update>`, or `<delete>` statement with a `cacheModel` attribute flushes that cache model when it executes, the same as a `<flushOnExecute>` entry. The attribute was previously accepted and ignored (#5).
- `composer.json` declares `extra.prado` for PRADO 4.4: `bootstrap` (`TSqlMapConfig`), `error-messages` (`src/Data/SqlMap/DataMapper/messages.txt`), and `class-map` (`config/classes.json`). The legacy top-level `extra.bootstrap` remains for PRADO 4.3.
- `config/classes.json` maps the 55 SqlMap short class names (`TSqlMapConfig`, `TSqlMapFifoCache`, ...) to their FQNs. It replaces the SqlMap entries of the framework's `classes.php`.
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ tests/
- **`if` statements** always use a block (`{}`), never a single-line body.
- **Backward compatibility** — all changes must be backward compatible for point releases.
- **`@since` tag** — use the next release version when adding new methods or classes; omit the method tag when it matches the class tag. Check prado's `CLAUDE.md` for the current next-release version number.
- **Package version** — `TSqlMapManager::VERSION` is the package version. Bump it with each release; `TSqlMapConfig` discards cached managers built by another version.
- **Uniform Access Principal / Self Encapsulation** required — access own properties via getters/setters, not raw `$this->_prop`.
- **Error codes** for PRADO exceptions are defined in `src/Data/SqlMap/DataMapper/messages.txt`.
- **`@author` tags** — only Wei Zhuo, Brad Anderson, and Fabio Bas are listed as extension authors.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,7 @@ Clinton Begin started iBATIS in 2001, and its SQL Maps component (2002) mapped S

## Versioning

The package has its own version line, starting at 1.0.0, independent of the PRADO framework version. The first release pairs with PRADO 4.4, the release that removes SqlMap from the framework. Point releases stay backward compatible. Changes are listed in the [changelog](CHANGELOG.md).
The package has its own version line, starting at 1.0.0, independent of the PRADO framework version. The first release pairs with PRADO 4.4, the release that removes SqlMap from the framework. Point releases stay backward compatible. Changes are listed in the [changelog](CHANGELOG.md). The version is available at runtime as `TSqlMapManager::VERSION`, and `TSqlMapManager::getVersion()` reports the version that built a manager instance.

## License

Expand Down
3 changes: 2 additions & 1 deletion docs/integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ return [

`TSqlMapConfig` bootstraps the extension. On first access of its `Client` property it:

1. Checks the application cache for a previously serialized [`TSqlMapManager`](#tsqlmapmanager) (when `EnableCache` is `true`).
1. Checks the application cache for a previously serialized [`TSqlMapManager`](#tsqlmapmanager) (when `EnableCache` is `true`). A cached manager whose `Version` is not `TSqlMapManager::VERSION` was written by another release of this package and is ignored.
2. If not cached, instantiates a new `TSqlMapManager`, calls `configureXml()` with the resolved config file path, and stores the result in the cache.
3. Calls `getSqlmapGateway()` on the manager and returns the resulting [`TSqlMapGateway`](#tsqlmapgateway).

Expand Down Expand Up @@ -108,6 +108,7 @@ When `ConnectionID` is set and the manager is loaded from cache, `TSqlMapConfig`
| `getDbConnection` | `getDbConnection(): TDbConnection` | Returns the active database connection. |
| `setDbConnection` | `setDbConnection(TDbConnection $conn): void` | Replaces the database connection (used by `TSqlMapConfig` after a cache hit). |
| `flushCacheModels` | `flushCacheModels(): void` | Flushes all statement-level cache models registered with this manager. |
| `getVersion` | `getVersion(): ?string` | Returns the package version that built the manager, in the form of `Prado::getVersion()`. A new manager returns `TSqlMapManager::VERSION`; a manager unserialized from a cache written by another release returns that release's version, or `null` when none was recorded. |

The manager is also the target for direct (non-PRADO) use:

Expand Down
7 changes: 3 additions & 4 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,12 @@ Statements with a `cacheModel` build their cache key from the SQL text. Cached s

### Serialized managers

With `EnableCache="true"`, `TSqlMapConfig` stores the parsed `TSqlMapManager` in the application cache. The serialized form of several classes differs from the framework copy. Clear the application cache once after upgrading so the manager is rebuilt.
With `EnableCache="true"`, `TSqlMapConfig` stores the parsed `TSqlMapManager` in the application cache. The serialized form of several classes differs from the framework copy. `TSqlMapConfig` detects this: a cached manager that reports a `Version` other than `TSqlMapManager::VERSION` (the framework copy reports none) is discarded and the manager is rebuilt from the config files on the next request. No cache clearing is needed.

## Verifying the move

After installing the package:

1. Confirm `class="TSqlMapConfig"` module definitions still resolve.
2. Clear the application cache, or delete the cache files under `protected/runtime`.
3. Replace any comparison of `TSqlMapException::getErrorCode()` with message text.
4. Run the application's own tests against its mapped statements.
2. Replace any comparison of `TSqlMapException::getErrorCode()` with message text.
3. Run the application's own tests against its mapped statements.
1 change: 1 addition & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<file>./tests/unit/Data/SqlMap/SqlMapSleepTest.php</file>
<file>./tests/unit/Data/SqlMap/StatementExtendsTest.php</file>
<file>./tests/unit/Data/SqlMap/Ticket589Test.php</file>
<file>./tests/unit/Data/SqlMap/TSqlMapManagerVersionTest.php</file>
</testsuite>
<!-- Driver-specific integration test suites — used by dedicated CI jobs only. -->
<testsuite name="db-mysql">
Expand Down
11 changes: 7 additions & 4 deletions src/Data/SqlMap/TSqlMapConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@
* config file path) so that XML parsing is skipped on subsequent requests.
* Cache entries are invalidated automatically when the config files change
* unless the application is running in {@see \Prado\TApplicationMode::Performance}
* mode.
* mode. A cached manager whose {@see TSqlMapManager::getVersion() Version} is not
* {@see TSqlMapManager::VERSION} was written by another release of this package;
* it is discarded and the manager is rebuilt from the config files.
*
* @author Wei Zhuo <weizho[at]gmail[dot]com>
* @since 3.1
Expand Down Expand Up @@ -157,16 +159,17 @@ protected function cacheSqlMapManager($manager)
}

/**
* Loads SqlMap manager from cache.
* @return ?TSqlMapManager SqlMap manager intance if load was successful, null otherwise.
* Loads SqlMap manager from cache. A cached manager built by another version
* of this package is ignored, so the caller rebuilds it and replaces the entry.
* @return ?TSqlMapManager SqlMap manager instance if load was successful, null otherwise.
*/
protected function loadCachedSqlMapManager()
{
if ($this->getEnableCache()) {
$cache = $this->getApplication()->getCache();
if ($cache !== null) {
$manager = $cache->get($this->getCacheKey());
if ($manager instanceof TSqlMapManager) {
if ($manager instanceof TSqlMapManager && $manager->getVersion() === TSqlMapManager::VERSION) {
return $manager;
}
}
Expand Down
31 changes: 31 additions & 0 deletions src/Data/SqlMap/TSqlMapManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,21 @@
* $result = $sqlmap->queryForObject('Products');
* ```
*
* {@see getVersion Version} is the package version that built the manager. It is
* serialized with the manager, so a manager restored from the application cache
* reports the version that wrote the cache entry.
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @since 3.1
*/
class TSqlMapManager extends \Prado\TComponent
{
/**
* Version of this package. Equal to {@see getVersion} for a new manager.
* @since 4.4.0
*/
public const VERSION = '1.0.0';

private $_mappedStatements;
private $_resultMaps;
private $_parameterMaps;
Expand All @@ -52,6 +62,11 @@ class TSqlMapManager extends \Prado\TComponent
private $_connection;
private $_gateway;
private $_cacheDependencies;
/**
* @var ?string package version that built this manager. Null for a manager
* unserialized from a cache entry written before the version was recorded.
*/
private $_version;

/**
* Constructor, create a new SqlMap manager.
Expand All @@ -60,6 +75,7 @@ class TSqlMapManager extends \Prado\TComponent
public function __construct($connection = null)
{
$this->_connection = $connection;
$this->_version = self::VERSION;

$this->_mappedStatements = new TMap();
$this->_resultMaps = new TMap();
Expand All @@ -68,6 +84,21 @@ public function __construct($connection = null)
parent::__construct();
}

/**
* The version of this package that built the manager, in the form of
* {@see \Prado\Prado::getVersion()}. A new manager reports {@see VERSION}.
* A manager unserialized from the application cache reports the version of
* the package that wrote the entry, or null when that package recorded no
* version. {@see TSqlMapConfig} discards a cached manager whose version is
* not {@see VERSION}.
* @return ?string package version that built this manager.
* @since 4.4.0
*/
public function getVersion(): ?string
{
return $this->_version;
}

/**
* @param \Prado\Data\TDbConnection $conn default database connection
*/
Expand Down
98 changes: 98 additions & 0 deletions tests/unit/Data/SqlMap/TSqlMapManagerVersionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

use Prado\Caching\TMemoryCache;
use Prado\Data\SqlMap\TSqlMapConfig;
use Prado\Data\SqlMap\TSqlMapManager;
use Prado\Prado;
use Prado\Test\Unit\PradoUnit;

class TSqlMapManagerVersionTest extends PHPUnit\Framework\TestCase
{
private $appCache;

protected function setUp(): void
{
$this->appCache = PradoUnit::getProp(Prado::getApplication(), '_cache');
}

protected function tearDown(): void
{
PradoUnit::setProp(Prado::getApplication(), '_cache', $this->appCache);
}

private function cachingConfig(): TSqlMapConfig
{
$cache = new TMemoryCache();
$cache->setPrimaryCache(false);
$cache->init(null);
PradoUnit::setProp(Prado::getApplication(), '_cache', $cache);

$config = new TSqlMapConfig();
$config->setConfigFile(__DIR__ . '/maps/tests.xml');
$config->setEnableCache(true);
return $config;
}

private function cacheKey(TSqlMapConfig $config): string
{
return $config->getID() . $config->getConfigFile();
}

public function testVersionIsSemantic()
{
$this->assertMatchesRegularExpression('/^\d+\.\d+\.\d+$/', TSqlMapManager::VERSION);
}

public function testNewManagerReportsPackageVersion()
{
$manager = new TSqlMapManager();
$this->assertSame(TSqlMapManager::VERSION, $manager->getVersion());
$this->assertSame(TSqlMapManager::VERSION, $manager->Version);
}

public function testVersionIsSerialized()
{
$manager = unserialize(serialize(new TSqlMapManager()));
$this->assertSame(TSqlMapManager::VERSION, $manager->getVersion());
}

public function testManagerSerializedWithoutVersionReportsNull()
{
$class = TSqlMapManager::class;
$manager = unserialize(sprintf('O:%d:"%s":0:{}', strlen($class), $class));
$this->assertInstanceOf(TSqlMapManager::class, $manager);
$this->assertNull($manager->getVersion());
}

public function testConfigReusesCachedManagerOfSameVersion()
{
$config = $this->cachingConfig();
$first = $config->getSqlMapManager();
$this->assertSame(TSqlMapManager::VERSION, $first->getVersion());

$cached = Prado::getApplication()->getCache()->get($this->cacheKey($config));
$this->assertInstanceOf(TSqlMapManager::class, $cached);
$this->assertSame(TSqlMapManager::VERSION, $cached->getVersion());

$second = $config->getSqlMapManager();
$this->assertSame(TSqlMapManager::VERSION, $second->getVersion());
$this->assertTrue($second->getMappedStatements()->contains('test'));
}

public function testConfigDiscardsCachedManagerOfAnotherVersion()
{
$config = $this->cachingConfig();
$cache = Prado::getApplication()->getCache();

foreach (['0.9.0', null] as $version) {
$stale = new TSqlMapManager();
PradoUnit::setProp($stale, '_version', $version);
$cache->set($this->cacheKey($config), $stale);

$manager = $config->getSqlMapManager();
$this->assertSame(TSqlMapManager::VERSION, $manager->getVersion());
$this->assertTrue($manager->getMappedStatements()->contains('test'));
$this->assertSame(TSqlMapManager::VERSION, $cache->get($this->cacheKey($config))->getVersion());
}
}
}
Loading