Skip to content

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.

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:

AdapterConnection classExtends
pgsqlPropulsion\Adapter\Pgsql\PgsqlPropulsionPDO\Pdo\Pgsql
mysqlPropulsion\Adapter\Mysql\MysqlPropulsionPDO\Pdo\Mysql
sqlitePropulsion\Adapter\Sqlite\SqlitePropulsionPDO\Pdo\Sqlite
mssqlPropulsion\Adapter\MSSQL\MssqlPropulsionPDO\Pdo\Dblib
oraclePropulsion\Adapter\Oracle\OraclePropulsionPDO\PDO
sqlsrv, nonePropulsion\Connection\GenericPropulsionPDO\PDO

Shared connection behaviour — savepoints, query counting, logging, statement caching — lives in PropulsionPDOTrait.

  • ?PropulsionPDO $con type 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 use PropulsionPDOTrait.
  • A connection.classname in your runtime configuration pointing at Propulsion\Connection\PropulsionPDO breaks. 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.

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.

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.

<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.

  • 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. Set propulsion.mysql.tableType = MyISAM in 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 left primaryTableName, the query comment, the setQueryCache() flags, and a pending _or() combine operator behind, and could null out dbName. If you reuse a Criteria after clear(), 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.
  • getPhp84TypeHint() and getPhp84PropertyType() are renamed getPhp85TypeHint()/getPhp85PropertyType(). Custom platforms, builders, or column-type code calling them needs updating.
  • ObjectBuilder’s per-column-type code generation moved out into handler classes under generator/Lib/Builder/OM/ColumnType/ with a resolving registry, replacing repeated column-type checks across six parallel elseif chains. If you subclassed ObjectBuilder to special-case a column type, the extension point is now a handler class rather than an override.

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_ALIASES and 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.

Once you’re upgraded, none of this is required, but this is what 2.0 added: