first commit
This commit is contained in:
7
plugins/nahoPropelOptimizerPlugin/LICENSE
Normal file
7
plugins/nahoPropelOptimizerPlugin/LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
(c) 2008 Nicolas Chambrier <naholyr@yahoo.fr>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
147
plugins/nahoPropelOptimizerPlugin/README
Normal file
147
plugins/nahoPropelOptimizerPlugin/README
Normal file
@@ -0,0 +1,147 @@
|
||||
= nahoPropelOptimizerPlugin plugin =
|
||||
|
||||
This `nahoPropelOptimizerPlugin` plugin fixes a few defects (not to say "bugs") in Propel model generation, that are not yet handled by Symfony builder wrapping.
|
||||
|
||||
== Instalation ==
|
||||
|
||||
* Install the plugin
|
||||
|
||||
{{{
|
||||
symfony plugin-install http://plugins.symfony-project.com/nahoPropelOptimizerPlugin
|
||||
}}}
|
||||
|
||||
Like any other plugin, you can either extract one of the attached archives, or use Subversion.
|
||||
|
||||
* Edit `config/propel.ini` and find the following lines :
|
||||
|
||||
{{{
|
||||
; builder settings
|
||||
propel.builder.peer.class = addon.propel.builder.SfPeerBuilder
|
||||
propel.builder.object.class = addon.propel.builder.SfObjectBuilder
|
||||
|
||||
propel.builder.objectstub.class = addon.propel.builder.SfExtensionObjectBuilder
|
||||
propel.builder.peerstub.class = addon.propel.builder.SfExtensionPeerBuilder
|
||||
propel.builder.objectmultiextend.class = addon.propel.builder.SfMultiExtendObjectBuilder
|
||||
propel.builder.mapbuilder.class = addon.propel.builder.SfMapBuilderBuilder
|
||||
}}}
|
||||
|
||||
And replace `addon.propel.builder.Sf` with `plugins.nahoPropelOptimizerPlugin.lib.SfOptimized` :
|
||||
|
||||
{{{
|
||||
; builder settings
|
||||
propel.builder.peer.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedPeerBuilder
|
||||
propel.builder.object.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedObjectBuilder
|
||||
|
||||
propel.builder.objectstub.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedExtensionObjectBuilder
|
||||
propel.builder.peerstub.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedExtensionPeerBuilder
|
||||
propel.builder.objectmultiextend.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedMultiExtendObjectBuilder
|
||||
propel.builder.mapbuilder.class = plugins.nahoPropelOptimizerPlugin.lib.SfOptimizedMapBuilderBuilder
|
||||
}}}
|
||||
|
||||
* Rebuild your model :
|
||||
|
||||
{{{
|
||||
$ symfony propel-build-model
|
||||
$ symfony cc
|
||||
}}}
|
||||
|
||||
Your model classes have been regenerated with all the supported optimizations. You're ready to go !
|
||||
|
||||
Default behavior is enabling all supported optimizations, see the "Configuration" section to disable some of them if you want.
|
||||
|
||||
== Optimizations ==
|
||||
|
||||
This section lists all the changes made by this custom builder.
|
||||
|
||||
=== Includes overhead (defect) ===
|
||||
|
||||
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.
|
||||
|
||||
`nahoPropelOptimizer` removes all these calls in Peer and MapBuilder classes.
|
||||
|
||||
=== Explicit joins (bug) ===
|
||||
|
||||
With the default builder, Peer classes join with an implicit JOIN. PostPeer::doSelectJoinAuthor will execute this query :
|
||||
|
||||
{{{
|
||||
SELECT * FROM post, author WHERE post.author_id = author.id AND ...
|
||||
}}}
|
||||
|
||||
Which is equivalent to :
|
||||
|
||||
{{{
|
||||
SELECT * FROM post INNER JOIN author ON (post.author_id = author.id) WHERE ...
|
||||
}}}
|
||||
|
||||
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`).
|
||||
|
||||
`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.
|
||||
|
||||
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 ;)
|
||||
|
||||
=== LEFT JOINS and related objects hydrated with NULL values (defect) ===
|
||||
|
||||
When you make a LEFT JOIN, Propel always hydrates the related object, even if there is *no* related object !
|
||||
|
||||
If there is no related object, you will get corrupted results.
|
||||
|
||||
Let's see this schema, where a user can have a group, but it's not required :
|
||||
|
||||
{{{
|
||||
t_group:
|
||||
id: ~
|
||||
name: { type: varchar(128), index: unique }
|
||||
t_user:
|
||||
id: ~
|
||||
login: { type: varchar(128), index: unique }
|
||||
group_id: { type: integer, foreignTable: t_group, foreignReference: id, required: false }
|
||||
}}}
|
||||
|
||||
{{{
|
||||
<?php
|
||||
|
||||
// Retrieve the user, joined with group, with explicit joins optimization activated
|
||||
// We will retrieve the user with ID = $id, and we know this user has no related group
|
||||
|
||||
$criteria = new Criteria;
|
||||
$criteria->add(TUser::ID, $id);
|
||||
|
||||
$users = TUserPeer::doSelectJoinTGroup($criteria);
|
||||
// Executed query : SELECT * FROM t_user JOIN t_group ON (t_user.group_id = t_group.id) WHERE t_user.id = $id
|
||||
|
||||
$user = $users[0];
|
||||
|
||||
$group = $user->getGroup();
|
||||
// Default behavior : $group is an instance of TGroup, and all its fields are NULL
|
||||
// Fixed behavior : $group is NULL
|
||||
}}}
|
||||
|
||||
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.
|
||||
|
||||
=== Calls to Propel::import (bug) ===
|
||||
|
||||
This bug makes the overriding of plugins' model totally impossible, and adds a little overhead just like includes.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
== Configuration ==
|
||||
|
||||
All optimizations are activated when you don't specify anything.
|
||||
|
||||
To disable an optimization, just add the corresponding option to your `config/propel.ini` :
|
||||
|
||||
{{{
|
||||
; Disable optimization "Includes overhead"
|
||||
propel.builder.addIncludes = true
|
||||
|
||||
; Disable optimization "Explicit joins"
|
||||
propel.builder.implicitJoins = true
|
||||
|
||||
; Disable optimization "LEFT JOINS and related objects hydrated with NULL values"
|
||||
propel.builder.hydrateNULLs = true
|
||||
|
||||
; Disable optimization "Calls to Propel::import"
|
||||
propel.builder.addPropelImports = true
|
||||
}}}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
include_once 'addon/propel/builder/SfExtensionObjectBuilder.php';
|
||||
|
||||
class SfOptimizedExtensionObjectBuilder extends SfExtensionObjectBuilder
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
include_once 'addon/propel/builder/SfExtensionPeerBuilder.php';
|
||||
|
||||
class SfOptimizedExtensionPeerBuilder extends SfExtensionPeerBuilder
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
include_once 'addon/propel/builder/SfMapBuilderBuilder.php';
|
||||
|
||||
class SfOptimizedMapBuilderBuilder extends SfMapBuilderBuilder
|
||||
{
|
||||
protected function addDoBuild(&$script)
|
||||
{
|
||||
parent::addDoBuild($script);
|
||||
|
||||
// $script = preg_replace("/\\\$tMap\->addColumn\('([^']+)', '([^']+)', '([^']+)', CreoleTypes\:\:DECIMAL, (false|true), ([0-9]+,[0-9]+)\)/e", '"\\\$tMap->addColumn(\'$1\', \'$2\', \'$3\', CreoleTypes::DECIMAL, $4, null)"', $script);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
include_once 'addon/propel/builder/SfMultiExtendObjectBuilder.php';
|
||||
|
||||
class SfOptimizedMultiExtendObjectBuilder extends SfMultiExtendObjectBuilder
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
<?php
|
||||
|
||||
include_once 'addon/propel/builder/SfObjectBuilder.php';
|
||||
|
||||
class SfOptimizedObjectBuilder extends SfObjectBuilder
|
||||
{
|
||||
protected function addAttributes(&$script)
|
||||
{
|
||||
$script .= "
|
||||
protected static \$dispatcher = null;
|
||||
";
|
||||
parent::addAttributes($script);
|
||||
}
|
||||
|
||||
protected function addCall(&$script)
|
||||
{
|
||||
$script .= "
|
||||
|
||||
public function getDispatcher()
|
||||
{
|
||||
if (null === self::\$dispatcher)
|
||||
{
|
||||
self::\$dispatcher = stEventDispatcher::getInstance();
|
||||
}
|
||||
|
||||
return self::\$dispatcher;
|
||||
}
|
||||
|
||||
public function __call(\$method, \$arguments)
|
||||
{
|
||||
\$event = \$this->getDispatcher()->notifyUntil(new sfEvent(\$this, '{$this->getStubObjectBuilder()->getClassname()}.' . \$method, array('arguments' => \$arguments, 'method' => \$method)));
|
||||
|
||||
if (\$event->isProcessed())
|
||||
{
|
||||
return \$event->getReturnValue();
|
||||
}
|
||||
|
||||
if (!\$callable = sfMixer::getCallable('{$this->getClassname()}:'.\$method))
|
||||
{
|
||||
throw new sfException(sprintf('Call to undefined method {$this->getClassname()}::%s', \$method));
|
||||
}
|
||||
|
||||
array_unshift(\$arguments, \$this);
|
||||
|
||||
return call_user_func_array(\$callable, \$arguments);
|
||||
}
|
||||
";
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
// Get original built code
|
||||
$objectCode = parent::build();
|
||||
|
||||
// Remove useless includes
|
||||
if (!DataModelBuilder::getBuildProperty('builderAddIncludes'))
|
||||
{
|
||||
//remove all inline includes:
|
||||
//peer class include inline the mapbuilder classes
|
||||
$objectCode = preg_replace("/include_once\s*.*Base.*Peer\.php.*\s*/", "", $objectCode);
|
||||
}
|
||||
|
||||
return $objectCode;
|
||||
}
|
||||
|
||||
protected function addFKAccessor(&$script, ForeignKey $fk)
|
||||
{
|
||||
// Make original modifications
|
||||
parent::addFKAccessor($script, $fk);
|
||||
|
||||
// With the explicit joins support, the related object returned can be hydrated with all NULL values, in this case we could simply return NULL
|
||||
if (!DataModelBuilder::getBuildProperty('builderHydrateNULLs'))
|
||||
{
|
||||
$varName = $this->getFKVarName($fk);
|
||||
$return = 'return $this->' . $varName . ';';
|
||||
$check_null_hydrated_script = '
|
||||
if (!is_null($this->' . $varName . ') && !$this->' . $varName . '->isNew() && is_null($this->' . $varName . '->getPrimaryKey())) {
|
||||
return NULL;
|
||||
}
|
||||
' . $return;
|
||||
$script = str_replace($return, $check_null_hydrated_script, $script);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds setter method for "normal" columns.
|
||||
* @param string &$script The script will be modified in this method.
|
||||
* @param Column $col The current column.
|
||||
* @see parent::addColumnMutators()
|
||||
*/
|
||||
protected function addDefaultMutator(&$script, Column $col)
|
||||
{
|
||||
$clo = strtolower($col->getName());
|
||||
|
||||
// FIXME: refactor this
|
||||
$defaultValue = null;
|
||||
if (($val = $col->getPhpDefaultValue()) !== null)
|
||||
{
|
||||
settype($val, $col->getPhpNative());
|
||||
$defaultValue = var_export($val, true);
|
||||
}
|
||||
|
||||
$this->addMutatorOpen($script, $col);
|
||||
|
||||
// Perform some smart checking here to handle possible type discrepancies
|
||||
// between the passed-in value and the value from the DB
|
||||
|
||||
if ($col->getPhpType() != 'object' && $col->getPhpType() != 'array')
|
||||
{
|
||||
if ($col->getPhpNative() === "int")
|
||||
{
|
||||
$script .= "
|
||||
if (\$v !== null && !is_int(\$v) && is_numeric(\$v)) {
|
||||
\$v = (int) \$v;
|
||||
}
|
||||
";
|
||||
} elseif ($col->getPhpNative() === "string")
|
||||
{
|
||||
$script .= "
|
||||
if (\$v !== null && !is_string(\$v)) {
|
||||
\$v = (string) \$v;
|
||||
}
|
||||
";
|
||||
} elseif ($col->getPhpNative() === "boolean")
|
||||
{
|
||||
$script .= "
|
||||
if (\$v !== null && !is_bool(\$v)) {
|
||||
\$v = (bool) \$v;
|
||||
}
|
||||
";
|
||||
}
|
||||
elseif ($col->getPhpNative() === "float" || $col->getPhpNative() === "double")
|
||||
{
|
||||
$script .= "
|
||||
if (\$v !== null && !is_float(\$v) && is_numeric(\$v)) {
|
||||
\$v = (float) \$v;
|
||||
}
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
$script .= "
|
||||
if (\$this->$clo !== \$v";
|
||||
if ($defaultValue !== null)
|
||||
{
|
||||
$script .= " || \$v === $defaultValue";
|
||||
}
|
||||
$script .= ") {
|
||||
\$this->$clo = \$v;
|
||||
\$this->modifiedColumns[] = ".$this->getColumnConstant($col).";
|
||||
}
|
||||
";
|
||||
$this->addMutatorClose($script, $col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a normal (non-temporal) getter method.
|
||||
* @param string &$script The script will be modified in this method.
|
||||
* @param Column $col The current column.
|
||||
* @see parent::addColumnAccessors()
|
||||
*/
|
||||
protected function addGenericAccessor(&$script, $col)
|
||||
{
|
||||
$cfc=$col->getPhpName();
|
||||
$clo=strtolower($col->getName());
|
||||
|
||||
$script .= "
|
||||
/**
|
||||
* Get the [$clo] column value.
|
||||
* ".$col->getDescription()."
|
||||
* @return ".$col->getPhpNative()."
|
||||
*/
|
||||
public function get$cfc(";
|
||||
if ($col->isLazyLoad()) $script .= "\$con = null";
|
||||
$script .= ")
|
||||
{
|
||||
";
|
||||
if ($col->isLazyLoad()) {
|
||||
$script .= "
|
||||
if (!\$this->".$clo."_isLoaded && \$this->$clo === null && !\$this->isNew()) {
|
||||
\$this->load$cfc(\$con);
|
||||
}
|
||||
";
|
||||
}
|
||||
if ($col->getType() == PropelTypes::DECIMAL)
|
||||
{
|
||||
$script .= "
|
||||
return null !== \$this->$clo ? (string)\$this->$clo : null;
|
||||
}
|
||||
";
|
||||
}
|
||||
else
|
||||
{
|
||||
$script .= "
|
||||
return \$this->$clo;
|
||||
}
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
protected function addDelete(&$script)
|
||||
{
|
||||
$script .= "
|
||||
/**
|
||||
* Removes this object from datastore and sets delete attribute.
|
||||
*
|
||||
* @param Connection \$con
|
||||
* @return void
|
||||
* @throws PropelException
|
||||
* @see BaseObject::setDeleted()
|
||||
* @see BaseObject::isDeleted()
|
||||
*/
|
||||
public function delete(\$con = null)
|
||||
{
|
||||
if (\$this->isDeleted()) {
|
||||
throw new PropelException(\"This object has already been deleted.\");
|
||||
}
|
||||
|
||||
if (\$this->getDispatcher()->getListeners('{$this->getStubObjectBuilder()->getClassname()}.preDelete')) {
|
||||
\$this->getDispatcher()->notify(new sfEvent(\$this, '{$this->getStubObjectBuilder()->getClassname()}.preDelete', array('con' => \$con)));
|
||||
}
|
||||
|
||||
if (sfMixer::hasCallables('{$this->getClassname()}:delete:pre'))
|
||||
{
|
||||
foreach (sfMixer::getCallables('{$this->getClassname()}:delete:pre') as \$callable)
|
||||
{
|
||||
\$ret = call_user_func(\$callable, \$this, \$con);
|
||||
if (\$ret)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\$con === null) {
|
||||
\$con = Propel::getConnection(".$this->getPeerClassname()."::DATABASE_NAME);
|
||||
}
|
||||
|
||||
try {
|
||||
\$con->begin();
|
||||
".$this->getPeerClassname()."::doDelete(\$this, \$con);
|
||||
\$this->setDeleted(true);
|
||||
\$con->commit();
|
||||
} catch (PropelException \$e) {
|
||||
\$con->rollback();
|
||||
throw \$e;
|
||||
}
|
||||
|
||||
if (\$this->getDispatcher()->getListeners('{$this->getStubObjectBuilder()->getClassname()}.postDelete')) {
|
||||
\$this->getDispatcher()->notify(new sfEvent(\$this, '{$this->getStubObjectBuilder()->getClassname()}.postDelete', array('con' => \$con)));
|
||||
}
|
||||
|
||||
if (sfMixer::hasCallables('{$this->getClassname()}:delete:post'))
|
||||
{
|
||||
foreach (sfMixer::getCallables('{$this->getClassname()}:delete:post') as \$callable)
|
||||
{
|
||||
call_user_func(\$callable, \$this, \$con);
|
||||
}
|
||||
}
|
||||
}
|
||||
";
|
||||
} // addDelete()
|
||||
|
||||
/**
|
||||
* Adds the save() method.
|
||||
* @param string &$script The script will be modified in this method.
|
||||
*/
|
||||
protected function addSave(&$script)
|
||||
{
|
||||
$updated = false;
|
||||
$created = false;
|
||||
$date_script = '';
|
||||
foreach ($this->getTable()->getColumns() as $col)
|
||||
{
|
||||
$clo = strtolower($col->getName());
|
||||
|
||||
if (!$updated && in_array($clo, array('updated_at', 'updated_on')))
|
||||
{
|
||||
$updated = true;
|
||||
$date_script .= "
|
||||
if (\$this->isModified() && !\$this->isColumnModified(".$this->getColumnConstant($col)."))
|
||||
{
|
||||
\$this->set".$col->getPhpName()."(time());
|
||||
}
|
||||
";
|
||||
}
|
||||
else if (!$created && in_array($clo, array('created_at', 'created_on')))
|
||||
{
|
||||
$created = true;
|
||||
$date_script .= "
|
||||
if (\$this->isNew() && !\$this->isColumnModified(".$this->getColumnConstant($col)."))
|
||||
{
|
||||
\$this->set".$col->getPhpName()."(time());
|
||||
}
|
||||
";
|
||||
}
|
||||
}
|
||||
$script .= "
|
||||
/**
|
||||
* Stores the object in the database. If the object is new,
|
||||
* it inserts it; otherwise an update is performed. This method
|
||||
* wraps the doSave() worker method in a transaction.
|
||||
*
|
||||
* @param Connection \$con
|
||||
* @return int The number of rows affected by this insert/update and any referring fk objects' save() operations.
|
||||
* @throws PropelException
|
||||
* @see doSave()
|
||||
*/
|
||||
public function save(\$con = null)
|
||||
{
|
||||
if (\$this->isDeleted()) {
|
||||
throw new PropelException(\"You cannot save an object that has been deleted.\");
|
||||
}
|
||||
|
||||
if (!\$this->alreadyInSave) {
|
||||
if (\$this->getDispatcher()->getListeners('{$this->getStubObjectBuilder()->getClassname()}.preSave')) {
|
||||
\$this->getDispatcher()->notify(new sfEvent(\$this, '{$this->getStubObjectBuilder()->getClassname()}.preSave', array('con' => \$con)));
|
||||
}
|
||||
|
||||
foreach (sfMixer::getCallables('{$this->getClassname()}:save:pre') as \$callable)
|
||||
{
|
||||
\$affectedRows = call_user_func(\$callable, \$this, \$con);
|
||||
if (is_int(\$affectedRows))
|
||||
{
|
||||
return \$affectedRows;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$date_script
|
||||
|
||||
if (\$con === null) {
|
||||
\$con = Propel::getConnection(".$this->getPeerClassname()."::DATABASE_NAME);
|
||||
}
|
||||
|
||||
try {
|
||||
\$con->begin();
|
||||
\$affectedRows = \$this->doSave(\$con);
|
||||
\$con->commit();
|
||||
|
||||
if (!\$this->alreadyInSave) {
|
||||
if (\$this->getDispatcher()->getListeners('{$this->getStubObjectBuilder()->getClassname()}.postSave')) {
|
||||
\$this->getDispatcher()->notify(new sfEvent(\$this, '{$this->getStubObjectBuilder()->getClassname()}.postSave', array('con' => \$con)));
|
||||
}
|
||||
|
||||
foreach (sfMixer::getCallables('{$this->getClassname()}:save:post') as \$callable)
|
||||
{
|
||||
call_user_func(\$callable, \$this, \$con, \$affectedRows);
|
||||
}
|
||||
}
|
||||
|
||||
return \$affectedRows;
|
||||
} catch (PropelException \$e) {
|
||||
\$con->rollback();
|
||||
throw \$e;
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the hydrate() method, which sets attributes of the object based on a ResultSet.
|
||||
*/
|
||||
protected function addHydrate(&$script)
|
||||
{
|
||||
$table = $this->getTable();
|
||||
|
||||
$script .= "
|
||||
/**
|
||||
* Hydrates (populates) the object variables with values from the database resultset.
|
||||
*
|
||||
* An offset (1-based \"start column\") is specified so that objects can be hydrated
|
||||
* with a subset of the columns in the resultset rows. This is needed, for example,
|
||||
* for results of JOIN queries where the resultset row includes columns from two or
|
||||
* more tables.
|
||||
*
|
||||
* @param ResultSet \$rs The ResultSet class with cursor advanced to desired record pos.
|
||||
* @param int \$startcol 1-based offset column which indicates which restultset column to start with.
|
||||
* @return int next starting column
|
||||
* @throws PropelException - Any caught Exception will be rewrapped as a PropelException.
|
||||
*/
|
||||
public function hydrate(ResultSet \$rs, \$startcol = 1)
|
||||
{
|
||||
try {
|
||||
if (\$this->getDispatcher()->getListeners('{$this->getStubObjectBuilder()->getClassname()}.preHydrate')) {
|
||||
\$event = \$this->getDispatcher()->notify(new sfEvent(\$this, '{$this->getStubObjectBuilder()->getClassname()}.preHydrate', array('resultset' => \$rs, 'startcol' => \$startcol)));
|
||||
\$startcol = \$event['startcol'];
|
||||
}
|
||||
";
|
||||
$n = 0;
|
||||
foreach($table->getColumns() as $col)
|
||||
{
|
||||
if(!$col->isLazyLoad())
|
||||
{
|
||||
$affix = CreoleTypes::getAffix(CreoleTypes::getCreoleCode($col->getType()));
|
||||
$clo = strtolower($col->getName());
|
||||
switch($col->getType())
|
||||
{
|
||||
|
||||
case PropelTypes::DATE:
|
||||
case PropelTypes::TIME:
|
||||
case PropelTypes::TIMESTAMP:
|
||||
$script .= "
|
||||
\$this->$clo = \$rs->get$affix(\$startcol + $n, null);
|
||||
";
|
||||
break;
|
||||
case PropelTypes::DECIMAL:
|
||||
$script .= "
|
||||
\$this->$clo = \$rs->getString(\$startcol + $n);
|
||||
if (null !== \$this->$clo && \$this->$clo == intval(\$this->$clo))
|
||||
{
|
||||
\$this->$clo = (string)intval(\$this->$clo);
|
||||
}
|
||||
";
|
||||
break;
|
||||
default:
|
||||
if ($col->getPhpType() == 'array' || $col->getPhpType() == 'object')
|
||||
{
|
||||
$script .= "
|
||||
\$this->$clo = \$rs->get$affix(\$startcol + $n) ? unserialize(\$rs->get$affix(\$startcol + $n)) : null;
|
||||
";
|
||||
}
|
||||
else
|
||||
{
|
||||
$script .= "
|
||||
\$this->$clo = \$rs->get$affix(\$startcol + $n);
|
||||
";
|
||||
}
|
||||
}
|
||||
$n++;
|
||||
} // if col->isLazyLoad()
|
||||
} /* foreach */
|
||||
|
||||
if ($this->getBuildProperty("addSaveMethod"))
|
||||
{
|
||||
$script .= "
|
||||
\$this->resetModified();
|
||||
";
|
||||
}
|
||||
|
||||
$script .= "
|
||||
\$this->setNew(false);
|
||||
if (\$this->getDispatcher()->getListeners('{$this->getStubObjectBuilder()->getClassname()}.postHydrate')) {
|
||||
\$event = \$this->getDispatcher()->notify(new sfEvent(\$this, '{$this->getStubObjectBuilder()->getClassname()}.postHydrate', array('resultset' => \$rs, 'startcol' => \$startcol + $n)));
|
||||
return \$event['startcol'];
|
||||
}
|
||||
|
||||
// FIXME - using NUM_COLUMNS may be clearer.
|
||||
return \$startcol + $n; // $n = ".$this->getPeerClassname()."::NUM_COLUMNS - ".$this->getPeerClassname()."::NUM_LAZY_LOAD_COLUMNS).
|
||||
|
||||
} catch (Exception \$e) {
|
||||
throw new PropelException(\"Error populating ".$table->getPhpName()." object\", \$e);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
} // addHydrate()
|
||||
|
||||
}
|
||||
450
plugins/nahoPropelOptimizerPlugin/lib/SfOptimizedPeerBuilder.php
Normal file
450
plugins/nahoPropelOptimizerPlugin/lib/SfOptimizedPeerBuilder.php
Normal file
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
|
||||
include_once 'addon/propel/builder/SfPeerBuilder.php';
|
||||
|
||||
class SfOptimizedPeerBuilder extends SfPeerBuilder
|
||||
{
|
||||
|
||||
protected function addDoSelectJoinAll(&$script)
|
||||
{
|
||||
$tmp = '';
|
||||
|
||||
parent::addDoSelectJoinAll($tmp);
|
||||
|
||||
$hydrator = "
|
||||
if (self::\$hydrateMethod)
|
||||
{
|
||||
return call_user_func(self::\$hydrateMethod, \$rs);
|
||||
}
|
||||
";
|
||||
|
||||
$tmp = str_replace('$results = array();', $hydrator.'$results = array();', $tmp);
|
||||
|
||||
$script .= str_replace('$results[] = $obj1;', "\$results[] = self::\$postHydrateMethod ? call_user_func(self::\$postHydrateMethod, \$obj1) : \$obj1;", $tmp);
|
||||
}
|
||||
|
||||
protected function addDoSelectWithI18n(&$script)
|
||||
{
|
||||
$table = $this->getTable();
|
||||
$thisTableObjectBuilder = OMBuilder::getNewObjectBuilder($table);
|
||||
$className = $table->getPhpName();
|
||||
$pks = $table->getPrimaryKey();
|
||||
$pk = PeerBuilder::getColumnName($pks[0], $className);
|
||||
|
||||
// get i18n table name and culture column name
|
||||
foreach ($table->getReferrers() as $fk)
|
||||
{
|
||||
$tblFK = $fk->getTable();
|
||||
if ($tblFK->getName() == $table->getAttribute('i18nTable'))
|
||||
{
|
||||
$i18nClassName = $tblFK->getPhpName();
|
||||
// FIXME
|
||||
$i18nPeerClassName = $i18nClassName.'Peer';
|
||||
|
||||
$i18nTable = $table->getDatabase()->getTable($tblFK->getName());
|
||||
$i18nTableObjectBuilder = OMBuilder::getNewObjectBuilder($i18nTable);
|
||||
$i18nTablePeerBuilder = OMBuilder::getNewPeerBuilder($i18nTable);
|
||||
$i18nPks = $i18nTable->getPrimaryKey();
|
||||
$i18nPk = PeerBuilder::getColumnName($i18nPks[0], $i18nClassName);
|
||||
|
||||
$culturePhpName = '';
|
||||
$cultureColumnName = '';
|
||||
foreach ($tblFK->getColumns() as $col)
|
||||
{
|
||||
if (("true" === strtolower($col->getAttribute('isCulture'))))
|
||||
{
|
||||
$culturePhpName = $col->getPhpName();
|
||||
$cultureColumnName = PeerBuilder::getColumnName($col, $i18nClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$script .= "
|
||||
|
||||
/**
|
||||
* Selects a collection of $className objects pre-filled with their i18n objects.
|
||||
*
|
||||
* @return array Array of $className objects.
|
||||
* @throws PropelException Any exceptions caught during processing will be
|
||||
* rethrown wrapped into a PropelException.
|
||||
*/
|
||||
public static function doSelectWithI18n(Criteria \$c, \$culture = null, \$con = null)
|
||||
{
|
||||
\$c = clone \$c;
|
||||
|
||||
if (\$culture === null)
|
||||
{
|
||||
\$culture = sfContext::getInstance()->getUser()->getCulture();
|
||||
}
|
||||
|
||||
// Set the correct dbName if it has not been overridden
|
||||
if (\$c->getDbName() == Propel::getDefaultDB())
|
||||
{
|
||||
\$c->setDbName(self::DATABASE_NAME);
|
||||
}
|
||||
|
||||
if (!\$c->getSelectColumns())
|
||||
{
|
||||
".$this->getPeerClassname()."::addSelectColumns(\$c);
|
||||
".$i18nPeerClassName."::addSelectColumns(\$c);
|
||||
}
|
||||
|
||||
\$c->addJoin(".$pk.", sprintf('%s AND %s = \'%s\'', ".$i18nPk.", ".$cultureColumnName.", \$culture), Criteria::LEFT_JOIN);
|
||||
|
||||
\$rs = ".$this->getPeerClassname()."::doSelectRs(\$c, \$con);
|
||||
|
||||
if (self::\$hydrateMethod)
|
||||
{
|
||||
return call_user_func(self::\$hydrateMethod, \$rs);
|
||||
}
|
||||
|
||||
\$results = array();
|
||||
|
||||
while(\$rs->next()) {
|
||||
";
|
||||
$script .= "
|
||||
\$obj1 = new ".$className."();
|
||||
\$startcol = \$obj1->hydrate(\$rs);
|
||||
\$obj1->setCulture(\$culture);
|
||||
";
|
||||
$script .= "
|
||||
\$obj2 = new ".$i18nClassName."();
|
||||
\$obj2->hydrate(\$rs, \$startcol);
|
||||
|
||||
\$obj1->set".$i18nClassName."ForCulture(\$obj2, \$culture);
|
||||
\$obj2->set".$className."(\$obj1);
|
||||
|
||||
\$results[] = self::\$postHydrateMethod ? call_user_func(self::\$postHydrateMethod, \$obj1) : \$obj1;
|
||||
}
|
||||
return \$results;
|
||||
}
|
||||
";
|
||||
}
|
||||
|
||||
protected function addDoSelectJoinAllExcept(&$script)
|
||||
{
|
||||
$tmp = '';
|
||||
|
||||
parent::addDoSelectJoinAllExcept($tmp);
|
||||
|
||||
$hydrator = "
|
||||
if (self::\$hydrateMethod)
|
||||
{
|
||||
return call_user_func(self::\$hydrateMethod, \$rs);
|
||||
}
|
||||
";
|
||||
|
||||
$tmp = str_replace('$results = array();', $hydrator.'$results = array();', $tmp);
|
||||
|
||||
$script .= str_replace('$results[] = $obj1;', "\$results[] = self::\$postHydrateMethod ? call_user_func(self::\$postHydrateMethod, \$obj1) : \$obj1;", $tmp);
|
||||
}
|
||||
|
||||
protected function addConstantsAndAttributes(&$script)
|
||||
{
|
||||
parent::addConstantsAndAttributes($script);
|
||||
|
||||
$script .="
|
||||
protected static \$hydrateMethod = null;
|
||||
|
||||
protected static \$postHydrateMethod = null;
|
||||
|
||||
public static function setHydrateMethod(\$callback)
|
||||
{
|
||||
self::\$hydrateMethod = \$callback;
|
||||
}
|
||||
|
||||
public static function setPostHydrateMethod(\$callback)
|
||||
{
|
||||
self::\$postHydrateMethod = \$callback;
|
||||
}
|
||||
";
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the addSelectColumns() method.
|
||||
* @param string &$script The script will be modified in this method.
|
||||
*/
|
||||
protected function addAddSelectColumns(&$script)
|
||||
{
|
||||
$script .= "
|
||||
/**
|
||||
* Add all the columns needed to create a new object.
|
||||
*
|
||||
* Note: any columns that were marked with lazyLoad=\"true\" in the
|
||||
* XML schema will not be added to the select list and only loaded
|
||||
* on demand.
|
||||
*
|
||||
* @param criteria object containing the columns to add.
|
||||
* @throws PropelException Any exceptions caught during processing will be
|
||||
* rethrown wrapped into a PropelException.
|
||||
*/
|
||||
public static function addSelectColumns(Criteria \$criteria)
|
||||
{
|
||||
";
|
||||
foreach ($this->getTable()->getColumns() as $col) {
|
||||
if (!$col->isLazyLoad()) {
|
||||
$script .= "
|
||||
\$criteria->addSelectColumn(".$this->getPeerClassname()."::".$this->getColumnName($col).");
|
||||
";
|
||||
} // if !col->isLazyLoad
|
||||
} // foreach
|
||||
$script .="
|
||||
|
||||
if (stEventDispatcher::getInstance()->getListeners('".$this->getPeerClassname().".postAddSelectColumns')) {
|
||||
stEventDispatcher::getInstance()->notify(new sfEvent(\$criteria, '".$this->getPeerClassname().".postAddSelectColumns'));
|
||||
}
|
||||
}
|
||||
";
|
||||
} // addAddSelectColumns()
|
||||
|
||||
protected function addPopulateObjects(&$script)
|
||||
{
|
||||
$tmp = '';
|
||||
parent::addPopulateObjects($tmp);
|
||||
|
||||
$hydrator = "
|
||||
if (self::\$hydrateMethod)
|
||||
{
|
||||
return call_user_func(self::\$hydrateMethod, \$rs);
|
||||
}
|
||||
";
|
||||
|
||||
$tmp = str_replace('$results = array();', $hydrator.'$results = array();', $tmp);
|
||||
|
||||
$script .= str_replace('$results[] = $obj;', "\$results[] = self::\$postHydrateMethod ? call_user_func(self::\$postHydrateMethod, \$obj) : \$obj;", $tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the doSelectRS() method.
|
||||
* @param string &$script The script will be modified in this method.
|
||||
*/
|
||||
protected function addDoSelectRS(&$script)
|
||||
{
|
||||
|
||||
$script .= "
|
||||
/**
|
||||
* Prepares the Criteria object and uses the parent doSelect()
|
||||
* method to get a ResultSet.
|
||||
*
|
||||
* Use this method directly if you want to just get the resultset
|
||||
* (instead of an array of objects).
|
||||
*
|
||||
* @param Criteria \$criteria The Criteria object used to build the SELECT statement.
|
||||
* @param Connection \$con the connection to use
|
||||
* @throws PropelException Any exceptions caught during processing will be
|
||||
* rethrown wrapped into a PropelException.
|
||||
* @return ResultSet The resultset object with numerically-indexed fields.
|
||||
* @see ".$this->basePeerClassname."::doSelect()
|
||||
*/
|
||||
public static function doSelectRS(Criteria \$criteria, \$con = null)
|
||||
{
|
||||
if (\$con === null) {
|
||||
\$con = Propel::getConnection(self::DATABASE_NAME);
|
||||
}
|
||||
|
||||
if (!\$criteria->getSelectColumns()) {
|
||||
\$criteria = clone \$criteria;
|
||||
".$this->getPeerClassname()."::addSelectColumns(\$criteria);
|
||||
}
|
||||
|
||||
if (stEventDispatcher::getInstance()->getListeners('BasePeer.preDoSelectRs')) {
|
||||
stEventDispatcher::getInstance()->notify(new sfEvent(\$criteria, 'BasePeer.preDoSelectRs'));
|
||||
}
|
||||
|
||||
// Set the correct dbName
|
||||
\$criteria->setDbName(self::DATABASE_NAME);
|
||||
|
||||
// BasePeer returns a Creole ResultSet, set to return
|
||||
// rows indexed numerically.
|
||||
\$rs = ".$this->basePeerClassname."::doSelect(\$criteria, \$con);
|
||||
|
||||
if (stEventDispatcher::getInstance()->getListeners('BasePeer.postDoSelectRs')) {
|
||||
stEventDispatcher::getInstance()->notify(new sfEvent(\$rs, 'BasePeer.postDoSelectRs'));
|
||||
}
|
||||
|
||||
return \$rs;
|
||||
}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the doSelectJoin*() methods.
|
||||
* @param string &$script The script will be modified in this method.
|
||||
*/
|
||||
protected function addDoSelectJoin(&$script)
|
||||
{
|
||||
$table = $this->getTable();
|
||||
$className = $table->getPhpName();
|
||||
$countFK = count($table->getForeignKeys());
|
||||
|
||||
if ($countFK >= 1) {
|
||||
|
||||
foreach ($table->getForeignKeys() as $fk) {
|
||||
|
||||
$joinTable = $table->getDatabase()->getTable($fk->getForeignTableName());
|
||||
|
||||
if (!$joinTable->isForReferenceOnly()) {
|
||||
|
||||
// FIXME - look into removing this next condition; it may not
|
||||
// be necessary:
|
||||
// --- IT is necessary because there needs to be a system for
|
||||
// aliasing the table if it is the same table.
|
||||
if ( $fk->getForeignTableName() != $table->getName() ) {
|
||||
|
||||
/*
|
||||
REPLACED BY USING THE ObjectBuilder objects below
|
||||
|
||||
// check to see if we need to add something to the method name.
|
||||
// For example if there are multiple columns that reference the same
|
||||
// table, then we have to have a methd name like doSelectJoinBooksByBookId
|
||||
$partJoinName = "";
|
||||
foreach ($fk->getLocalColumns() as $columnName ) {
|
||||
$column = $table->getColumn($columnName);
|
||||
// this second part is not currently ever true (right?)
|
||||
if ($column->isMultipleFK() || $fk->getForeignTableName() == $table->getName()) {
|
||||
$partJoinName = $partJoinName . $column->getPhpName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$joinClassName = $joinTable->getPhpName();
|
||||
|
||||
if ($joinTable->getInterface()) {
|
||||
$interfaceName = $joinTable->getInterface();
|
||||
} else {
|
||||
$interfaceName = $joinTable->getPhpName();
|
||||
}
|
||||
|
||||
if ($partJoinName == "") {
|
||||
$joinColumnId = $joinClassName;
|
||||
$joinInterface = $interfaceName;
|
||||
$collThisTable = $className . "s";
|
||||
$collThisTableMs = $className;
|
||||
} else {
|
||||
$joinColumnId = $joinClassName . "RelatedBy" . $partJoinName;
|
||||
$joinInterface = $interfaceName . "RelatedBy" . $partJoinName;
|
||||
$collThisTable = $className . "sRelatedBy" . $partJoinName;
|
||||
$collThisTableMs = $className . "RelatedBy" . $partJoinName;
|
||||
}
|
||||
*/
|
||||
|
||||
$joinClassName = $joinTable->getPhpName();
|
||||
|
||||
$thisTableObjectBuilder = OMBuilder::getNewObjectBuilder($table);
|
||||
$joinedTableObjectBuilder = OMBuilder::getNewObjectBuilder($joinTable);
|
||||
$joinedTablePeerBuilder = OMBuilder::getNewPeerBuilder($joinTable);
|
||||
|
||||
$script .= "
|
||||
|
||||
/**
|
||||
* Selects a collection of $className objects pre-filled with their $joinClassName objects.
|
||||
*
|
||||
* @return {$className}[] Array of $className objects.
|
||||
* @throws PropelException Any exceptions caught during processing will be
|
||||
* rethrown wrapped into a PropelException.
|
||||
*/
|
||||
public static function doSelectJoin".$thisTableObjectBuilder->getFKPhpNameAffix($fk, $plural = false)."(Criteria \$c, \$con = null)
|
||||
{
|
||||
\$c = clone \$c;
|
||||
|
||||
// Set the correct dbName if it has not been overridden
|
||||
if (\$c->getDbName() == Propel::getDefaultDB()) {
|
||||
\$c->setDbName(self::DATABASE_NAME);
|
||||
}
|
||||
|
||||
".$this->getPeerClassname()."::addSelectColumns(\$c);
|
||||
|
||||
".$joinedTablePeerBuilder->getPeerClassname()."::addSelectColumns(\$c);
|
||||
";
|
||||
$fk_columns = array();
|
||||
$lfMap = $fk->getLocalForeignMapping();
|
||||
foreach ($fk->getLocalColumns() as $columnName ) {
|
||||
$column = $table->getColumn($columnName);
|
||||
$fk_columns[] = $column->getPhpName().'()';
|
||||
$columnFk = $joinTable->getColumn( $lfMap[$columnName] );
|
||||
$script .= "
|
||||
\$c->addJoin(".$this->getColumnConstant($column).", ".$joinedTablePeerBuilder->getColumnConstant($columnFk).");"; //CHECKME
|
||||
}
|
||||
$script .= "
|
||||
\$rs = ".$this->getPeerClassname()."::doSelectRs(\$c, \$con);
|
||||
|
||||
if (self::\$hydrateMethod)
|
||||
{
|
||||
return call_user_func(self::\$hydrateMethod, \$rs);
|
||||
}
|
||||
|
||||
\$results = array();
|
||||
|
||||
while(\$rs->next()) {
|
||||
";
|
||||
$script .= "
|
||||
\$obj1 = new ".$className."();
|
||||
\$startcol = \$obj1->hydrate(\$rs);
|
||||
if (\$obj1->get".implode(' && $obj1->get', $fk_columns).")
|
||||
{
|
||||
";
|
||||
|
||||
$script .= "
|
||||
\$obj2 = new ".$joinClassName."();
|
||||
\$obj2->hydrate(\$rs, \$startcol);
|
||||
\$obj2->add".$joinedTableObjectBuilder->getRefFKPhpNameAffix($fk, $plural = false)."(\$obj1);
|
||||
}
|
||||
\$results[] = self::\$postHydrateMethod ? call_user_func(self::\$postHydrateMethod, \$obj1) : \$obj1;;
|
||||
}
|
||||
return \$results;
|
||||
}
|
||||
";
|
||||
} // if fk table name != this table name
|
||||
} // if ! is reference only
|
||||
} // foreach column
|
||||
} // if count(fk) > 1
|
||||
|
||||
} // addDoSelectJoin()
|
||||
|
||||
public function build()
|
||||
{
|
||||
// Get original built code
|
||||
$peerCode = parent::build();
|
||||
|
||||
// Remove useless includes
|
||||
if (!DataModelBuilder::getBuildProperty('builderAddIncludes'))
|
||||
{
|
||||
//remove all inline includes:
|
||||
//peer class include inline the mapbuilder classes
|
||||
$peerCode = preg_replace("/(include|require)_once\s*.*\.php.*\s*/", "", $peerCode);
|
||||
}
|
||||
|
||||
// Change implicit joins (all inner) to explicit INNER or LEFT, depending on the fact the key can be null or not
|
||||
if (!DataModelBuilder::getBuildProperty('builderImplicitJoins'))
|
||||
{
|
||||
foreach ($this->getTable()->getColumns() as $column)
|
||||
{
|
||||
if ($column->isForeignKey())
|
||||
{
|
||||
$colName = PeerBuilder::getColumnName($column, $this->getTable()->getPhpName());
|
||||
$from = '/->addJoin\('.preg_quote($colName, '/').'\s*,\s*([^,]*?)\)/';
|
||||
if ($column->isNotNull())
|
||||
{
|
||||
$to = '->addJoin('.$colName.', $1)';
|
||||
}
|
||||
else
|
||||
{
|
||||
$to = '->addJoin('.$colName.', $1, Criteria::LEFT_JOIN)';
|
||||
}
|
||||
$peerCode = preg_replace($from, $to, $peerCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove calls to Propel::import(), which prevent to extend plugin's model classes
|
||||
if (!DataModelBuilder::getBuildProperty('builderAddPropelImports'))
|
||||
{
|
||||
$from = '/Propel::import\((.*?)\)/';
|
||||
$to = 'substr($1, ($pos=strrpos($1,\'.\'))?$pos+1:0)';
|
||||
$peerCode = preg_replace($from, $to, $peerCode);
|
||||
}
|
||||
|
||||
return $peerCode;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user