Categorías
Symfony

Generando el Modelo, a mi manera

(El presente es un ensayo de como ire descubriendo como hacer cada cosa, por lo que tendría modificaciones sucesivas, sin embargo la idea es que lo que leas sirva y funcione)

Es quizás muy petulante el título, pero esa es la idea. Osea Generar el Modelo. 🙂

Como seguro ya se darían cuenta, con anteriores artículos y traducciones, estoy estudiando symfony 1.2 para mi proyecto.

Una de las cosas que hice primero fue hacer un ambiente para mis pruebas, con el fin de tener un ámbito para jugar a lo que se me ocurra. Osea que mi schema tiene muchas cosas, pero como es de pruebas … que va!

Una de las cosas que siempre me gusto es lo del método mágico __toString() de las clases del modelo generado. El poder luego redefinirlo en la clase personalizada, solicitando que devuelva un getXXX() y poder ver el dato de la base en los ABM / CRUD del administrador en lugar de los PrimaryKey, es realmente mas lindo.

Pero tener que redefinir en cada clase, puede ser molesto …
Vamos, soy de los que se pierden haciendos “xxxxxx_id” por todos lados en el modelo … 🙂 Por lo que tengo que redefinir muchos __toString() .

Entonces porque no poder en el schema, definir un atributo tostring a true o especificar el nombre del campo a utilizar por el método magico, para que el generador/builder/constructor del modelo lo haga por mi ya desde la BaseClase.php ?

Mi instalacion de Symfony 1.2 esta en /Symfony1.2.
Digamos que en otro equipo distinto del que estamos trabajando, hice una descarga por SVN del la versión 1.2, y luego la copie a este DIR.
Imagino que esto no tiene ningun tipo de incidencia.

Veamos un poco el schema:

propel:
   category:
    _attributes:    { phpName: Category, phpToString: Name }
    name:        varchar(255)

La idea central, es definir un atributo phpToString que nos permita definir un texto, en este caso Name, y que en este caso tambien hace referencia al campo name declarado en la linea debajo.
La finalidad es lograr que se genere un código / método toString() para esta clase, como el siguiente :

    /**
     * Magic Method __toString will return the value of some field
     * if you setup the attributes of your schema for this object/table
     */
    public function __toString()
    {
        return $this->getName();

    }

Note que por esta razón, puse Name, con la N en mayúsculas. Ya vere despues como hacer la transformación camelcase…
El generador al $this->get le agregará a continuación lo que hallamos definido

Entiendo que iniciamos el script por generar de cada BaseClase.php, desde SfObjectBuilder. A continuación un extracto de como las clases se van heredando, según su definición:

class SfObjectBuilder extends PHP5ObjectBuilder {

class PHP5ObjectBuilder extends ObjectBuilder {

abstract class ObjectBuilder extends OMBuilder {

abstract class OMBuilder extends DataModelBuilder {

Hagamos un salto al objeto OMBuilder (/Symfony1.2/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/builder/om/OMBuilder.php)

	 /**
	 * 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();

		$script = "<" . "?php\n"; // intentional concatenation
		$this->addIncludes($script);
		$this->addClassOpen($script);
		$this->addClassBody($script);
		$this->addClassClose($script);
		return $script;
	}

Estos metodo tan interesantes los podemos encontrar implementado en (/Symfony1.2\lib\plugins\sfPropelPlugin\lib\vendor\propel-generator\classes\propel\engine\builder\om\php5\PHP5ObjectBuilder.php) el objeto PHP5ObjectBuilder (use la flechita que esta a la derecha de la barra siguiente para ver los metodos)

	/**
	 * Adds the include() statements for files that this class depends on or utilizes.
	 * @param      string &$script The script will be modified in this method.
	 */
	protected function addIncludes(&$script)
	{
	} // addIncludes()

	/**
	 * Adds class phpdoc comment and openning of class.
	 * @param      string &$script The script will be modified in this method.
	 */
	protected function addClassOpen(&$script)
	{

		$table = $this->getTable();
		$tableName = $table->getName();
		$tableDesc = $table->getDescription();
		$interface = $this->getInterface();

		$script .= "
/**
 * Base class that represents a row from the '$tableName' table.
 *
 * $tableDesc
 *";
		if ($this->getBuildProperty('addTimeStamp')) {
			$now = strftime('%c');
			$script .= "
 * This class was autogenerated by Propel " . $this->getBuildProperty('version') . " on:
 *
 * $now
 *";
		}
		$script .= "
 * @package    ".$this->getPackage()."
 */
abstract class ".$this->getClassname()." extends ".ClassTools::classname($this->getBaseClass())." ";

		$interface = ClassTools::getInterface($table);
		if ($interface) {
			$script .= " implements " . ClassTools::classname($interface);
		}

		$script .= " {

";
	}

	/**
	 * Specifies the methods that are added as part of the basic OM class.
	 * This can be overridden by subclasses that wish to add more methods.
	 * @see        ObjectBuilder::addClassBody()
	 */
	protected function addClassBody(&$script)
	{
		$script = "\n" . $script;
		$table = $this->getTable();
		if (!$table->isAlias()) {
			$this->addConstants($script);
			$this->addAttributes($script);
		}

		$this->addConstructor($script);
		$this->addApplyDefaultValues($script);

		// NUEVO CODIGO // NEW CODE
		if ($this->getPhpToString() != false) {
			$this->addToString($script);
		}

		$this->addColumnAccessorMethods($script);
		$this->addColumnMutatorMethods($script);

		$this->addHasOnlyDefaultValues($script);

		$this->addHydrate($script);
		$this->addEnsureConsistency($script);

		$this->addManipulationMethods($script);
		$this->addValidationMethods($script);

		if ($this->isAddGenericAccessors()) {
			$this->addGetByName($script);
			$this->addGetByPosition($script);
			$this->addToArray($script);
		}

		if ($this->isAddGenericMutators()) {
			$this->addSetByName($script);
			$this->addSetByPosition($script);
			$this->addFromArray($script);
		}

		$this->addBuildCriteria($script);
		$this->addBuildPkeyCriteria($script);
		$this->addGetPrimaryKey($script);
		$this->addSetPrimaryKey($script);

		$this->addCopy($script);

		if (!$table->isAlias()) {
			$this->addGetPeer($script);
		}

		$this->addFKMethods($script);
		$this->addRefFKMethods($script);
		$this->addClearAllReferences($script);
	}

	/**
	 * Closes class.
	 * @param      string &$script The script will be modified in this method.
	 */
	protected function addClassClose(&$script)
	{
		$script .= "
} // " . $this->getClassname() . "
";
	}

Note la nueva porción de código

		// NUEVO CODIGO // NEW CODE
		if ($this->getPhpToString() != false) {
			$this->addToString($script);
		}

Quien se encarga de analizar el schema sea YML o XML, es la clase sfPropelDatabaseSchema (E:\wamp\apps\symfony\1.2\lib\plugins\sfPropelPlugin\lib\addon\sfPropelDatabaseSchema.class.php)

La Clase Abstracta XMLElement (/Symfony1.2/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/database/model/XMLElement.php) representa a los elementos de un archivo XML. Es el método loadFromXML() quien toma esos atributos

	/**
	 * This is the entry point method for loading data from XML.
	 * It calls a setupObject() method that must be implemented by the child class.
	 * @param      array $attributes The attributes for the XML tag.
	 */
	public function loadFromXML($attributes)
	{
		$this->attributes = array_change_key_case($attributes, CASE_LOWER);
		$this->setupObject();
	}

La misma es heredada por otras como Table y Database.

A la clase Table, debemos agregarle estas modificaciones. Es quien hereda de XMLElement e implementa IDMethod.
(/Symfony1.2/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/database/model/Table.php
)

Un atributo $phpToString

    /**
     * The field name for the magic method __toString().
     *
     * @var        string
     */
    private $phpToString;

Cargamos los valores al objeto, entre ellos $this->phpToString

	/**
	 * Sets up the Rule object based on the attributes that were passed to loadFromXML().
	 * @see        parent::loadFromXML()
	 */
	public function setupObject()
	{
		$this->name = $this->getAttribute("name");
		$this->phpName = $this->getAttribute("phpName");
		$this->idMethod = $this->getAttribute("idMethod", $this->getDatabase()->getDefaultIdMethod());
		$this->allowPkInsert = $this->booleanValue($this->getAttribute("allowPkInsert"));

		// retrieves the method for converting from specified name to a PHP name.
		$this->phpNamingMethod = $this->getAttribute("phpNamingMethod", $this->getDatabase()->getDefaultPhpNamingMethod());

		$this->skipSql = $this->booleanValue($this->getAttribute("skipSql"));
		$this->readOnly = $this->booleanValue($this->getAttribute("readOnly"));

		$this->phpToString = $this->getAttribute("phpToString");
		$this->pkg = $this->getAttribute("package");
		$this->abstractValue = $this->booleanValue($this->getAttribute("abstract"));
		$this->baseClass = $this->getAttribute("baseClass");
		$this->basePeer = $this->getAttribute("basePeer");
		$this->alias = $this->getAttribute("alias");

		$this->heavyIndexing = ( $this->booleanValue($this->getAttribute("heavyIndexing"))
		|| ("false" !== $this->getAttribute("heavyIndexing")
		&& $this->getDatabase()->isHeavyIndexing() ) );
		$this->description = $this->getAttribute("description");
		$this->enterface = $this->getAttribute("interface"); // sic ('interface' is reserved word)
		$this->treeMode = $this->getAttribute("treeMode");

		$this->reloadOnInsert = $this->booleanValue($this->getAttribute("reloadOnInsert"));
		$this->reloadOnUpdate = $this->booleanValue($this->getAttribute("reloadOnUpdate"));
	}

Los Getter y Setter

    /**
     * Get the value of phpToString.
     * @return     value of phpToString.
     */
    public function getPhpToString()
    {
        return $this->phpToString;
    }

    /**
     * Set the value of phpToString.
     * @param      v  Value to assign to phpToString.
     */
    public function setPhpToString($v)
    {
        $this->phpToString = $v;
    }

Y tambien debemos adaptar la clase Database
(/Symfony1.2/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/database/model/Database.php)

Un atributo $phpToString

	private $phpToString;

Cargamos los valores al objeto, entre ellos $this->phpToString

	/**
	 * Sets up the Database object based on the attributes that were passed to loadFromXML().
	 * @see        parent::loadFromXML()
	 */
	protected function setupObject()
	{
		$this->name = $this->getAttribute("name");
		$this->phpToString = $this->getAttribute("phpToString");
		$this->pkg = $this->getAttribute("package");
		$this->baseClass = $this->getAttribute("baseClass");
		$this->basePeer = $this->getAttribute("basePeer");
		$this->defaultIdMethod = $this->getAttribute("defaultIdMethod", IDMethod::NATIVE);
		$this->defaultPhpNamingMethod = $this->getAttribute("defaultPhpNamingMethod", NameGenerator::CONV_METHOD_UNDERSCORE);
		$this->defaultTranslateMethod = $this->getAttribute("defaultTranslateMethod", Validator::TRANSLATE_NONE);
		$this->heavyIndexing = $this->booleanValue($this->getAttribute("heavyIndexing"));
	}

Los Getter y Setter

    /**
     * Get the value of phpToString.
     * @return     value of phpToString.
     */
    public function getPhpToString()
    {
        return $this->phpToString;
    }

    /**
     * Set the value of phpToString.
     * @param      v  Value to assign to phpToString.
     */
    public function setPhpToString($v)
    {
        $this->phpToString = $v;
    }

Ahora el XML

	/**
	 * @see        XMLElement::appendXml(DOMNode)
	 */
	public function appendXml(DOMNode $node)
	{
		$doc = ($node instanceof DOMDocument) ? $node : $node->ownerDocument;

		$dbNode = $node->appendChild($doc->createElement('database'));

		$dbNode->setAttribute('name', $this->name);

        if ($this->phpToString) {
            $dbNode->setAttribute('phpToString', $this->phpToString);
        }

		if ($this->pkg) {
			$dbNode->setAttribute('package', $this->pkg);
		}

		if ($this->defaultIdMethod) {
			$dbNode->setAttribute('defaultIdMethod', $this->defaultIdMethod);
		}

		if ($this->baseClass) {
			$dbNode->setAttribute('baseClass', $this->baseClass);
		}

		if ($this->basePeer) {
			$dbNode->setAttribute('basePeer', $this->basePeer);
		}

		if ($this->defaultPhpNamingMethod) {
			$dbNode->setAttribute('defaultPhpNamingMethod', $this->defaultPhpNamingMethod);
		}

		if ($this->defaultTranslateMethod) {
			$dbNode->setAttribute('defaultTranslateMethod', $this->defaultTranslateMethod);
		}

		/*

		FIXME - Before we can add support for domains in the schema, we need
		to have a method of the Column that indicates whether the column was mapped
		to a SPECIFIC domain (since Column->getDomain() will always return a Domain object)

		foreach ($this->domainMap as $domain) {
		$domain->appendXml($dbNode);
		}
		*/
		foreach ($this->vendorInfos as $vi) {
			$vi->appendXml($dbNode);
		}

		foreach ($this->tableList as $table) {
			$table->appendXml($dbNode);
		}

	}

Con estas dos clases modificadas, estamos en condiciones de usar estos cambios para la clase OMBuilder que hereda de DataModelBuilder. Pero esta ultima clase no sera necesario modificarla. (/Symfony1.2/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/builder/om/OMBuilder.php). Solo hay que :

    /**
     * Gets the field name for the magic method __toString.
     * @return     string
     */
    public function getPhpToString()
    {
        $phpToString = ($this->getTable()->getPhpToString() ? $this->getTable()->getPhpToString() : $this->getDatabase()->getPhpToString());
        if (!$phpToString) {
            $phpToString = false;
        }
        return $phpToString;
    }

Como vimos la clase hija de la anterior OMBuilder, es PHP5ObjectBuilder.
(/Symfony1.2/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/builder/om/php5/PHP5ObjectBuilder.php)

2 respuestas en “Generando el Modelo, a mi manera”

Muy interesante, aunque yo lo resolveria con un behavior, es un ejemplo perfecto de pizarron para comenzar a desarrollar un framework de generación de código que no permita no solo mayor personalización de nuestro código, sino tambien una mejor calidad de codigo y mayor riquesa de opciones.

Pues con un behavior, ni se me ocurre como 🙂 , eso se los dejo a los expertos.

Faltaria de extender quizas un poco las clases que gestionan las tareas

estsa pueden ser algunas…

/lib/plugins/sfPropelPlugin/lib/task/sfPropelBaseTask.class.php
/lib/plugins/sfPropelPlugin/lib/task/sfPropelBuildModelTask.class.php
/lib/plugins/sfPropelPlugin/lib/task/sfPropelGenerateAdminTask.class.php

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *