Upgrading from 1.0 to 2.0
Propulsion 2.0 adds a large batch of query-layer capabilities, new column types, per-platform DDL parity, a two-tier query result cache, and support for running under a persistent-worker SAPI. It also removes several legacy code paths, which is what makes it a major version.
Most projects need to change nothing. Everything new is opt-in, existing generated code keeps working without regeneration, and the removals are all of paths that were either already broken or Propel-1-era leftovers. This page is the checklist for the cases that do need attention.
If you’re coming from Propel 1 rather than Propulsion 1.0, start at Migrating from Propel 1 instead.
Check these first
Section titled “Check these first”PropulsionPDO is now an interface
Section titled “PropulsionPDO is now an interface”Propulsion\Connection\PropulsionPDO was a concrete class; it is now an interface. Every connection Propulsion constructs is a driver-specific subclass of the matching PHP 8.4 \Pdo\* class:
| Adapter | Connection class | Extends |
|---|---|---|
pgsql | Propulsion\Adapter\Pgsql\PgsqlPropulsionPDO | \Pdo\Pgsql |
mysql | Propulsion\Adapter\Mysql\MysqlPropulsionPDO | \Pdo\Mysql |
sqlite | Propulsion\Adapter\Sqlite\SqlitePropulsionPDO | \Pdo\Sqlite |
mssql | Propulsion\Adapter\MSSQL\MssqlPropulsionPDO | \Pdo\Dblib |
oracle | Propulsion\Adapter\Oracle\OraclePropulsionPDO | \PDO |
sqlsrv, none | Propulsion\Connection\GenericPropulsionPDO | \PDO |
Shared connection behaviour — savepoints, query counting, logging, statement caching — lives in PropulsionPDOTrait.
?PropulsionPDO $contype hints keep working, and are still the right way to type a connection parameter.- Code that instantiated
new PropulsionPDO(...)or extended the class breaks. Extend the driver-specific class for your platform, or implement the interface and usePropulsionPDOTrait. - A
connection.classnamein your runtime configuration pointing atPropulsion\Connection\PropulsionPDObreaks. Drop the key: the default now comes from the adapter (DBAdapter::getDefaultPdoClass()), which picks the right driver-specific class for you. See the runtime configuration reference. - If you implement the interface directly rather than using the trait, note it gained
resetDebugCounters(). The bundled classes get it from the trait.
buildtime-conf.xml is no longer read
Section titled “buildtime-conf.xml is no longer read”Build-time connection configuration is plain-PHP-array only: a .php file returning the array, or the propulsion.buildtimeConfigArray build property. The XML parser, the base64-encoded-XML string branch of the old propulsion.buildtimeConf property, and the stale propulsion.buildtime.conf.file default are gone.
If you still have a buildtime-conf.xml, convert it to the PHP form — it’s a small file, and sql:diff plus the migration commands are the only things that read it.
runtime-conf.xml (a different legacy format) and schema-XML parsing are untouched.
--target-platform=php5 is gone
Section titled “--target-platform=php5 is gone”Its only remaining behaviour was a dead branch emitting an untyped create() signature to match a builder class that had already been removed, so passing it produced a fatal signature clash. The option itself still exists for real codegen-dialect overrides (--target-platform=php84); only the php5 value is gone.
The query_cache behavior is gone
Section titled “The query_cache behavior is gone”<behavior name="query_cache" /> is no longer a registered behavior — remove it from your schema. It only cached the rendered SQL string (queries still hit the database), called APC functions that don’t exist on PHP 8.5, had no invalidation, and had no test coverage.
Its replacement is a real result cache, opt-in per query rather than per table: Query caches.
Cache-enabled queries using a streaming formatter are no longer cached
Section titled “Cache-enabled queries using a streaming formatter are no longer cached”If you had setQueryCache() on a query that also used FORMAT_STATEMENT or FORMAT_ON_DEMAND, that query now runs uncached at both tiers. Both formatters are tied to a live statement, so a second cache hit handed back an exhausted cursor — the entry was never usable. This is a behaviour change with no action needed unless you were relying on the (broken) hit.
Changed defaults
Section titled “Changed defaults”- MySQL’s default storage engine is InnoDB, not MyISAM (
propulsion.mysql.tableType). This matches MySQL’s own default since 5.5, what every Propulsion test fixture already overrode it to, and what native delete triggers require. Setpropulsion.mysql.tableType = MyISAMin your build configuration if you genuinely need the old behaviour. - Minimum database versions are documented and higher: PostgreSQL 16, MySQL 8.0, MariaDB 10.5, SQLite 3.35, SQL Server 2012, Oracle 12c. Only PostgreSQL’s floor is enforced in code; the others are what the emitted SQL and DDL now assume. MSSQL and Oracle pagination were rewritten to
OFFSET/FETCH, which is where the SQL Server 2012 and Oracle 12c floors come from. See Supported databases. Criteria::clear()now resets everything it claims to. It previously leftprimaryTableName, the query comment, thesetQueryCache()flags, and a pending_or()combine operator behind, and could null outdbName. If you reuse aCriteriaafterclear(), it now genuinely starts clean — which is a fix, but it does mean a reused object no longer carries a stale primary table you may have been depending on by accident.- Three new PSR dependencies come in with Composer:
psr/event-dispatcher,psr/simple-cache, and (already present in 1.0)psr/log. No implementation is required — each facade is inert until you register something.
If you maintain custom generator classes
Section titled “If you maintain custom generator classes”getPhp84TypeHint()andgetPhp84PropertyType()are renamedgetPhp85TypeHint()/getPhp85PropertyType(). Custom platforms, builders, or column-type code calling them needs updating.ObjectBuilder’s per-column-type code generation moved out into handler classes undergenerator/Lib/Builder/OM/ColumnType/with a resolving registry, replacing repeated column-type checks across six parallelelseifchains. If you subclassedObjectBuilderto special-case a column type, the extension point is now a handler class rather than an override.
Worth regenerating for
Section titled “Worth regenerating for”Nothing forces a regeneration — existing generated code is unaffected, because the legacy global class aliases still install by default. Two improvements need one, though:
- Flat (global-namespace) generated code now imports the runtime classes it uses instead of relying on those global aliases. Namespaced generated code always did.
- After regenerating, a project whose own code doesn’t use the bare historic names can set
PROPULSION_SKIP_LEGACY_CLASS_ALIASESand save 3.2 MB and 175 classes per process. That matters most under a persistent worker — see Reclaiming the memory the legacy aliases cost for the safety rules, which are worth reading before you set it.
Regeneration is also what gives an older model populateObjectsFromRows(), which rawQuery()->hydrate() needs.
What’s new to opt into
Section titled “What’s new to opt into”Once you’re upgraded, none of this is required, but this is what 2.0 added:
- Common table expressions, window functions, set operations, and
EXISTS/INsubquery filters - Upsert, bulk loading,
RETURNING, and database-computedUPDATEvalues - Pessimistic row locking and the
optimistic_lockbehavior - Unit of Work for coordinated multi-object flush ordering
- PSR-14 model lifecycle events
- Real savepoint-based nested transactions on every supported platform
- New column types: JSON/JSONB, UUID, native PHP enums,
BcMath\Numberdecimals, intervals, network types, ranges, vectors, geometry, and MySQLSET - Per-platform DDL parity — identity columns, generated/computed columns, full-text search, partial and expression indexes, temporal tables, and more, all in the schema reference
- The two-tier query result cache and caching for hand-written SQL
- Support for persistent-worker deployment
- A PHPStan extension for
ModelCriteria’s magic methods, with the whole project now clean at level 9