Autogenerated Model

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Autogenerated Model

Post by Benjamin »

As some of you know, I'm a big fan of the Symfony framework. I've decided that it's not in my best interest to use it however, due to the time involved in constantly looking things up and working around the constraints it imposes. The extra time involved defeats the purpose of using a framework to speed up the development process.

So yet again, I've decided to role my own framework. I am currently working on code which will auto generate model classes from a database schema. Posted below is what the generator will currently create by itself. I intend to add auto generated validation (within the constraints of the MySQL field itself), table joins support and some other things I haven't thought of yet.

This is modelled after the models created by Symfony, so it may look a bit familiar. Anyway, any insight would be appreciated. Here is a class generated from the phpbb_icons table.

Code: Select all

<?php
/**
 * Base class that represents a row from the 'phpbb_icons' table.
 *
 * This class was autogenerated.
 *
 * Tue April 13th 14:07:27 2010
 *
 * @package     forum
 */

abstract class basePhpbbIcons {
    /** The primary key for this table */
    const PRIMARY_KEY = 'icons_id';

    /** The MySQL table name */
    const TABLE_NAME = 'phpbb_icons';

    /** The MySQL database name */
    const DATABASE_NAME = 'forum';

    /**
     * Container for field values
     * @var     array
     */
    protected $_values;

    /**
     * Container for default field values
     * @var     array
     */
    protected $_defaultValues = array(
        'IconsId'          => NULL,
        'IconsUrl'         => NULL,
        'IconsWidth'       => '0',
        'IconsHeight'      => '0',
        'IconsOrder'       => '0',
        'DisplayOnPosting' => '1',
    );

    /**
     * Maps standardized field names to MySQL field names
     * @var     array
     */
    private static $_fieldMap = array(
        'IconsId'          => 'icons_id',
        'IconsUrl'         => 'icons_url',
        'IconsWidth'       => 'icons_width',
        'IconsHeight'      => 'icons_height',
        'IconsOrder'       => 'icons_order',
        'DisplayOnPosting' => 'display_on_posting',
    );

    /**
     * Container for tracking modified fields
     * @var     array
     */
    protected $_changedColumns = array();

    /**
     * Container for tracking object state
     * @var     array
     */
    protected $_attributes = array(
        'is_deleted'   => false,
        'is_validated' => false,
        'is_saved'     => false,
        'is_new'       => true,
    );

    public function __construct() {
        $this->kernel = kernel::get_instance();

        $this->_setDefaults();
    }

    /**
     * Get the [icons_id] column value.
     *
     * @return    mediumint(8) unsigned
     */
    public function getIconsId() {
        return $this->_values['icons_id'];
    }

    /**
     * Get the [icons_url] column value.
     *
     * @return    varchar(255)
     */
    public function getIconsUrl() {
        return $this->_values['icons_url'];
    }

    /**
     * Get the [icons_width] column value.
     *
     * @return    tinyint(4)
     */
    public function getIconsWidth() {
        return $this->_values['icons_width'];
    }

    /**
     * Get the [icons_height] column value.
     *
     * @return    tinyint(4)
     */
    public function getIconsHeight() {
        return $this->_values['icons_height'];
    }

    /**
     * Get the [icons_order] column value.
     *
     * @return    mediumint(8) unsigned
     */
    public function getIconsOrder() {
        return $this->_values['icons_order'];
    }

    /**
     * Get the [display_on_posting] column value.
     *
     * @return    tinyint(1) unsigned
     */
    public function getDisplayOnPosting() {
        return $this->_values['display_on_posting'];
    }

    /**
     * Set the value of [icons_id] column.
     *
     * @param      mediumint(8) unsigned $v new value
     * @return     PhpbbIcons The current object (for fluent API support)
     */
    public function setIconsId($v) {
        if ($this->_values['icons_id'] !== $v) {
            $this->_values['icons_id'] = $v;
            $this->updateModified('icons_id');
        }

        return $this;
    }

    /**
     * Set the value of [icons_url] column.
     *
     * @param      varchar(255) $v new value
     * @return     PhpbbIcons The current object (for fluent API support)
     */
    public function setIconsUrl($v) {
        if ($this->_values['icons_url'] !== $v) {
            $this->_values['icons_url'] = $v;
            $this->updateModified('icons_url');
        }

        return $this;
    }

    /**
     * Set the value of [icons_width] column.
     *
     * @param      tinyint(4) $v new value
     * @return     PhpbbIcons The current object (for fluent API support)
     */
    public function setIconsWidth($v) {
        if ($this->_values['icons_width'] !== $v) {
            $this->_values['icons_width'] = $v;
            $this->updateModified('icons_width');
        }

        return $this;
    }

    /**
     * Set the value of [icons_height] column.
     *
     * @param      tinyint(4) $v new value
     * @return     PhpbbIcons The current object (for fluent API support)
     */
    public function setIconsHeight($v) {
        if ($this->_values['icons_height'] !== $v) {
            $this->_values['icons_height'] = $v;
            $this->updateModified('icons_height');
        }

        return $this;
    }

    /**
     * Set the value of [icons_order] column.
     *
     * @param      mediumint(8) unsigned $v new value
     * @return     PhpbbIcons The current object (for fluent API support)
     */
    public function setIconsOrder($v) {
        if ($this->_values['icons_order'] !== $v) {
            $this->_values['icons_order'] = $v;
            $this->updateModified('icons_order');
        }

        return $this;
    }

    /**
     * Set the value of [display_on_posting] column.
     *
     * @param      tinyint(1) unsigned $v new value
     * @return     PhpbbIcons The current object (for fluent API support)
     */
    public function setDisplayOnPosting($v) {
        if ($this->_values['display_on_posting'] !== $v) {
            $this->_values['display_on_posting'] = $v;
            $this->updateModified('display_on_posting');
        }

        return $this;
    }

    /**
     * Sets all values to their defaults
     *
     * @return     PhpbbIcons The current object (for fluent API support)
     */
    public function _setDefaults() {
        $this->_values = $this->_defaultValues;
        return $this;
    }

    /**
     * Deletes the current object from the database
     *
     * @param      object $dbCon A database connection
     * @return     bool True on success, False on failure
     */
    public function doDelete($dbCon = null) {
        if ($this->_attributes['is_deleted']) {
            return true;
        }

        if ($this->_attributes['is_new']) {
            return true;
        }

        if (self::PRIMARY_KEY == '') {
            trigger_error("'" . self::TABLE_NAME . "' does not have a primary key.", E_USER_ERROR);
            return false;
        }

        if (empty($dbCon) || !is_object($dbCon)) {
            $dbCon = basePhpbbIcons::_getDBCon();
        }

        if ($dbCon->query("DELETE FROM " . self::DATABASE_NAME . '.' . self::TABLE_NAME . " WHERE " . self::TABLE_NAME . "." . self::PRIMARY_KEY . " = '" . $dbCon->escape($this->_values[self::PRIMARY_KEY]) . "' LIMIT 1")) {
            $this->_setAttributes(array(
              'is_deleted'     => true,
            ));

            return true;
        }

        return false;
    }

    /**
     * Saves the current object.  The object will either be inserted or updated
     * depending on its source. Objects populated from a database query will be
     * updated.
     *
     * @param      object $dbCon A database connection
     * @return     bool True on success, False on failure
     */
    public function doSave($dbCon = null) {
        if ($this->_attributes['is_deleted']) {
            trigger_error("doSave() Deleted objects cannot be saved.", E_USER_ERROR);
            return false;
        }

        if ($this->_attributes['is_saved']) {
            return true;
        }

        if (self::PRIMARY_KEY == '') {
            trigger_error("'" . self::TABLE_NAME . "' does not have a primary key.", E_USER_ERROR);
            return false;
        }

        if (empty($dbCon) || !is_object($dbCon)) {
            $dbCon = basePhpbbIcons::_getDBCon();
        }

        if ($this->_attributes['is_new']) {
            $data = $this->_values;
            unset($data[self::PRIMARY_KEY]);
            if ($dbCon->insert($data, self::DATABASE_NAME . '.' . self::TABLE_NAME)) {
		        $this->_setAttributes(array(
		          'is_saved'     => true,
		          'is_new'       => false,
		        ));

		        $this->_values[self::PRIMARY_KEY] = $dbCon->insert_id();
		        $this->changedColumns = array();
		        return true;
            } else {
                return false;
            }
        } else {
            $query = "UPDATE " . self::DATABASE_NAME . '.' . self::TABLE_NAME . " SET";

            foreach ($this->changedColumns as $columnName => $isChanged) {
                if (!$isChanged) continue;
                $value = ($this->_values[$columnName] === NULL) ? "NULL" : "'" . $dbCon->escape($this->_values[$columnName]) . "'";
                $query .= " " . self::TABLE_NAME . ".$columnName = $value,";
            }

            $query = rtrim($query, ",");

            $query .= " WHERE " . self::TABLE_NAME . "." . self::PRIMARY_KEY . " = '" . $dbCon->escape($this->_values[self::PRIMARY_KEY]) . "' LIMIT 1";

            if ($dbCon->query($query)) {
                $this->_setAttributes(array(
                  'is_saved'     => true,
                ));

                $this->changedColumns = array();
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * Flag updated columns
     *
     * @param      string $fieldName The modified field name
     * @return     PhpbbIcons The current object (for fluent API support)
     */
    protected function updateModified($fieldName) {
        $this->changedColumns[$fieldName] = 'true';

        $this->_attributes['is_validated'] = false;
        $this->_attributes['is_saved'] = false;

        return $this;
    }


    /**
     * Sets the attributes for the current object
     *
     * @param      array $atr the attributes to set
     * @return     PhpbbIcons The current object (for fluent API support)
     */
    protected function _setAttributes($atr) {
        if (!is_array($atr) || count($atr) < 1) {
            trigger_error("_setAttributes() First argument must be an array", E_USER_ERROR);
        }

        foreach ($atr as $k => $v) {
            if (!isset($this->_attributes[$k])) {
               trigger_error("_setAttributes() $k does not exist as an attribute.", E_USER_ERROR);
               continue;
            }

            $this->_attributes[$k] = $v;
        }
        return $this;
    }

    /**
     * Retrieve a single object by pkey.
     *
     * @param      int $key the primary key.
     * @return     $PhpbbIcons
     */
    public static function retrieveByPK($key, $dbCon = null) {
        if (self::PRIMARY_KEY == '') {
            trigger_error("'" . self::TABLE_NAME . "' does not have a primary key.", E_USER_ERROR);
            return false;
        }

        if (empty($dbCon) || !is_object($dbCon)) {
            $dbCon = basePhpbbIcons::_getDBCon();
        }

        return basePhpbbIcons::doSelectOne("SELECT * FROM " . self::DATABASE_NAME . '.' . self::TABLE_NAME . " WHERE " . self::TABLE_NAME . '.' . self::PRIMARY_KEY . "= '" . $dbCon->escape($key) . "'");
    }

    public static function _getDBCon() {
        $kernel = kernel::get_instance();
        return $kernel->db;
    }

    /**
     * Retrieve a single object with MySQL query
     *
     * @param      string $query the database query
     * @param      object $dbCon the database connection
     * @return     $PhpbbIcons
     */
    public static function doSelectOne($query, $dbCon = null) {
        if (empty($dbCon) || !is_object($dbCon)) {
            $dbCon = basePhpbbIcons::_getDBCon();
        }

        if (false === $n = $dbCon->turbo_row($query)) {
            return false;
        }

        $PhpbbIcons = new PhpbbIcons();
        $PhpbbIcons->_values = $n;
        $PhpbbIcons->_setAttributes(array(
          'is_deleted'   => false,
          'is_validated' => true,
          'is_saved'     => true,
          'is_new'       => false,
        ));
        return $PhpbbIcons;
    }
}
User avatar
AbraCadaver
DevNet Master
Posts: 2572
Joined: Mon Feb 24, 2003 10:12 am
Location: The Republic of Texas
Contact:

Re: Autogenerated Model

Post by AbraCadaver »

I haven't had time to look through it, but you might have a look at the CakePHP bake scripts. Even if you don't intend to use Cake, most of the auto code generation code should be usable with modifications to suite your framework. I also remember there being templates to generate the M/V/C files. The bake script supports joins, validation etc. and it's all PHP for your enjoyment :-) Might save some time. Also, I haven't looked at it in a while, but the Qcodo framework had a fairly good code generator. Cake uses naming conventions such as table and field names to work out the relations. Qcodo used the primary/foreign keys defined in the database tables to work out the relations.
mysql_function(): WARNING: This extension is deprecated as of PHP 5.5.0, and will be removed in the future. Instead, the MySQLi or PDO_MySQLextension should be used. See also MySQL: choosing an API guide and related FAQ for more information.
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Autogenerated Model

Post by Benjamin »

AbraCadaver wrote:I haven't had time to look through it, but you might have a look at the CakePHP bake scripts. Even if you don't intend to use Cake, most of the auto code generation code should be usable with modifications to suite your framework. I also remember there being templates to generate the M/V/C files. The bake script supports joins, validation etc. and it's all PHP for your enjoyment :-) Might save some time. Also, I haven't looked at it in a while, but the Qcodo framework had a fairly good code generator. Cake uses naming conventions such as table and field names to work out the relations. Qcodo used the primary/foreign keys defined in the database tables to work out the relations.
Yeah, that's not a bad idea. This has only taken me about 8 hours or so to write so far and I feel like I'm more than halfway done. I don't think it will take me over an hour or two to add support for joins. The data validation might take a bit longer. So I don't know if I would save any time by pulling apart another framework.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Autogenerated Model

Post by Christopher »

The two things that jump out at me are:

1. You could use __get() and __set() for all your field getters/setters are greatly reduce the code, and code generation needs.

2. Your naming is not very standard for either ActiveRecord or TableDataGateway. I think just save(), findOne(), findAll(), findByPK(), etc. would be more standard.
(#10850)
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Autogenerated Model

Post by Weirdan »

Christopher wrote:2. Your naming is not very standard for either ActiveRecord or TableDataGateway. I think just save(), findOne(), findAll(), findByPK(), etc. would be more standard.
Depends on your background. For example, retrieveByPK is something I immediately recognize (having experience with Propel), and find*() are not.

Benjamin, are there any specific reasons to not reuse the Propel library?
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Autogenerated Model

Post by Benjamin »

Weirdan wrote:Benjamin, are there any specific reasons to not reuse the Propel library?
I'm wanting a solution that will create these from an existing schema, rather than yaml files.
I want to integrate this into a custom framework.
I don't want to use the database abstraction layer.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Autogenerated Model

Post by Weirdan »

Benjamin wrote:I'm wanting a solution that will create these from an existing schema, rather than yaml files.
Propel can do that: propel-gen ./ reverse
Benjamin wrote:I want to integrate this into a custom framework.
Integrating Propel into ZF was quite easy... this, of course, depends on how much integration you want.
Benjamin wrote:I don't want to use the database abstraction layer.
Ok, this rules Propel out since it uses PDO.
phu
Forum Commoner
Posts: 61
Joined: Tue Mar 30, 2010 6:18 pm

Re: Autogenerated Model

Post by phu »

Have you considered going at it from the other direction? I've always been a fan of automatically building the schema based on the code; that way you can stick to writing PHP and let the framework (for the most part) handle the database setup.
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Autogenerated Model

Post by Benjamin »

phu wrote:I've always been a fan of automatically building the schema based on the code;
How would that work? Given a large enough data set you could determine the appropriate and most efficient data type for each field programmatically, but otherwise without implicitly specifying them directly the code would be guessing. Also, since writing code takes longer than creating a DB schema, how would that save time?
phu
Forum Commoner
Posts: 61
Joined: Tue Mar 30, 2010 6:18 pm

Re: Autogenerated Model

Post by phu »

Benjamin wrote:How would that work? Given a large enough data set you could determine the appropriate and most efficient data type for each field programmatically, but otherwise without implicitly specifying them directly the code would be guessing. Also, since writing code takes longer than creating a DB schema, how would that save time?
Based on the code, not the data. Obviously you wouldn't want to try to guess based on the data it processes; using a simple string specification as the default for your classes (and nulling the values on instantiation), though, would be a simple and effective way to handle type definition that could be used when generating tables. It would also provide the groundwork for implicit field validation.

I've linked to a framework on github that illustrates one (more involved) way to handle this; it'd be simpler but less flexible to just infer types based on defaults, e.g. turning array members into joined relations in the schema based on their names (which I have also done to good effect, but wanted more flexibility with naming and type handling).
Post Reply