SitePoint Sponsor

User Tag List

Results 1 to 13 of 13
  1. #1
    SitePoint Member
    Join Date
    Feb 2005
    Posts
    4
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Design approach for a CRUD application

    Hello,
    I am about to start writing a system which is basically a CRUD application. I am currently in the planning phase, and have to choose development tools, but I afraid that I will not be able to take a good decision, hence my post in the forum.

    I need the folowing functionality (nothing fancy here)
    - Privilege control system, I would prefer role based one.
    - Input validation
    - Preferably some set of predefined controls to ease the handling of some DB attributes - such as dates.
    - Easy handling of insert/update/delete of records.
    - Means to support handling the standard set of entity relantionships: one-to-many, many-to-many.

    I searched the forum quite much, and read a couple of discussions about frameworks and ORM solutions for PHP. From my impressions the participants in the forum are inclined to agree that Mojavi is generally the best solution for MVC framework, and Propel for ORM. However I couldn't find a CRUD framework discussion / opinions. Unfortunately because of time constraints I do not have the time to properly explore at the available frameworks , and decide for myself which will be the best tool for the job. I found a couple of references to CakePHP, which perhaps is up to the task, but I was scared away by its small version number / immaturity . If there are people who are using it in a production environments I would love to hear their comments.

    Another thing which is notable in the spec of the project is that its user base could grow fast, and be a large one, so I should keep an eye on performance. Again, comments on CakePHP's performance would be welcome.

    Thank you.

  2. #2
    SitePoint Wizard
    Join Date
    May 2003
    Location
    Berlin, Germany
    Posts
    1,829
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    * Front Controller with Intercepting Filters for the Authentication Stuff
    * Model View Controller with Commands/Page Controllers as the Controllers and Active Records as the Views
    * Data Mappers to allow transparent db field names
    * Collections in the Active Records to support one-to-many and many-to-many relationships
    * Form Handlers within the Controllers for input validation
    * a general InputParser that makes $_GET, $_POST and $_COOKIE save

    I will provide you with some code this afternoon, gotta run now though.

  3. #3
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by DarkAngelBGE
    * Model View Controller with Commands/Page Controllers as the Controllers and Active Records as the Views
    * Model View Controller with Commands as the Controllers and Templates as the Views
    Christopher

  4. #4
    SitePoint Wizard
    Join Date
    May 2003
    Location
    Berlin, Germany
    Posts
    1,829
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Oops, spelling mistake there, sorry.

    * Model View Controller with Commands as the Controllers, Active Records as the Models and Templates as the Views

  5. #5
    SitePoint Zealot
    Join Date
    Aug 2005
    Location
    South Africa
    Posts
    185
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hi,

    Here is my 2c worth. Recently I went, and definitaly am still going through the same process. Trying to read alot and making sense out of what the folks talk about on this forum and elsewhere.

    The two basic choices I see you have is,

    1. develop/put things together as DarkAngel and Arborint have pointed out.
    2. use a current framework that has all the functionality described in DarkAngels post.

    I personally went for option 1 with a recent project and have enjoyed the learning experience, warts and all.
    - I basically used the skeleton code from the front controller and app controller threads (the version Arborint made availble with collaboration of many others) to create the MVC environment with Front,App,Form,Input controllers,Validation and Filters.
    - I used Savant as the template (View) part within the app.
    - I created a generic Active Record class that gets extended to take care of the CRUD and includes calls to static methods where applicable to return collections to support relationships. Obviously within the extended classes I also handle other applicable business logic. I must say that I looked at propel, ezpdo, myobjects, pear dataobjects and in the end chose with developing something myself, with the help of many ideas from others.
    - I used Adodb for the database abstraction, having tried PEAR DB, creole and even writing my own. If my hosting environment supported it I would have used PDO found in PHP 5.0 and upwards.

    If I had decided to go for option 2 I would have most probably used the Symphony framework. To me it is a very promising development framework/environment that has taken alot of good from other projects and put them together very nicely in my opinion. It uses creole for database abstraction and propel for persistance.

    Basically DarkAngel summarized the needed components in my humble opinion pretty well, with his spelling mistake corrections

    Good luck,

    --
    lv

  6. #6
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by DarkAngelBGE
    * Front Controller with Intercepting Filters for the Authentication Stuff
    * Model View Controller with Commands/Page Controllers as the Controllers and Active Records as the Views
    * Data Mappers to allow transparent db field names
    * Collections in the Active Records to support one-to-many and many-to-many relationships
    * Form Handlers within the Controllers for input validation
    * a general InputParser that makes $_GET, $_POST and $_COOKIE save

    I will provide you with some code this afternoon, gotta run now though.
    I have a question about general CRUD implementations. You list both Active Record and Data Mappers. How often do you really need something like a Data Mapper for a basic CRUD implementation? I find that they are mostly simple single table cases where Active Record or Table Data Gateway work fine. Anywhere where there are multiple tables, I find that it quickly becomes much more complex than basic CRUD and turns into a mini-app to manage the data.
    Christopher

  7. #7
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lvismer
    Hi,
    - I created a generic Active Record class that gets extended to take care of the CRUD and includes calls to static methods where applicable to return collections to support relationships.
    Hey, would you mind posting your active record class?

    -matt

  8. #8
    SitePoint Wizard
    Join Date
    May 2003
    Location
    Berlin, Germany
    Posts
    1,829
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by arborint
    I have a question about general CRUD implementations. You list both Active Record and Data Mappers. How often do you really need something like a Data Mapper for a basic CRUD implementation? I find that they are mostly simple single table cases where Active Record or Table Data Gateway work fine. Anywhere where there are multiple tables, I find that it quickly becomes much more complex than basic CRUD and turns into a mini-app to manage the data.
    Dont' use them often, depends on how likely the table names and field names are going to change. In most cases DataMappers provide, and you seem to be of the same opinion, a lot of overhead, since it would easier to change all the table field names by hand in case they change instead of writing an entire new layer.

    Quote Originally Posted by arborint
    I find that they are mostly simple single table cases where Active Record or Table Data Gateway work fine.
    I am not sure I follow your simple single tables cases "definition". Care to elaborate?

  9. #9
    SitePoint Zealot
    Join Date
    Aug 2005
    Location
    South Africa
    Posts
    185
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by mwmitchell
    Hey, would you mind posting your active record class?
    After spending alot of time implementing Row and Table data gateways very much like Jason does in his patterns book and trying other implementations, I eventually settled for an implementation I saw at
    daholygoat.

    What I currently have must be accredited to the implementation on the site above. It suites me as I can create CRUD stuff quickly with a base class like this. I gladly include my revised version. I changed the mysql stuff to support adodb and therefor prepared queries. I must still include things like orderby for queries and would like to include a static array to support locator functionality. I will also take the abuse of things done incorrectly or that could be implemented better

    The abstract class:

    PHP Code:
    abstract class ActiveRecord
    {
        protected 
    $conn;
        protected 
    $data             = array();
        protected 
    $fields           = array();
        protected 
    $primary          = array();
        protected 
    $queryPrepaired   = array();
        protected 
    $queryPairs       = array();
        protected 
    $queryValues      = array();
        protected 
    $table            '';

        public function 
    __construct()
        {
            
    $this->conn DB::conn();
            
    $this->initFields();
            
    $this->initTable();
            
    $this->initPrimary();
        }

        public function 
    set($key$value)
        {
            
    $getAccessor 'get'.ucfirst($key);
            if ( 
    method_exists($this$getAccessor) ) {
                return 
    $this->$getAccessor();
            } else {
                
    $this->data[$key] = $value;
            }
        }

        public function 
    __set($key$value)
        {
            
    $this->set($key$value);
        }

        public function 
    get($key)
        {
            
    $getAccessor 'get'.ucfirst($key);
            if ( 
    method_exists($this$getAccessor) ) {
                return 
    $this->$getAccessor();
            } else {
                if ( isset(
    $this->data[$key]) ) {
                    return 
    $this->data[$key];
                } else {
                    return 
    false;
                }
            }
        }

        public function 
    __get($key)
        {
            return 
    $this->get($key);
        }

        public function 
    getAll()
        {
            
    $this->setQueryFields();
            
    $sql 'SELECT * FROM '.$this->table;
            if ( 
    count($this->data) > ) {
                
    $sql .= ' WHERE '.implode(' AND '$this->queryPairs);
            }
            
    $rs $this->conn->execute($sql$this->queryValues);
            
    $count $rs->numRows();
            foreach ( 
    $rs as $row ) {
                
    $className get_class($this);
                
    $result = new $className();
                
    $result->load($row);
                
    $results[] = $result;
            }
            return 
    $results;
        }

        public function 
    fetch()
        {
            
    $this->setQueryFields();
            
    $sql 'SELECT * FROM '.$this->table;
            if ( 
    count($this->data) > ) {
                
    $sql .= ' WHERE '.implode(' AND '$this->queryPairs);
            }
            
    $rs $this->conn->execute($sql$this->queryValues);
            if ( 
    $rs ) {
                
    $this->load($rs->fields);
            }
        }

        public function 
    unsetAttr($key '')
        {
            if ( empty(
    $key) ) {
                
    $this->data = array();
            } else {
                unset(
    $this->data[$key]);
            }
            
    flush();
        }

        public function 
    insert()
        {
            
    $this->setQueryFields();
            
    $sql 'INSERT INTO '.$this->table.' ('.implode(', 'array_keys($this->queryPairs)).') '.
                   
    'VALUES ('.implode(', '$this->queryPrepaired).')';
            
    $rs $this->conn->execute($sql$this->queryValues);
            if ( !
    $rs ) {
                
    trigger_error("Database Query Error: INSERT");
            }
        }

        public function 
    update()
        {
            
    $this->setQueryFields();
            
    $sql 'UPDATE '.$this->table.' SET '.implode(', '$this->queryPairs).' '.
                   
    'WHERE '.implode(', '$this->setQueryPrimary());
            
    $rs $this->conn->execute($sql$this->queryValues);
            if ( !
    $rs ) {
                
    trigger_error("Database Query Error: UPDATE");
            }
        }

        public function 
    delete()
        {
            
    $this->setQueryFields();
            
    $sql 'DELETE FROM '.$this->table.' WHERE '.implode(' AND '$this->queryPairs);
            
    $rs $this->conn->execute($sql$this->queryValues);
            if ( !
    $rs ) {
                
    trigger_error("Database Query Error: DELETE");
            }
        }

        protected function 
    load($data)
        {
            foreach ( 
    $data as $key => $value ) {
                
    $this->data[$key] = $value;
            }
        }

        protected function 
    setQueryFields()
        {
            
    $this->queryPairs = array();
            
    $this->queryValues = array();
            foreach ( 
    $this->data as $key => $value ) {
                
    $this->queryPrepaired[] =  '?';
                
    $this->queryPairs[$key] = $key .' = ?';
                
    $this->queryValues[] = $value;
            }
        }

        protected function 
    setQueryPrimary()
        {
            
    $primary = array();
            foreach ( 
    $this->primary as $key ) {
                
    $primary[$key] = $key.' = ?';
                
    $this->queryValues[] = $this->data[$key];
            }
            return 
    $primary;
        }

        abstract function 
    initFields();
        abstract function 
    initTable();
        abstract function 
    initPrimary();

    A user table class:



    PHP Code:
    class User extends ActiveRecord
    {
        public function 
    __construct($id NULL)
        {
            
    parent::__construct();
            if ( 
    $id ) {
                
    $this->set($this->primary[0], $id);
                
    $this->fetch();
            }
        }

        public function 
    initFields()
        {
            
    $this->fields = array (
                
    'userID',
                
    'title',
                
    'name',
                
    'surname',
                
    'email',
                
    'password',
                
    'active'
            
    );
        }

        public function 
    initTable()
        {
            
    $this->table 'user';
        }

        public function 
    initPrimary()
        {
            
    $this->primary[] = 'userID';
        }

        public function 
    getFullname()
        {
            return 
    $this->data['title'].', '.$this->data['name'].' '.$this->data['surname'];
        }

    Usage of the class:

    PHP Code:
    $user = new User();
    $user->set('title''Mr');
    $user->set('name''Frodo');
    $user->set('surname''Baggins');
    $user->set('email''frodo@the-shire.com');
    $user->insert();

    $user->unsetAttr();

    $rs $user->getAll();
    foreach ( 
    $rs as $result ) {
        echo 
    $result->get('fullname') ."\n";
        
    /* OR */
        
    echo $result->fullname ."\n";
    }

    $user->unsetAttr();

    $user->set('name''Frodo');
    $user->delete();

    $user->unsetAttr();

    $user->set('userID'1);
    $user->set('title''title');
    $user->set('name''name');
    $user->set('surname''surname');
    $user->set('email''email');
    $user->update();

    $newUser = new User(1);
    echo 
    $newUser->fullname ."\n"
    --
    lv

  10. #10
    SitePoint Addict
    Join Date
    Jan 2005
    Location
    United Kingdom
    Posts
    208
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    It seems to me that in your getAll() method you traverse the result set and build an array of objects, then pass the array to the client to be traversed again. This could be a fairly significant overhead on large collections of objects, particularly as the array is not passed by reference. I'm not sure how PHP 5 will handle an array of objects - whether the actual objects will be referenced or copied.

    In the past when using data mappers I have passed the result set to an iterator and passed that object to the client, leaving the actual object creation to the last minute, perhaps this goes against the design of ActiveRecord?

  11. #11
    SitePoint Addict
    Join Date
    Jan 2005
    Location
    United Kingdom
    Posts
    208
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Having just done a quick test it seems objects are always passed by reference regardless of how they are passed, so the only overhead is traversing the collection twice.

  12. #12
    SitePoint Zealot
    Join Date
    Aug 2005
    Location
    South Africa
    Posts
    185
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Shrike
    It seems to me that in your getAll() method you traverse the result set and build an array of objects, then pass the array to the client to be traversed again. This could be a fairly significant overhead on large collections of objects, particularly as the array is not passed by reference. I'm not sure how PHP 5 will handle an array of objects - whether the actual objects will be referenced or copied.
    I see your point here, it might not be the best way to do things in large applications, especially without a limit feature. I also share you uncertainty regarding how PHP will manage the array not being passed as a reference. I figured that the objects inside the array are accessed via references. Perhaps someone else could shed some light on this?

    Quote Originally Posted by Shrike
    In the past when using data mappers I have passed the result set to an iterator and passed that object to the client, leaving the actual object creation to the last minute, perhaps this goes against the design of ActiveRecord?
    How about the following. Using the ADODB_interator the new getAll would look like:

    PHP Code:
    public function getAll()
        {
            
    $this->setQueryFields();
            
    $sql 'SELECT * FROM '.$this->table;
            if ( 
    count($this->data) > ) {
                
    $sql .= ' WHERE '.implode(' AND '$this->queryPairs);
            }
            
    $rs $this->conn->execute($sql$this->queryValues);
            
    $results = new ARCollectionIterator($rsget_class($this));
            return 
    $results;
        } 
    Then the ARCollectionIterator using lazy loading:

    PHP Code:
    class ARCollectionIterator implements Iterator
    {
        protected   
    $rs;
        private     
    $objectName;

        public function 
    __construct($rs$objectName)
        {
            
    $this->objectName $objectName;
            
    $this->rs = new ADODB_Iterator($rs);
        }

        public function 
    next()
        {
            
    $result $this->rs->next();
            if ( 
    $result ) {
                
    $activeRecord = new $this->objectName;
                
    $activeRecord->load($result);
                return 
    $activeRecord;
            } else {
                return 
    NULL;
            }
        }

        public function 
    current()
        {
            
    $result $this->rs->fetchObj();
            if ( 
    $result ) {
                
    $activeRecord = new $this->objectName;
                
    $activeRecord->load($result);
                return 
    $activeRecord;
            } else {
                return 
    NULL;
            }
        }

        public function 
    key()
        {
            return 
    $this->rs->key();
        }

        public function 
    valid()
        {
            return 
    $this->rs->valid();
        }

        public function 
    rewind()
        {
            return 
    $this->rs->rewind();
        }

    Your thoughts?

    --
    lv

  13. #13
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ha. I like that. It's almost exactly what I've done. I have a result set iterator that includes the name of a default decorator (ex: MySQLRecord) and if I want, I can change it (the decorator) by setting that name in the iterator, before iterating. This is not built in to a an active record object, but a query statement object.

    The only thing that has been bugging me about these types of db objects (active records) is the finder methods. Why does a product need to find more of itself? If you think about it, it's odd. If you have a car, you wouldn't ask the car for more of it self, you'd ask the factory or sales man. So I've been thinking about a way to have a generic finder for these active records. Let the active record do what it does (car: drive, honk, go faster etc...). The most recent idea/implementation was:

    $rs =& ARFinder::all('Car');
    while($rs->next()){
    // etc...
    }

    And the same for saving/deleting. Some kind of persistance object that accepts a record, inspects and persists (err, deletes). But that also takes away from the simple easy way of what you have posted (and I currently). I'm still trying to figure it out! I've noticed that this is the rails way too and PEAR's dataobject. Maybe it's not all that akward? If you add field and field type inspection of the database table, you'd have a very easy and fast way to do crud apps, easy to do with most db packages.

    - matt


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •