perplorm / perpl
Perpl is an improved and still maintained fork of Propel2, an open-source Object-Relational Mapping (ORM) for PHP.
Requires
- php: >=8.1
- ext-json: *
- ext-pdo: *
- ext-xml: *
- psr/log: ^1.0 || ^2.0 || ^3.0
- symfony/config: ^5.0.0 || ^6.0.0 || ^7.0.0
- symfony/console: ^5.0.0 || ^6.0.0 || ^7.0.0
- symfony/filesystem: ^5.0.0 || ^6.0.0 || ^7.0.0
- symfony/finder: ^5.0.0 || ^6.0.0 || ^7.0.0
- symfony/polyfill-php82: ^1.31
- symfony/polyfill-php83: ^1.31
- symfony/polyfill-php84: ^1.31
- symfony/translation: ^5.0.0 || ^6.0.0 || ^7.0.0
- symfony/validator: ^5.0.0 || ^6.0.0 || ^7.0.0
- symfony/yaml: ^5.0.0 || ^6.0.0 || ^7.0.0
Requires (Dev)
- mikey179/vfsstream: ^1.6
- monolog/monolog: ^1.3 || ^2.3 || ^3.0
- phpstan/phpstan: ^1.2
- phpunit/phpunit: ^9.5.0
- psalm/phar: ^6
- spryker/code-sniffer: ^0.17.2
Suggests
- monolog/monolog: The recommended logging library to use with Propel.
Replaces
- propel/propel: dev-main as 2.0.x-dev
- dev-main
- v2.4.0
- v2.3.0
- v2.2.0
- v2.1.0
- dev-master / 2.0.x-dev
- v2.0.0
- 2.0.0-alpha12
- 2.0.0-alpha11
- 2.0.0-alpha10
- 2.0.0-alpha9
- 2.0.0-alpha8
- 2.0.0-alpha7
- 2.0.0-alpha6
- 2.0.0-alpha5
- 2.0.0-alpha4
- 2.0.0-alpha3
- 2.0.0-alpha2
- 2.0.0-alpha1
- dev-propel-backmerge
- dev-lint-queries
- dev-model-stan
- dev-remove_polyfill_replacements
- dev-fix-timestampable-datetimeclass
- dev-model_code_cleanup
- dev-psalm_5
- dev-update_psalm
- dev-php8.4-fixes
- dev-typed_collections
- dev-getLocalColumnName_without_default_value_
- dev-cross-ref
- dev-nulls
- dev-update_php_min_version_to_8.1
- dev-no_multitable_delete
- dev-bundle_filters
- dev-type_fixes
- dev-deprecated_criteria_methods
- dev-split_map
- dev-avoid_column_string_ops
- dev-feature/load_multiple_behaviors_from_composer
- dev-extract_column_replacement_from_ModelCriteria
- dev-tests/always_close_statements
- dev-sqlite_replace_test
- dev-fix_sqlite_tests
- dev-run_propel_as_root
- dev-feature/bulk-load_tables
- dev-fix-github-actions-cache
- dev-remove_boolean
- dev-feature/auditable_behavior
- dev-feature/synced_table_behavior
- dev-fix_tests/add_unique_index_for_complex_fks
- dev-feature/synced_table_with_ignored_columns
- dev-feature/output_group_behavior
- dev-speedup_tests
- dev-analyzer_fixes
- dev-feature/output_group
- dev-bugfix/issue1489-mysql_types
- dev-php8.3-test
- dev-bugfix/fix_inheritance_classkey_type
- dev-bugfix/convert_uuid_in_pk_on_update
- dev-bugfix/fix_order_in_test_assertion
- dev-bugfix/avoid_cross_ref_relation_name_collisions
- dev-patch-1
- dev-feature/uuid_adjustments_for_mariadb
- dev-feature/untangle_criteria_class
- dev-feature/untangle_criteria_class1-query
- dev-php8
- dev-feature/move-tests-to-ga
- dev-feature/move-to-github-actions
- dev-data-mapper
This package is auto-updated.
Last update: 2025-05-14 18:42:33 UTC
README
Perpl is a fork of the unmaintained Propel2, an open-source Object-Relational Mapping (ORM) for PHP. It adds several improvements and fixes, including proper versioning.
Installation
- Replace the
require
declaration for Propel with Perpl:
"require": { + "perplorm/perpl": ">=2.0", - "propel/propel": "dev-main as 2.0.x-dev", },
- Remove the
vcs
entry for Propel2 dev in composer.json:
"repositories": [ - { - "type": "vcs", - "url": "git@github.com:propelorm/Propel2.git" - } ],
- Update libraries:
$ composer update
- Rebuild models:
$ vendor/bin/propel --config-dir <path/to/config> model:build $ composer dump-autoload
- Open a file where you call
Query::find()
and replace it withQuery::findObjects()
. If everything worked, you get return typeObjectCollection<YourModelName>
. Yay!
⚡ Don't forget to analyze your project with PHPStan (or similar) to get notice where updates are necessary due to improved types in Perpl. |
---|
Features
Motivation for Perpl was to make features available around code-style, typing, performance and usability.
Ready for PHP 8.4
Code is tested to run in PHP 8.4 without deprecation notices or warnings.
Type-preserving queries
Improved types allow for code completion and statistic analysis.
- preserves types between calls to
useXXXQuery()
/endUse()
- adds methods
findObjects()
/findTuples()
, which return typed Collection objects
$q = BookQuery::create(); // BookQuery<null> $q = $q->useAuthorQuery(); // AuthorQuery<BookQuery<null>> $q = $q->useEssayRelatedByFirstAuthorIdExistsQuery(); // EssayQuery<AuthorQuery<BookQuery<null>>> $q = $q->endUse(); // AuthorQuery<BookQuery<null>> $q = $q->endUse(); // BookQuery<null> // findObjects() returns object collection $o = $q->findObjects(); // BookCollection $a = $o->populateAuthor() // AuthorCollection $a = $a->getFirst(); // Author|null // findTuples() returns arrays $a = $q->findTuples(); // ArrayCollection $r = $q->getFirst(); // array<string, mixed>|null
Note that type propagation in endUse()
requires child query classes to declare and pass on the generic parameter from their parent/base class:
/* * @template ParentQuery extends \Propel\Runtime\ActiveQuery\TypedModelCriteria|null = null * @extends BaseBookQuery<ParentQuery> */ class BookQuery extends BaseBookQuery
This cannot be added automatically for existing classes. While IDEs seem to figure it out without the declaration, phpstan or psalm will (correctly) see the return type as null
and report errors. Add the declaration in the child class to fix it.
Generating collection classes for models can be configured in schema.xml, (see #47 for details)
Code cleanup and improved performance
These changes mostly improve internal readability/soundness of core Propel code. They mostly allow for easier and safe maintenance, but occasionally lead to performance improvements, for example when repetitive operations on strings are replaced by proper data structures.
Some notable changes:
- columns in queries are turned to objects, which leads to more readable code and makes tons of string operations obsolete (~30-50% faster query build time, see #24)
- fixes some confusing names (Criteria vs Criterion)
- spreads out some "one size fits none" overloads, i.e.
Criteria::map
becomesCriteria::columnFilters
andCriteria::updateValues
Nested filters through operators
Introduces Criteria::combineFilters()
/Criteria::endCombineFilters()
which build nested filter conditions:
// A=1 AND (B=2 OR C=3) AND (D=4 OR E=5) (new Criteria()) ->addFilter('A', 1) ->combineFilters() ->addFilter('B', 2) ->addOr('C', 3) ->endCombineFilters() ->combineFilters() ->addFilter('D', 4) ->addOr('E', 5) ->endCombineFilters()
Previously, this required to register the individual parts under an arbitrary name using Criteria::addCond()
and then combining them with Criteria::combine()
, possibly under another arbitrary name for further processing.
Read multiple behaviors from same repository
Propel restricts reading behaviors from repositories to one per repo. This allows to read multiple behaviors (see #25 for details).
Fixed cross-relations
Creates methods for all elements of ternary relation (Propel only uses first foreign key). Fixes naming issues and detects duplicates in model method names.
Breaking Changes
Perpl is fully backwards compatible with Propel2, with few exceptions. They mostly affect the low-level Criteria interface. Impact for regular users should be slim to none.
Setting update values and adding filters uses dedicated methods
Affects manual use of Criteria::update() - does not affect updates through Propel
Propel uses the same methods to set update values and filters. As a consequence, it requires a second Criteria to distinguish between filters and values, and a peculiar format to register update expressions:
$bookQuery = BookQuery::create()->add('id', 42); $value = new Criteria ->add('title', 'foo') ->add('count', ['raw' => 'count + ?', 'value' => 5'], Criteria::CUSTOM_EQUAL)`); $bookQuery->update($value);
Now dedicated methods are used to add filters or set update values/expressions, the update can be performed on a single query:
$bookQuery = BookQuery::create() ->addFilter('id', 42) ->setUpdateValue('title', 'foo') ->setUpdateExpression('count', 'count + ?', 5) ->update();
The add()
method can still be used to set filter, but is deprecated in favor of addFilter()
. Update values cannot be added using add()
.
UPDATE only affects one table per criteria
Affects manual use of Criteria::update() - does not affect updates through Propel
Propel allows to update multiple tables in the same query:
$criteria = (new Criteria())->add('a.name', 'foo')->add('b.count', 10, '>'); $values ->add('a.comment', 'I am foo') ->add('b.comment', 'I have count over 10') ->add('c.title', 'No title but me'); $criteria->update($values); // runs three queries: // UPDATE a SET comment="I am foo" WHERE name="foo"; // UPDATE b SET comment="I have count over 10" WHERE count > 10; // UPDATE c SET title="No title but me";
This requires table names in filters to match table names in values, which is not save, considering that Propel allows multiple names and aliases but often fails to resolve them.
Now update()
produces a single query and (in above example) subsequently a database error, when the columns cannot be found on the table. Usage has to be replaced with individual objects.
DELETE only affects one table per criteria
Affects manual use of Criteria::delete() or TableMap::doDelete()- does not affect updates through Propel
Same as with Criteria::update()
above, delete can only affect one table per object.
// Causes exception now $c = new Criteria(); $c->add(BookTableMap::COL_ID, $hp->getId()); $c->add(AuthorTableMap::COL_ID, $hp->getAuthorId()); $c->add(PublisherTableMap::COL_ID, $hp->getPublisherId()); BookTableMap::doDelete($c);
Use model objects ($hp->delete()
) or query (BookQuery::create()->filterById($hp->getId())->delete()
) to delete, one for each table.
Forcing table alias in UPDATE on Sqlite
Affects users that force table alias on a DBMS that does not allow table aliases in UPDATE
Even when forcing an alias in an update, Propel will not use it.
BookQuery::create()->setModelAlias('b', true)->...->update();
Now it does, breaking DBMSs that do not allow aliases in UPDATE (like Sqlite).
This will be resolved at some point, but for now: if you don't need it, don't force it.
Deprecated low-level API methods
Affects usage of old API methods like Criteria::get()
, Criteria::put()
, etc.
Obscure names, replaced functionality, unclear use-case - removing those methods is part of de-cluttering the query object interface and code. Internally, they are not used anymore.
The methods are still available, but only through magic __call()
. They do not appear on the query object interface and thus are not suggested by autocomplete. Using them will trigger a deprecation warning. The method docs in src/Propel/Runtime/ActiveQuery/DeprecatedCriteriaMethods.php
describe how to replace them.
A full list of deprecated methods can be found in #28. Notable mentions are Criteria::addCond()
and Criteria::combine()
, which are replaced by Criteria::combineFilters()
(see above), and Criteria::addSelectQuery()
, which is replaced by the aptly-named Criteria::addSubquery()
.
Changing method names of cross-relations
Affects cross-relations with parallel regular FK between target tables
Fixed naming might lead to exceptions during model:build
, requesting to change relation names that cause duplicate names.
Cross-relations that were previously prefixed with "Cross" are now named correctly, meaning model methods like addCrossBook()
are now replaced with the correctly named addBook()
.
Outlook
Some things I would like to do when I find the time:
- Delay resolving of column names until query is created.
Automatically build subclasses of ObjectCollection for each model class, which provide typed entries to✔️ObjectCollection::populateRelation()
for model relations (i.e.AuthorQuery::create()->findObjects()->populateBooks()
).- Get prepared statement parameters without building filters for QueryCache behavior.
Disclaimer
Built with care, tested, but not yet proven over time. Provided as is. Test before deployment. Let me know how it goes!
Feedback and PRs are welcome.
Thanks to Propel2!
License
MIT. See the LICENSE
file for details.