propelorm/Propel2

src/Propel/Generator/Builder/Om/AbstractOMBuilder.php
F

High overall complexity: 171

<?php

/**
 * This file is part of the Propel package.
 * For the full copyright and license information, please view the LICENSE

When classes take on too many responsibilities, they grow. A large number of instance variables or methods can make a class hard to understand. Large classes tend to have lower cohesion and higher churn.

Often large classes have methods that do not operate on all of the class state. Identifying the groups of data that are used together can point to seams to split out additional collaborator classes or value objects.

Another trick is to look for repeated prefixes or suffixes in method and variable names, or repeated parameter names, and use them to guide extractions.

Refactorings

Further Reading

Complex method in extractCrossInformation

    protected function extractCrossInformation(
        CrossForeignKeys $crossFKs,
        $crossFKToIgnore = null,
        &$signature,
        &$shortSignature,

Long or complex methods can make code harder to understand. In most circumstances, methods are best as a small chunk of code (the "how") with a clear, understandable name (the "what"). Long methods can also lead to duplication, as it's harder to reuse logic that is tightly coupled to surrounding code.

Refactorings

Read More

Complex method in declareClassNamespace

    public function declareClassNamespace($class, $namespace = '', $alias = false)
    {
        $namespace = trim($namespace, '\\');

        // check if the class is already declared

Long or complex methods can make code harder to understand. In most circumstances, methods are best as a small chunk of code (the "how") with a clear, understandable name (the "what"). Long methods can also lead to duplication, as it's harder to reuse logic that is tightly coupled to surrounding code.

Refactorings

Read More

Complex method in getCrossFKInformation

    protected function getCrossFKInformation(CrossForeignKeys $crossFKs)
    {
        $names = [];
        $signatures = [];
        $shortSignature = [];

Long or complex methods can make code harder to understand. In most circumstances, methods are best as a small chunk of code (the "how") with a clear, understandable name (the "what"). Long methods can also lead to duplication, as it's harder to reuse logic that is tightly coupled to surrounding code.

Refactorings

Read More

Complex method in clean

    private function clean($content)
    {
        // line feed
        $content = str_replace("\r\n", "\n", $content);
     

Long or complex methods can make code harder to understand. In most circumstances, methods are best as a small chunk of code (the "how") with a clear, understandable name (the "what"). Long methods can also lead to duplication, as it's harder to reuse logic that is tightly coupled to surrounding code.

Refactorings

Read More

Complex method in getCrossFKAddMethodInformation

    protected function getCrossFKAddMethodInformation(CrossForeignKeys $crossFKs, $crossFK = null)
    {
        if ($crossFK instanceof ForeignKey) {
            $crossObjectName = '$' . lcfirst($this->getFKPhpNameAffix($crossFK));
            $crossObjectClassName = $this->getClassNameFromTable($crossFK->getForeignTable());

Long or complex methods can make code harder to understand. In most circumstances, methods are best as a small chunk of code (the "how") with a clear, understandable name (the "what"). Long methods can also lead to duplication, as it's harder to reuse logic that is tightly coupled to surrounding code.

Refactorings

Read More

Complex method in needAliasForClassName

    protected function needAliasForClassName($class, $namespace)
    {
        if ($namespace == $this->getNamespace()) {
            return false;
        }

Long or complex methods can make code harder to understand. In most circumstances, methods are best as a small chunk of code (the "how") with a clear, understandable name (the "what"). Long methods can also lead to duplication, as it's harder to reuse logic that is tightly coupled to surrounding code.

Refactorings

Read More

Complex method in getRefRelatedBySuffix

    protected static function getRefRelatedBySuffix(ForeignKey $fk)
    {
        $relCol = '';
        foreach ($fk->getMapping() as $mapping) {
            list($localColumn, $foreignValueOrColumn) = $mapping;

Long or complex methods can make code harder to understand. In most circumstances, methods are best as a small chunk of code (the "how") with a clear, understandable name (the "what"). Long methods can also lead to duplication, as it's harder to reuse logic that is tightly coupled to surrounding code.

Refactorings

Read More

File is too long

<?php

/**
 * This file is part of the Propel package.
 * For the full copyright and license information, please view the LICENSE
Severity: Minor
Found in src/Propel/Generator/Builder/Om/AbstractOMBuilder.php - About 3 days to fix

Class AbstractOMBuilder contains too many public methods

abstract class AbstractOMBuilder extends DataModelBuilder
{
    /**
     * Declared fully qualified classnames, to build the 'namespace' statements
     * according to this table's namespace.
Severity: Minor
Found in src/Propel/Generator/Builder/Om/AbstractOMBuilder.php - About 2 hrs to fix

Cyclomatic complexity for function declareClassNamespace is too high

    public function declareClassNamespace($class, $namespace = '', $alias = false)
    {
        $namespace = trim($namespace, '\\');

        // check if the class is already declared
Severity: Minor
Found in src/Propel/Generator/Builder/Om/AbstractOMBuilder.php - About 1 hr to fix

Too many parameters in definition of function extractCrossInformation

        CrossForeignKeys $crossFKs,
        $crossFKToIgnore = null,
        &$signature,
        &$shortSignature,
        &$normalizedShortSignature,
Severity: Minor
Found in src/Propel/Generator/Builder/Om/AbstractOMBuilder.php - About 40 mins to fix

Method declareClassNamespace is too long

    public function declareClassNamespace($class, $namespace = '', $alias = false)
    {
        $namespace = trim($namespace, '\\');

        // check if the class is already declared
Severity: Minor
Found in src/Propel/Generator/Builder/Om/AbstractOMBuilder.php - About 35 mins to fix

The variable name, declaredShortClassesOrAlias, is too long

    protected $declaredShortClassesOrAlias = [];
Severity: Minor
Found in src/Propel/Generator/Builder/Om/AbstractOMBuilder.php - About 5 mins to fix

The variable name, whiteListOfDeclaredClasses, is too long

    protected $whiteListOfDeclaredClasses = ['PDO', 'Exception', 'DateTime'];
Severity: Minor
Found in src/Propel/Generator/Builder/Om/AbstractOMBuilder.php - About 5 mins to fix

The variable name, fullyQualifiedClassName, is too long

        $fullyQualifiedClassName = trim($fullyQualifiedClassName, '\\');
Severity: Minor
Found in src/Propel/Generator/Builder/Om/AbstractOMBuilder.php - About 5 mins to fix

The variable name, normalizedShortSignature, is too long

        $normalizedShortSignature = implode(', ', $normalizedShortSignature);
Severity: Minor
Found in src/Propel/Generator/Builder/Om/AbstractOMBuilder.php - About 5 mins to fix

The variable name, foreignKeysToForeignTable, is too long

            $foreignKeysToForeignTable = $localTable->getForeignKeysReferencingTable($fk->getForeignTableName());
Severity: Minor
Found in src/Propel/Generator/Builder/Om/AbstractOMBuilder.php - About 5 mins to fix

There are no issues that match your filters.

<?php

/**
 * This file is part of the Propel package.
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @license MIT License
 */

namespace Propel\Generator\Builder\Om;

use Propel\Generator\Builder\DataModelBuilder;
use Propel\Generator\Builder\Util\PropelTemplate;
use Propel\Generator\Exception\InvalidArgumentException;
use Propel\Generator\Exception\LogicException;
use Propel\Generator\Exception\RuntimeException;
use Propel\Generator\Model\Column;
use Propel\Generator\Model\CrossForeignKeys;
use Propel\Generator\Model\ForeignKey;
use Propel\Generator\Model\Table;

/**
 * Baseclass for OM-building classes.
 *
 * OM-building classes are those that build a PHP (or other) class to service
 * a single table.  This includes Entity classes, Map classes,
 * Node classes, Nested Set classes, etc.
 *
 * @author Hans Lellelid <hans@xmpl.org>
 */
abstract class AbstractOMBuilder extends DataModelBuilder
{
    /**
     * Declared fully qualified classnames, to build the 'namespace' statements
     * according to this table's namespace.
     *
     * @var array
     */
    protected $declaredClasses = [];

    /**
     * Mapping between fully qualified classnames and their short classname or alias
     *
     * @var array
     */
    protected $declaredShortClassesOrAlias = [];

    /**
     * List of classes that can be use without alias when model don't have namespace
     *
     * @var array
     */
    protected $whiteListOfDeclaredClasses = ['PDO', 'Exception', 'DateTime'];

    /**
     * Builds the PHP source for current class and returns it as a string.
     *
     * This is the main entry point and defines a basic structure that classes should follow.
     * In most cases this method will not need to be overridden by subclasses.  This method
     * does assume that the output language is PHP code, so it will need to be overridden if
     * this is not the case.
     *
     * @return string The resulting PHP sourcecode.
     */
    public function build()
    {
        $this->validateModel();
        $this->declareClass($this->getFullyQualifiedClassName());

        $script = '';
        $this->addClassOpen($script);
        $this->addClassBody($script);
        $this->addClassClose($script);

        $ignoredNamespace = ltrim($this->getNamespace(), '\\');

        if ($useStatements = $this->getUseStatements($ignoredNamespace ?: 'namespace')) {
            $script = $useStatements . $script;
        }

        if ($namespaceStatement = $this->getNamespaceStatement()) {
            $script = $namespaceStatement . $script;
        }

        $script =  "<?php

" . $script;

        return $this->clean($script);
    }

    /**
     * Validates the current table to make sure that it won't
     * result in generated code that will not parse.
     *
     * This method may emit warnings for code which may cause problems
     * and will throw exceptions for errors that will definitely cause
     * problems.
     */
    protected function validateModel()
    {
        // Validation is currently only implemented in the subclasses.
    }

    /**
     * Creates a $obj = new Book(); code snippet. Can be used by frameworks, for instance, to
     * extend this behavior, e.g. initialize the object after creating the instance or so.
     *
     * @return string Some code
     */
    public function buildObjectInstanceCreationCode($objName, $clsName)
    {
        return "$objName = new $clsName();";
    }

    /**
     * Returns the qualified (prefixed) classname that is being built by the current class.
     * This method must be implemented by child classes.
     *
     * @return string
     */
    abstract public function getUnprefixedClassName();

    /**
     * Returns the unqualified classname (e.g. Book)
     *
     * @return string
     */
    public function getUnqualifiedClassName()
    {
        return $this->getUnprefixedClassName();
    }

    /**
     * Returns the qualified classname (e.g. Model\Book)
     *
     * @return string
     */
    public function getQualifiedClassName()
    {
        if ($namespace = $this->getNamespace()) {
            return $namespace . '\\' . $this->getUnqualifiedClassName();
        }

        return $this->getUnqualifiedClassName();
    }

    /**
     * Returns the fully qualified classname (e.g. \Model\Book)
     *
     * @return string
     */
    public function getFullyQualifiedClassName()
    {
        return '\\' . $this->getQualifiedClassName();
    }
    /**
     * Returns FQCN alias of getFullyQualifiedClassName
     *
     * @return string
     */
    public function getClassName()
    {
        return $this->getFullyQualifiedClassName();
    }

    /**
     * Gets the dot-path representation of current class being built.
     *
     * @return string
     */
    public function getClasspath()
    {
        if ($this->getPackage()) {
            return $this->getPackage() . '.' . $this->getUnqualifiedClassName();
        }

        return $this->getUnqualifiedClassName();
    }

    /**
     * Gets the full path to the file for the current class.
     *
     * @return string
     */
    public function getClassFilePath()
    {
        return ClassTools::createFilePath($this->getPackagePath(), $this->getUnqualifiedClassName());
    }

    /**
     * Gets package name for this table.
     * This is overridden by child classes that have different packages.
     * @return string
     */
    public function getPackage()
    {
        $pkg = ($this->getTable()->getPackage() ? $this->getTable()->getPackage() : $this->getDatabase()->getPackage());
        if (!$pkg) {
            $pkg = $this->getBuildProperty('generator.targetPackage');
        }

        return $pkg;
    }

    /**
     * Returns filesystem path for current package.
     * @return string
     */
    public function getPackagePath()
    {
        $pkg = $this->getPackage();

        if (false !== strpos($pkg, '/')) {
            $pkg = preg_replace('#\.(map|om)$#', '/\1', $pkg);
            $pkg = preg_replace('#\.(Map|Om)$#', '/\1', $pkg);

            return $pkg;
        }

        $path = $pkg;

        $path = str_replace('...', '$$/', $path);
        $path = strtr(ltrim($path, '.'), '.', '/');
        $path = str_replace('$$/', '../', $path);

        return $path;
    }

    /**
     * Returns the user-defined namespace for this table,
     * or the database namespace otherwise.
     *
     * @return string
     */
    public function getNamespace()
    {
        return $this->getTable()->getNamespace();
    }

    /**
     * This declares the class use and returns the correct name to use (short classname, Alias, or FQCN)
     *
     * @param  AbstractOMBuilder $builder
     * @param  boolean           $fqcn    true to return the $fqcn classname
     * @return string            ClassName, Alias or FQCN
     */
    public function getClassNameFromBuilder($builder, $fqcn = false)
    {
        if ($fqcn) {
            return $builder->getFullyQualifiedClassName();
        }

        $namespace = $builder->getNamespace();
        $class = $builder->getUnqualifiedClassName();

        if (isset($this->declaredClasses[$namespace])
            && isset($this->declaredClasses[$namespace][$class])) {
            return $this->declaredClasses[$namespace][$class];
        }

        return $this->declareClassNamespace($class, $namespace, true);
    }

    /**
     * This declares the class use and returns the correct name to use
     *
     * @param Table $table
     * @param bool $fqcn
     * @return string
     */
    public function getClassNameFromTable(Table $table, $fqcn = false)
    {
        $namespace = $table->getNamespace();
        $class = $table->getPhpName();

        return $this->declareClassNamespace($class, $namespace, true);
    }

    /**
     * Declare a class to be use and return it's name or it's alias
     *
     * @param  string         $class     the class name
     * @param  string         $namespace the namespace
     * @param  string|boolean $alias     the alias wanted, if set to True, it automatically adds an alias when needed
     * @return string         the class name or it's alias
     */
    public function declareClassNamespace($class, $namespace = '', $alias = false)
    {
        $namespace = trim($namespace, '\\');

        // check if the class is already declared
        if (isset($this->declaredClasses[$namespace])
            && isset($this->declaredClasses[$namespace][$class])) {
            return $this->declaredClasses[$namespace][$class];
        }

        $forcedAlias = $this->needAliasForClassName($class, $namespace);

        if (false === $alias || true === $alias || null === $alias) {
            $aliasWanted = $class;
            $alias = $alias || $forcedAlias;
        } else {
            $aliasWanted = $alias;
            $forcedAlias = false;
        }

        if (!$forcedAlias && !isset($this->declaredShortClassesOrAlias[$aliasWanted])) {
            if (!isset($this->declaredClasses[$namespace])) {
                $this->declaredClasses[$namespace] = [];
            }

            $this->declaredClasses[$namespace][$class] = $aliasWanted;
            $this->declaredShortClassesOrAlias[$aliasWanted] = $namespace . '\\' . $class;

            return $aliasWanted;
        }

        // we have a duplicate class and asked for an automatic Alias
        if (false !== $alias) {
            if ('\\Base' == substr($namespace, -5) || 'Base' == $namespace) {
                return $this->declareClassNamespace($class, $namespace, 'Base' . $class);
            }

            if ('Child' == substr($alias, 0, 5)) {
                //we already requested Child.$class and its in use too,
                //so use the fqcn
                return ($namespace ? '\\' . $namespace : '') .  '\\' . $class;
            } else {
                $autoAliasName = 'Child' . $class;
            }

            return $this->declareClassNamespace($class, $namespace, $autoAliasName);
        }

        throw new LogicException(sprintf(
            'The class %s duplicates the class %s and can\'t be used without alias',
            $namespace . '\\' . $class,
            $this->declaredShortClassesOrAlias[$aliasWanted]
        ));
    }

    /**
     * check if the current $class need an alias or if the class could be used with a shortname without conflict
     *
     * @param string $class
     * @param string $namespace
     * @return boolean
     */
    protected function needAliasForClassName($class, $namespace)
    {
        if ($namespace == $this->getNamespace()) {
            return false;
        }

        if (str_replace('\\Base', '', $namespace) == str_replace('\\Base', '', $this->getNamespace())) {
            return true;
        }

        if (empty($namespace) && 'Base' === $this->getNamespace()) {
            if (str_replace(['Query'], '', $class) == str_replace(['Query'], '', $this->getUnqualifiedClassName())) {
                return true;
            }

            if ((false !== strpos($class, 'Query'))) {
                return true;
            }

            // force alias for model without namespace
            if (false === array_search($class, $this->whiteListOfDeclaredClasses, true)) {
                return true;
            }
        }

        if ('Base' === $namespace && '' === $this->getNamespace()) {
            // force alias for model without namespace
            if (false === array_search($class, $this->whiteListOfDeclaredClasses, true)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Declare a use statement for a $class with a $namespace and an $aliasPrefix
     * This return the short ClassName or an alias
     *
     * @param  string $class       the class
     * @param  string $namespace   the namespace
     * @param  mixed  $aliasPrefix optionally an alias or True to force an automatic alias prefix (Base or Child)
     * @return string the short ClassName or an alias
     */
    public function declareClassNamespacePrefix($class, $namespace = '', $aliasPrefix = false)
    {
        if (false !== $aliasPrefix && true !== $aliasPrefix) {
            $alias = $aliasPrefix . $class;
        } else {
            $alias = $aliasPrefix;
        }

        return $this->declareClassNamespace($class, $namespace, $alias);
    }

    /**
     * Declare a Fully qualified classname with an $aliasPrefix
     * This return the short ClassName to use or an alias
     *
     * @param  string $fullyQualifiedClassName the fully qualified classname
     * @param  mixed  $aliasPrefix             optionally an alias or True to force an automatic alias prefix (Base or Child)
     * @return string the short ClassName or an alias
     */
    public function declareClass($fullyQualifiedClassName, $aliasPrefix = false)
    {
        $fullyQualifiedClassName = trim($fullyQualifiedClassName, '\\');
        if (($pos = strrpos($fullyQualifiedClassName, '\\')) !== false) {
            return $this->declareClassNamespacePrefix(substr($fullyQualifiedClassName, $pos + 1), substr($fullyQualifiedClassName, 0, $pos), $aliasPrefix);
        }
        // root namespace
        return $this->declareClassNamespacePrefix($fullyQualifiedClassName, '', $aliasPrefix);
    }

    /**
     * @param  self           $builder
     * @param  boolean|string $aliasPrefix the prefix for the Alias or True for auto generation of the Alias
     * @return string
     */
    public function declareClassFromBuilder(self $builder, $aliasPrefix = false)
    {
        return $this->declareClassNamespacePrefix($builder->getUnqualifiedClassName(), $builder->getNamespace(), $aliasPrefix);
    }

    public function declareClasses()
    {
        $args = func_get_args();
        foreach ($args as $class) {
            $this->declareClass($class);
        }
    }

    /**
     * Get the list of declared classes for a given $namespace or all declared classes
     *
     * @param  string $namespace the namespace or null
     * @return array  list of declared classes
     */
    public function getDeclaredClasses($namespace = null)
    {
        if (null !== $namespace && isset($this->declaredClasses[$namespace])) {
            return $this->declaredClasses[$namespace];
        }

        return $this->declaredClasses;
    }

    /**
     * return the string for the class namespace
     *
     * @return string
     */
    public function getNamespaceStatement()
    {
        $namespace = $this->getNamespace();
        if (!empty($namespace)) {
            return sprintf("namespace %s;

", $namespace);
        }
    }

    /**
     * Return all the use statement of the class
     *
     * @param  string $ignoredNamespace the ignored namespace
     * @return string
     */
    public function getUseStatements($ignoredNamespace = null)
    {
        $script = '';
        $declaredClasses = $this->declaredClasses;
        unset($declaredClasses[$ignoredNamespace]);
        ksort($declaredClasses);
        foreach ($declaredClasses as $namespace => $classes) {
            asort($classes);
            foreach ($classes as $class => $alias) {
                // Don't use our own class
                if ($class == $this->getUnqualifiedClassName() && $namespace == $this->getNamespace()) {
                    continue;
                }
                if ($class == $alias) {
                    $script .= sprintf("use %s\\%s;
", $namespace, $class);
                } else {
                    $script .= sprintf("use %s\\%s as %s;
", $namespace, $class, $alias);
                }
            }
        }

        return $script;
    }

    /**
     * Shortcut method to return the [stub] query classname for current table.
     * This is the classname that is used whenever object or tablemap classes want
     * to invoke methods of the query classes.
     * @param  boolean $fqcn
     * @return string  (e.g. 'Myquery')
     */
    public function getQueryClassName($fqcn = false)
    {
        return $this->getClassNameFromBuilder($this->getStubQueryBuilder(), $fqcn);
    }

    /**
     * Returns the object classname for current table.
     * This is the classname that is used whenever object or tablemap classes want
     * to invoke methods of the object classes.
     * @param  boolean $fqcn
     * @return string  (e.g. 'MyTable' or 'ChildMyTable')
     */
    public function getObjectClassName($fqcn = false)
    {
        return $this->getClassNameFromBuilder($this->getStubObjectBuilder(), $fqcn);
    }

    /**
     * Returns always the final unqualified object class name. This is only useful for documentation/phpdoc,
     * not in the actual code.
     *
     * @return string
     */
    public function getObjectName()
    {
        return $this->getStubObjectBuilder()->getUnqualifiedClassName();
    }

    /**
     * Returns the tableMap classname for current table.
     * This is the classname that is used whenever object or tablemap classes want
     * to invoke methods of the object classes.
     * @param  boolean $fqcn
     * @return string  (e.g. 'My')
     */
    public function getTableMapClassName($fqcn = false)
    {
        return $this->getClassNameFromBuilder($this->getTableMapBuilder(), $fqcn);
    }

    /**
     * Get the column constant name (e.g. TableMapName::COLUMN_NAME).
     *
     * @param Column $col       The column we need a name for.
     * @param string $classname The TableMap classname to use.
     *
     * @return string If $classname is provided, then will return $classname::COLUMN_NAME; if not, then the TableMapName is looked up for current table to yield $currTableTableMap::COLUMN_NAME.
     */
    public function getColumnConstant($col, $classname = null)
    {
        if (null === $col) {
            throw new InvalidArgumentException('No columns were specified.');
        }

        if (null === $classname) {
            return $this->getBuildProperty('generator.objectModel.classPrefix') . $col->getFQConstantName();
        }

        // was it overridden in schema.xml ?
        if ($col->getTableMapName()) {
            $const = strtoupper($col->getTableMapName());
        } else {
            $const = strtoupper($col->getName());
        }

        return $classname.'::'.Column::CONSTANT_PREFIX.$const;
    }

    /**
     * Convenience method to get the default Join Type for a relation.
     * If the key is required, an INNER JOIN will be returned, else a LEFT JOIN will be suggested,
     * unless the schema is provided with the DefaultJoin attribute, which overrules the default Join Type
     *
     * @param  ForeignKey $fk
     * @return string
     */
    protected function getJoinType(ForeignKey $fk)
    {
        if ($defaultJoin = $fk->getDefaultJoin()) {
            return "'" . $defaultJoin . "'";
        }

        if ($fk->isLocalColumnsRequired()) {
            return 'Criteria::INNER_JOIN';
        }

        return 'Criteria::LEFT_JOIN';
    }

    /**
     * Gets the PHP method name affix to be used for fkeys for the current table (not referrers to this table).
     *
     * The difference between this method and the getRefFKPhpNameAffix() method is that in this method the
     * classname in the affix is the foreign table classname.
     *
     * @param  ForeignKey $fk     The local FK that we need a name for.
     * @param  boolean    $plural Whether the php name should be plural (e.g. initRelatedObjs() vs. addRelatedObj()
     * @return string
     */
    public function getFKPhpNameAffix(ForeignKey $fk, $plural = false)
    {
        if ($fk->getPhpName()) {
            if ($plural) {
                return $this->getPluralizer()->getPluralForm($fk->getPhpName());
            }

            return $fk->getPhpName();
        }

        $className = $fk->getForeignTable()->getPhpName();
        if ($plural) {
            $className = $this->getPluralizer()->getPluralForm($className);
        }

        return $className . $this->getRelatedBySuffix($fk);
    }

    /**
     * @param  CrossForeignKeys $crossFKs
     * @param  bool             $plural
     * @return string
     */
    protected function getCrossFKsPhpNameAffix(CrossForeignKeys $crossFKs, $plural = true)
    {
        $names = [];

        if ($plural) {
            if ($crossFKs->getUnclassifiedPrimaryKeys()) {
                //we have a non fk as pk as well, so we need to make pluralisation on our own and can't
                //rely on getFKPhpNameAffix's pluralisation
                foreach ($crossFKs->getCrossForeignKeys() as $fk) {
                    $names[] = $this->getFKPhpNameAffix($fk, false);
                }
            } else {
                //we have only fks, so give us names with plural and return those
                $lastIdx = count($crossFKs->getCrossForeignKeys()) - 1;
                foreach ($crossFKs->getCrossForeignKeys() as $idx => $fk) {
                    $needPlural = $idx === $lastIdx; //only last fk should be plural
                    $names[] = $this->getFKPhpNameAffix($fk, $needPlural);
                }

                return implode($names);
            }
        } else {
            // no plural, so $plural=false
            foreach ($crossFKs->getCrossForeignKeys() as $fk) {
                $names[] = $this->getFKPhpNameAffix($fk, false);
            }
        }

        foreach ($crossFKs->getUnclassifiedPrimaryKeys() as $pk) {
            $names[] = $pk->getPhpName();
        }

        $name = implode($names);

        return (true === $plural ? $this->getPluralizer()->getPluralForm($name) : $name);
    }

    /**
     * @param  CrossForeignKeys $crossFKs
     * @param  ForeignKey       $excludeFK
     * @return string
     */
    protected function getCrossRefFKGetterName(CrossForeignKeys $crossFKs, ForeignKey $excludeFK)
    {
        $names = [];

        $fks = $crossFKs->getCrossForeignKeys();

        foreach ($crossFKs->getMiddleTable()->getForeignKeys() as $fk) {
            if ($fk !== $excludeFK && ($fk === $crossFKs->getIncomingForeignKey() || in_array($fk, $fks))) {
                $names[] = $this->getFKPhpNameAffix($fk, false);
            }
        }

        foreach ($crossFKs->getUnclassifiedPrimaryKeys() as $pk) {
            $names[] = $pk->getPhpName();
        }

        $name = implode($names);

        return $this->getPluralizer()->getPluralForm($name);
    }

    /**
     * @param CrossForeignKeys $crossFKs
     * @return array
     */
    protected function getCrossFKInformation(CrossForeignKeys $crossFKs)
    {
        $names = [];
        $signatures = [];
        $shortSignature = [];
        $phpDoc = [];

        foreach ($crossFKs->getCrossForeignKeys() as $fk) {
            $crossObjectName  = '$' . lcfirst($this->getFKPhpNameAffix($fk));
            $crossObjectClassName  = $this->getNewObjectBuilder($fk->getForeignTable())->getObjectClassName();

            $names[] = $crossObjectClassName;
            $signatures[] = "$crossObjectClassName $crossObjectName" . ($fk->isAtLeastOneLocalColumnRequired() ? '' : ' = null');
            $shortSignature[] = $crossObjectName;
            $phpDoc[] = "
     * @param $crossObjectClassName $crossObjectName The object to relate";
        }

        $names = implode(', ', $names). (1 < count($names) ? ' combination' : '');
        $phpDoc = implode($phpDoc);
        $signatures = implode(', ', $signatures);
        $shortSignature = implode(', ', $shortSignature);

        return [
            $names,
            $phpDoc,
            $signatures,
            $shortSignature
        ];
    }

    /**
     * @param  CrossForeignKeys $crossFKs
     * @param  array|ForeignKey $crossFK  will be the first variable defined
     * @return array
     */
    protected function getCrossFKAddMethodInformation(CrossForeignKeys $crossFKs, $crossFK = null)
    {
        if ($crossFK instanceof ForeignKey) {
            $crossObjectName = '$' . lcfirst($this->getFKPhpNameAffix($crossFK));
            $crossObjectClassName = $this->getClassNameFromTable($crossFK->getForeignTable());
            $signature[] = "$crossObjectClassName $crossObjectName" . ($crossFK->isAtLeastOneLocalColumnRequired() ? '' : ' = null');
            $shortSignature[] = $crossObjectName;
            $normalizedShortSignature[] = $crossObjectName;
            $phpDoc[] = "
     * @param $crossObjectClassName $crossObjectName";
        }

        $this->extractCrossInformation($crossFKs, $crossFK, $signature, $shortSignature, $normalizedShortSignature, $phpDoc);

        $signature = implode(', ', $signature);
        $shortSignature = implode(', ', $shortSignature);
        $normalizedShortSignature = implode(', ', $normalizedShortSignature);
        $phpDoc = implode(', ', $phpDoc);

        return [$signature, $shortSignature, $normalizedShortSignature, $phpDoc];
    }

    /**
     * Extracts some useful information from a CrossForeignKeys object.
     *
     * @param CrossForeignKeys $crossFKs
     * @param array|ForeignKey $crossFKToIgnore
     * @param array            $signature
     * @param array            $shortSignature
     * @param array            $normalizedShortSignature
     * @param array            $phpDoc
     */
    protected function extractCrossInformation(
        CrossForeignKeys $crossFKs,
        $crossFKToIgnore = null,
        &$signature,
        &$shortSignature,
        &$normalizedShortSignature,
        &$phpDoc
    ) {
        foreach ($crossFKs->getCrossForeignKeys() as $fk) {
            if (is_array($crossFKToIgnore) && in_array($fk, $crossFKToIgnore)) {
                continue;
            } else if ($fk === $crossFKToIgnore) {
                continue;
            }

            $phpType = $typeHint = $this->getClassNameFromTable($fk->getForeignTable());
            $name = '$' . lcfirst($this->getFKPhpNameAffix($fk));

            $normalizedShortSignature[] = $name;

            $signature[] = ($typeHint ? "$typeHint " : '') . $name;
            $shortSignature[] = $name;
            $phpDoc[] = "
     * @param $phpType $name";
        }

        foreach ($crossFKs->getUnclassifiedPrimaryKeys() as $primaryKey) {
            //we need to add all those $primaryKey s as additional parameter as they are needed
            //to create the entry in the middle-table.
            $defaultValue = $primaryKey->getDefaultValueString();

            $phpType = $primaryKey->getPhpType();
            $typeHint = $primaryKey->isPhpArrayType() ? 'array' : '';
            $name = '$' . lcfirst($primaryKey->getPhpName());

            $normalizedShortSignature[] = $name;
            $signature[] = ($typeHint ? "$typeHint " : '') . $name . ('null' !== $defaultValue ? " = $defaultValue" : '');
            $shortSignature[] = $name;
            $phpDoc[] = "
     * @param $phpType $name";
        }

    }


    /**
     * @param  CrossForeignKeys $crossFKs
     * @return string
     */
    protected function getCrossFKsVarName(CrossForeignKeys $crossFKs)
    {
        return 'coll' . $this->getCrossFKsPhpNameAffix($crossFKs);
    }

    /**
     * @param  ForeignKey $crossFK
     * @return string
     */
    protected function getCrossFKVarName(ForeignKey $crossFK)
    {
        return 'coll' . $this->getFKPhpNameAffix($crossFK, true);
    }

    /**
     * Gets the "RelatedBy*" suffix (if needed) that is attached to method and variable names.
     *
     * The related by suffix is based on the local columns of the foreign key.  If there is more than
     * one column in a table that points to the same foreign table, then a 'RelatedByLocalColName' suffix
     * will be appended.
     *
     * @return string
     */
    protected static function getRelatedBySuffix(ForeignKey $fk)
    {
        $relCol = '';

        foreach ($fk->getMapping() as $mapping) {
            list($localColumn, $foreignValueOrColumn) = $mapping;
            $localColumnName = $localColumn->getPhpName();
            $localTable  = $fk->getTable();
            if (!$localColumn) {
                throw new RuntimeException(sprintf('Could not fetch column: %s in table %s.', $localColumnName, $localTable->getName()));
            }

            if (count($localTable->getForeignKeysReferencingTable($fk->getForeignTableName())) > 1
             || count($fk->getForeignTable()->getForeignKeysReferencingTable($fk->getTableName())) > 0
             || $fk->getForeignTableName() == $fk->getTableName()) {
                // self referential foreign key, or several foreign keys to the same table, or cross-reference fkey
                $relCol .= $localColumn->getPhpName();
            }
        }

        if (!empty($relCol)) {
            $relCol = 'RelatedBy' . $relCol;
        }

        return $relCol;
    }

    /**
     * Gets the PHP method name affix to be used for referencing foreign key methods and variable names (e.g. set????(), $coll???).
     *
     * The difference between this method and the getFKPhpNameAffix() method is that in this method the
     * classname in the affix is the classname of the local fkey table.
     *
     * @param  ForeignKey $fk     The referrer FK that we need a name for.
     * @param  boolean    $plural Whether the php name should be plural (e.g. initRelatedObjs() vs. addRelatedObj()
     * @return string
     */
    public function getRefFKPhpNameAffix(ForeignKey $fk, $plural = false)
    {
        $pluralizer = $this->getPluralizer();
        if ($fk->getRefPhpName()) {
            return $plural ? $pluralizer->getPluralForm($fk->getRefPhpName()) : $fk->getRefPhpName();
        }

        $className = $fk->getTable()->getPhpName();
        if ($plural) {
            $className = $pluralizer->getPluralForm($className);
        }

        return $className . $this->getRefRelatedBySuffix($fk);
    }

    protected static function getRefRelatedBySuffix(ForeignKey $fk)
    {
        $relCol = '';
        foreach ($fk->getMapping() as $mapping) {
            list($localColumn, $foreignValueOrColumn) = $mapping;
            $localColumnName = $localColumn->getPhpName();
            $localTable = $fk->getTable();
            if (!$localColumn) {
                throw new RuntimeException(sprintf('Could not fetch column: %s in table %s.', $localColumnName, $localTable->getName()));
            }
            $foreignKeysToForeignTable = $localTable->getForeignKeysReferencingTable($fk->getForeignTableName());
            if ($foreignValueOrColumn instanceof Column && $fk->getForeignTableName() == $fk->getTableName()) {
                $foreignColumnName = $foreignValueOrColumn->getPhpName();
                // self referential foreign key
                $relCol .= $foreignColumnName;
                if (count($foreignKeysToForeignTable) > 1) {
                    // several self-referential foreign keys
                    $relCol .= array_search($fk, $foreignKeysToForeignTable);
                }
            } elseif (count($foreignKeysToForeignTable) > 1 || count($fk->getForeignTable()->getForeignKeysReferencingTable($fk->getTableName())) > 0) {
                // several foreign keys to the same table, or symmetrical foreign key in foreign table
                $relCol .= $localColumn->getPhpName();
            }
        }

        if (!empty($relCol)) {
            $relCol = 'RelatedBy' . $relCol;
        }

        return $relCol;
    }

    /**
     * Checks whether any registered behavior on that table has a modifier for a hook
     * @param  string  $hookName The name of the hook as called from one of this class methods, e.g. "preSave"
     * @param  string  $modifier The name of the modifier object providing the method in the behavior
     * @return boolean
     */
    public function hasBehaviorModifier($hookName, $modifier)
    {
        $modifierGetter = 'get' . $modifier;
        foreach ($this->getTable()->getBehaviors() as $behavior) {
            if (method_exists($behavior->$modifierGetter(), $hookName)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks whether any registered behavior on that table has a modifier for a hook
     * @param string $hookName The name of the hook as called from one of this class methods, e.g. "preSave"
     * @param string $modifier The name of the modifier object providing the method in the behavior
     * @param string &$script  The script will be modified in this method.
     */
    public function applyBehaviorModifierBase($hookName, $modifier, &$script, $tab = "        ")
    {
        $modifierGetter = 'get' . $modifier;
        foreach ($this->getTable()->getBehaviors() as $behavior) {
            $modifier = $behavior->$modifierGetter();
            if (method_exists($modifier, $hookName)) {
                if (strpos($hookName, 'Filter') !== false) {
                    // filter hook: the script string will be modified by the behavior
                    $modifier->$hookName($script, $this);
                } else {
                    // regular hook: the behavior returns a string to append to the script string
                    $addedScript = $modifier->$hookName($this);
                    if (!$addedScript) {
                        continue;
                    }
                    $script .= "
" . $tab . '// ' . $behavior->getId() . " behavior
";
                    $script .= preg_replace('/^/m', $tab, $addedScript);
                }
            }
        }
    }

    /**
     * Checks whether any registered behavior content creator on that table exists a contentName
     * @param string $contentName The name of the content as called from one of this class methods, e.g. "parentClassName"
     * @param string $modifier    The name of the modifier object providing the method in the behavior
     */
    public function getBehaviorContentBase($contentName, $modifier)
    {
        $modifierGetter = 'get' . $modifier;
        foreach ($this->getTable()->getBehaviors() as $behavior) {
            $modifier = $behavior->$modifierGetter();
            if (method_exists($modifier, $contentName)) {
                return $modifier->$contentName($this);
            }
        }
    }

    /**
     * Use Propel simple templating system to render a PHP file using variables
     * passed as arguments. The template file name is relative to the behavior's
     * directory name.
     *
     * @param  string $filename
     * @param  array  $vars
     * @param  string $templateDir
     * @return string
     */
    public function renderTemplate($filename, $vars = [], $templateDir = '/templates/')
    {
        $filePath = __DIR__ . $templateDir . $filename;
        if (!file_exists($filePath)) {
            // try with '.php' at the end
            $filePath = $filePath . '.php';
            if (!file_exists($filePath)) {
                throw new \InvalidArgumentException(sprintf('Template "%s" not found in "%s" directory', $filename, __DIR__ . $templateDir));
            }
        }
        $template = new PropelTemplate();
        $template->setTemplateFile($filePath);
        $vars = array_merge($vars, ['behavior' => $this]);

        return $template->render($vars);
    }

    /**
     * @return string
     */
    public function getTableMapClass()
    {
        return $this->getStubObjectBuilder()->getUnqualifiedClassName() . 'TableMap';
    }

    /**
     * Most of the code comes from the PHP-CS-Fixer project
     *
     * @param $content
     * @return mixed
     */
    private function clean($content)
    {
        // line feed
        $content = str_replace("\r\n", "\n", $content);
     
        // trailing whitespaces
        $content = preg_replace('/[ \t]*$/m', '', $content);

        // indentation
        $content = preg_replace_callback('/^([ \t]+)/m', function ($matches) use ($content) {
            return str_replace("\t", '    ', $matches[0]);
        }, $content);

        // Unused "use" statements
        preg_match_all('/^use (?P<class>[^\s;]+)(?:\s+as\s+(?P<alias>.*))?;/m', $content, $matches, PREG_SET_ORDER);
        foreach ($matches as $match) {
            if (isset($match['alias'])) {
                $short = $match['alias'];
            } else {
                $parts = explode('\\', $match['class']);
                $short = array_pop($parts);
            }

            preg_match_all('/\b'.$short.'\b/i', str_replace($match[0]."\n", '', $content), $m);
            if (!count($m[0])) {
                $content = str_replace($match[0]."\n", '', $content);
            }
        }

        // end of line
        if (strlen($content) && "\n" != substr($content, -1)) {
            $content = $content."\n";
        }

        return $content;
    }

    /**
     * Opens class.
     *
     * @param string &$script
     */
    abstract protected function addClassOpen(&$script);

    /**
     * This method adds the contents of the generated class to the script.
     *
     * This method is abstract and should be overridden by the subclasses.
     *
     * Hint: Override this method in your subclass if you want to reorganize or
     * drastically change the contents of the generated object class.
     *
     * @param string &$script The script will be modified in this method.
     */
    abstract protected function addClassBody(&$script);

    /**
     * Closes class.
     *
     * @param string &$script
     */
    abstract protected function addClassClose(&$script);
}

Size

Lines of code
580