| 1 |
= nahoPropelOptimizerPlugin plugin = |
|---|
| 2 |
|
|---|
| 3 |
This `nahoPropelOptimizerPlugin` plugin fixes a few defects (not to say "bugs") in Propel model generation, that are not yet handled by Symfony builder wrapping. |
|---|
| 4 |
|
|---|
| 5 |
== Instalation == |
|---|
| 6 |
|
|---|
| 7 |
* Install the plugin |
|---|
| 8 |
|
|---|
| 9 |
{{{ |
|---|
| 10 |
symfony plugin-install http://plugins.symfony-project.com/nahoPropelOptimizerPlugin |
|---|
| 11 |
}}} |
|---|
| 12 |
|
|---|
| 13 |
Like any other plugin, you can either extract one of the attached archives, or use Subversion. |
|---|
| 14 |
|
|---|
| 15 |
* Edit `config/propel.ini` and find the following lines : |
|---|
| 16 |
|
|---|
| 17 |
{{{ |
|---|
| 18 |
; builder settings |
|---|
| 19 |
propel.builder.peer.class = addon.propel.builder.SfPeerBuilder |
|---|
| 20 |
propel.builder.object.class = addon.propel.builder.SfObjectBuilder |
|---|
| 21 |
|
|---|
| 22 |
propel.builder.objectstub.class = addon.propel.builder.SfExtensionObjectBuilder |
|---|
| 23 |
propel.builder.peerstub.class = addon.propel.builder.SfExtensionPeerBuilder |
|---|
| 24 |
propel.builder.objectmultiextend.class = addon.propel.builder.SfMultiExtendObjectBuilder |
|---|
| 25 |
propel.builder.mapbuilder.class = addon.propel.builder.SfMapBuilderBuilder |
|---|
| 26 |
}}} |
|---|
| 27 |
|
|---|
| 28 |
And replace `addon.propel.builder.Sf` with `plugins.nahoPropelOptimizerPlugin.lib.SfOptimized` : |
|---|
| 29 |
|
|---|
| 30 |
{{{ |
|---|
| 31 |
; builder settings |
|---|
| 32 |
propel.builder.peer.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedPeerBuilder |
|---|
| 33 |
propel.builder.object.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedObjectBuilder |
|---|
| 34 |
|
|---|
| 35 |
propel.builder.objectstub.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedExtensionObjectBuilder |
|---|
| 36 |
propel.builder.peerstub.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedExtensionPeerBuilder |
|---|
| 37 |
propel.builder.objectmultiextend.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedMultiExtendObjectBuilder |
|---|
| 38 |
propel.builder.mapbuilder.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedMapBuilderBuilder |
|---|
| 39 |
}}} |
|---|
| 40 |
|
|---|
| 41 |
* Rebuild your model : |
|---|
| 42 |
|
|---|
| 43 |
{{{ |
|---|
| 44 |
$ symfony propel-build-model |
|---|
| 45 |
$ symfony cc |
|---|
| 46 |
}}} |
|---|
| 47 |
|
|---|
| 48 |
Your model classes have been regenerated with all the supported optimizations. You're ready to go ! |
|---|
| 49 |
|
|---|
| 50 |
Default behavior is enabling all supported optimizations, see the "Configuration" section to disable some of them if you want. |
|---|
| 51 |
|
|---|
| 52 |
== Optimizations == |
|---|
| 53 |
|
|---|
| 54 |
This section lists all the changes made by this custom builder. |
|---|
| 55 |
|
|---|
| 56 |
=== Includes overhead (defect) === |
|---|
| 57 |
|
|---|
| 58 |
Default behavior does not remove all the useless calls to "include_once" made by Propel (it removes some of them, but not all). They are useless as the autoloader already takes care of this, and they add a little useless overhead. |
|---|
| 59 |
|
|---|
| 60 |
`nahoPropelOptimizer` removes all these calls in Peer and MapBuilder classes. |
|---|
| 61 |
|
|---|
| 62 |
=== Explicit joins (bug) === |
|---|
| 63 |
|
|---|
| 64 |
With the default builder, Peer classes join with an implicit JOIN. PostPeer::doSelectJoinAuthor will execute this query : |
|---|
| 65 |
|
|---|
| 66 |
{{{ |
|---|
| 67 |
SELECT * FROM post, author WHERE post.author_id = author.id AND ... |
|---|
| 68 |
}}} |
|---|
| 69 |
|
|---|
| 70 |
Which is equivalent to : |
|---|
| 71 |
|
|---|
| 72 |
{{{ |
|---|
| 73 |
SELECT * FROM post INNER JOIN author ON (post.author_id = author.id) WHERE ... |
|---|
| 74 |
}}} |
|---|
| 75 |
|
|---|
| 76 |
This behavior makes the `doSelectJoinXXX` methods unreliable if you expected to be sure to have the same results with or without the join (if you had a not required foreign key, this implicit inner join will just drop the lines where this key was `NULL`). |
|---|
| 77 |
|
|---|
| 78 |
`nahoPropelOptimizer` forces Propel to use real explicit joins, and checks if the foreign key is `required` or not to make a LEFT JOIN or an INNER JOIN. |
|---|
| 79 |
|
|---|
| 80 |
Due to a still partial support of those joins in Propel, the related object is always hydrated, but when the foreign key was `NULL` it's hydrated with `NULL` values... But the next optimization is here to fix that ;) |
|---|
| 81 |
|
|---|
| 82 |
=== LEFT JOINS and related objects hydrated with NULL values (defect) === |
|---|
| 83 |
|
|---|
| 84 |
When you make a LEFT JOIN, Propel always hydrates the related object, even if there is *no* related object ! |
|---|
| 85 |
|
|---|
| 86 |
If there is no related object, you will get corrupted results. |
|---|
| 87 |
|
|---|
| 88 |
Let's see this schema, where a user can have a group, but it's not required : |
|---|
| 89 |
|
|---|
| 90 |
{{{ |
|---|
| 91 |
t_group: |
|---|
| 92 |
id: ~ |
|---|
| 93 |
name: { type: varchar(128), index: unique } |
|---|
| 94 |
t_user: |
|---|
| 95 |
id: ~ |
|---|
| 96 |
login: { type: varchar(128), index: unique } |
|---|
| 97 |
group_id: { type: integer, foreignTable: t_group, foreignReference: id, required: false } |
|---|
| 98 |
}}} |
|---|
| 99 |
|
|---|
| 100 |
{{{ |
|---|
| 101 |
<?php |
|---|
| 102 |
|
|---|
| 103 |
// Retrieve the user, joined with group, with explicit joins optimization activated |
|---|
| 104 |
// We will retrieve the user with ID = $id, and we know this user has no related group |
|---|
| 105 |
|
|---|
| 106 |
$criteria = new Criteria; |
|---|
| 107 |
$criteria->add(TUser::ID, $id); |
|---|
| 108 |
|
|---|
| 109 |
$users = TUserPeer::doSelectJoinTGroup($criteria); |
|---|
| 110 |
// Executed query : SELECT * FROM t_user JOIN t_group ON (t_user.group_id = t_group.id) WHERE t_user.id = $id |
|---|
| 111 |
|
|---|
| 112 |
$user = $users[0]; |
|---|
| 113 |
|
|---|
| 114 |
$group = $user->getGroup(); |
|---|
| 115 |
// Default behavior : $group is an instance of TGroup, and all its fields are NULL |
|---|
| 116 |
// Fixed behavior : $group is NULL |
|---|
| 117 |
}}} |
|---|
| 118 |
|
|---|
| 119 |
With this optimization, you will not retreive corrupted objects, when the object does not exist, you get NULL as you would expect it to be. |
|---|
| 120 |
|
|---|
| 121 |
=== Calls to Propel::import (bug) === |
|---|
| 122 |
|
|---|
| 123 |
This bug makes the overriding of plugins' model totally impossible, and adds a little overhead just like includes. |
|---|
| 124 |
|
|---|
| 125 |
If a plugin has a bundled schema with a package attribute different than "lib.model", because of this bug you will not be able to customize the model without touching the files directly located in the plugin's directory. |
|---|
| 126 |
|
|---|
| 127 |
This is caused by useless calls to Propel::import(). This optimization just removes all of them : they are fully useless as the autoloader handles the loading of model classes very better. |
|---|
| 128 |
|
|---|
| 129 |
== Configuration == |
|---|
| 130 |
|
|---|
| 131 |
All optimizations are activated when you don't specify anything. |
|---|
| 132 |
|
|---|
| 133 |
To disable an optimization, just add the corresponding option to your `config/propel.ini` : |
|---|
| 134 |
|
|---|
| 135 |
{{{ |
|---|
| 136 |
; Disable optimization "Includes overhead" |
|---|
| 137 |
propel.builder.addIncludes = true |
|---|
| 138 |
|
|---|
| 139 |
; Disable optimization "Explicit joins" |
|---|
| 140 |
propel.builder.implicitJoins = true |
|---|
| 141 |
|
|---|
| 142 |
; Disable optimization "LEFT JOINS and related objects hydrated with NULL values" |
|---|
| 143 |
propel.builder.hydrateNULLs = true |
|---|
| 144 |
|
|---|
| 145 |
; Disable optimization "Calls to Propel::import" |
|---|
| 146 |
propel.builder.addPropelImports = true |
|---|
| 147 |
}}} |
|---|