PSQLx2.0 overview and support

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: PSQLx2.0 overview and support

Post by josh »

Wow implementation specific information overload, but what you're building is a basically a repository, an OO interface to query against a collection of sub objects. I'm not sure what the context of the $a->v = 'string' example you posted is. Do you plan to write your own ORM? What ORM patterns are you going to use to map this to a relational database?
crazycoders
Forum Contributor
Posts: 260
Joined: Tue Oct 28, 2008 7:48 am
Location: Montreal, Qc, Canada

Re: PSQLx2.0 overview and support

Post by crazycoders »

I had already built my own and sincerely i never followed any design patter or quite remembered the difference between them all. I'd actually need a good kick in the arse for that portion to make sure i don't code something that is useless... Since you seem to know A LOT about these, mind giving me a few hints as to how you'd see a good ORM mapped to this tech?

I will obviously take my own, for example, pluqv1 (or psql1) ORM code looked like this: (It was very lame but it served it's purpose)

Code: Select all

 
if(!defined('CLASSDEF_car')){
    define('CLASSDEF_car', true);
 
    class car {
 
        public static function getAllCars($orderfields = array(), $limit = 0, $limitfrom = 0){
            $sql = 'SELECT * FROM `racingfever`.`cars` ';
            if(isset($orderfields) && is_array($orderfields) && count($orderfields) > 0){
                $orderfieldconcat='';
                $sql .= 'ORDER BY ';
                foreach($orderfields as $orderfielddata){
                    $sql .= $orderfieldconcat.$orderfielddata['name'];
                    $orderfieldconcat=', ';
                }
            }
            if(isset($limit) && is_numeric($limit) && $limit > 0){
                $sql .= ' LIMIT '.$limit;
                if(isset($limitfrom) && is_numeric($limitfrom) && $limitfrom > 0){
                    $sql .= ', '.$limitfrom;
                }
            }
            $datatable = mysql_query($sql);
            $resultingobjects = array();
            while($datarow = mysql_fetch_assoc($datatable)){ 
                if(class_exists("carex")){ $newcar = new carex($datarow); }else{ $newcar = new car($datarow); } 
                $resultingobjects[] = $newcar; 
            } 
            return $resultingobjects; 
        }
        
        public static function getCarById($id){ 
            if(is_null($id) == true || is_numeric($id) == false){ throw new exception('$id is not a valid NUMBER value'); } 
            $sql = 'SELECT * FROM `racingfever`.`cars` WHERE id = "'.mysql_escape_string($id).'" ';
            if(isset($orderfields) && is_array($orderfields) && count($orderfields) > 0){
                $orderfieldconcat='';
                $sql .= 'ORDER BY ';
                foreach($orderfields as $orderfielddata){
                    $sql .= $orderfieldconcat.$orderfielddata['name'];
                    $orderfieldconcat=', ';
                }
            }
            if(isset($limit) && is_numeric($limit) && $limit > 0){
                $sql .= ' LIMIT '.$limit;
                if(isset($limitfrom) && is_numeric($limitfrom) && $limitfrom > 0){
                    $sql .= ', '.$limitfrom;
                }
            }
            $datatable = mysql_query($sql);
            $resultingobjects = array();
            while($datarow = mysql_fetch_assoc($datatable)){ 
                if(class_exists("carex")){ $newcar = new carex($datarow); }else{ $newcar = new car($datarow); } 
                $resultingobjects[] = $newcar; 
            } 
            return $resultingobjects[0]; 
        }
        
        protected $data = array();
        protected $newrow = true;
        protected $deletedrow = true;
        
        public function __construct($data = NULL){
            if(is_array($data) == false || is_null($data)){
                $this->newrow();
            }else{
                $this->data = $data;
                $this->newrow = false;
                $this->deletedrow = false;
            }
        }
        
        public function newrow(){
            $this->data = array(
                'id' => 0,
                'racerprofileid' => 0,
                'basecarid' => 0,
                'bought' => date('Y-m-d G:i:s', time()),
                'worth' => 0,
                'onsale' => 0,
                'onsalesince' => '0000-00-00 00:00:00',
                'onsaleprice' => 0,
                'make' => '',
                'model' => '',
                'collection' => '',
                'instance' => 0,
                'year' => '',
                'imagefull' => '',
                'imagesmall' => '',
                'imagethumb' => '',
                'basebhp' => 0,
                'incbhp' => 0,
                'totbhp' => 0,
                'baseacl' => 0,
                'incacl' => 0,
                'totacl' => 0,
                'basehdl' => 0,
                'inchdl' => 0,
                'tothdl' => 0,
                'basetot' => 0,
                'inctot' => 0,
                'tot' => 0,
                'curexhaust' => 0,
                'curbrake' => 0,
                'curtune' => 0,
                'curpolish' => 0,
                'curbalance' => 0,
                'curdispl' => 0,
                'curcomp' => 0,
                'curnos' => 0,
                'curtrans' => 0,
                'curclutch' => 0,
                'curflywheel' => 0,
                'curturbine' => 0,
                'curcooler' => 0,
                'cursusp' => 0,
                'curtires' => 0,
                'curreduction' => 0,
                'curbrakecontrol' => 0,
                'odometer' => 0,
            );
            $this->deletedrow = false;
            $this->newrow = true;
        }
        
        public function deleterow(){
            $this->deletedrow = true;
        }
        
        public function save_changes(){
            if($this->newrow == true && $this->deletedrow == true){
                $this->newrow();
            }elseif($this->newrow == true){
                mysql_query('INSERT INTO `racingfever`.`cars`(`racerprofileid`, `basecarid`, `bought`, `worth`, `onsale`, `onsalesince`, `onsaleprice`, `make`, `model`, `collection`, `instance`, `year`, `imagefull`, `imagesmall`, `imagethumb`, `basebhp`, `incbhp`, `totbhp`, `baseacl`, `incacl`, `totacl`, `basehdl`, `inchdl`, `tothdl`, `basetot`, `inctot`, `tot`, `curexhaust`, `curbrake`, `curtune`, `curpolish`, `curbalance`, `curdispl`, `curcomp`, `curnos`, `curtrans`, `curclutch`, `curflywheel`, `curturbine`, `curcooler`, `cursusp`, `curtires`, `curreduction`, `curbrakecontrol`, `odometer`)VALUES('.$this->data['racerprofileid'].', '.$this->data['basecarid'].', "'.$this->data['bought'].'", '.$this->data['worth'].', '.$this->data['onsale'].', "'.$this->data['onsalesince'].'", '.$this->data['onsaleprice'].', "'.mysql_escape_string($this->data['make']).'", "'.mysql_escape_string($this->data['model']).'", "'.mysql_escape_string($this->data['collection']).'", '.$this->data['instance'].', "'.mysql_escape_string($this->data['year']).'", "'.mysql_escape_string($this->data['imagefull']).'", "'.mysql_escape_string($this->data['imagesmall']).'", "'.mysql_escape_string($this->data['imagethumb']).'", '.$this->data['basebhp'].', '.$this->data['incbhp'].', '.$this->data['totbhp'].', '.$this->data['baseacl'].', '.$this->data['incacl'].', '.$this->data['totacl'].', '.$this->data['basehdl'].', '.$this->data['inchdl'].', '.$this->data['tothdl'].', '.$this->data['basetot'].', '.$this->data['inctot'].', '.$this->data['tot'].', '.$this->data['curexhaust'].', '.$this->data['curbrake'].', '.$this->data['curtune'].', '.$this->data['curpolish'].', '.$this->data['curbalance'].', '.$this->data['curdispl'].', '.$this->data['curcomp'].', '.$this->data['curnos'].', '.$this->data['curtrans'].', '.$this->data['curclutch'].', '.$this->data['curflywheel'].', '.$this->data['curturbine'].', '.$this->data['curcooler'].', '.$this->data['cursusp'].', '.$this->data['curtires'].', '.$this->data['curreduction'].', '.$this->data['curbrakecontrol'].', '.$this->data['odometer'].')');
                $this->data['id'] = mysql_insert_id();
                $this->newrow = false;
                $this->deletedrow = false;
            }elseif($this->deletedrow == true){
                mysql_query('DELETE FROM `racingfever`.`cars` WHERE  id = '.$this->data['id'].'');
                $this->newrow();
            }else{
                mysql_query('UPDATE `racingfever`.`cars` SET  racerprofileid = '.$this->data['racerprofileid'].',  basecarid = '.$this->data['basecarid'].',  bought = "'.$this->data['bought'].'",  worth = '.$this->data['worth'].',  onsale = '.$this->data['onsale'].',  onsalesince = "'.$this->data['onsalesince'].'",  onsaleprice = '.$this->data['onsaleprice'].',  make = "'.mysql_escape_string($this->data['make']).'",  model = "'.mysql_escape_string($this->data['model']).'",  collection = "'.mysql_escape_string($this->data['collection']).'",  instance = '.$this->data['instance'].',  year = "'.mysql_escape_string($this->data['year']).'",  imagefull = "'.mysql_escape_string($this->data['imagefull']).'",  imagesmall = "'.mysql_escape_string($this->data['imagesmall']).'",  imagethumb = "'.mysql_escape_string($this->data['imagethumb']).'",  basebhp = '.$this->data['basebhp'].',  incbhp = '.$this->data['incbhp'].',  totbhp = '.$this->data['totbhp'].',  baseacl = '.$this->data['baseacl'].',  incacl = '.$this->data['incacl'].',  totacl = '.$this->data['totacl'].',  basehdl = '.$this->data['basehdl'].',  inchdl = '.$this->data['inchdl'].',  tothdl = '.$this->data['tothdl'].',  basetot = '.$this->data['basetot'].',  inctot = '.$this->data['inctot'].',  tot = '.$this->data['tot'].',  curexhaust = '.$this->data['curexhaust'].',  curbrake = '.$this->data['curbrake'].',  curtune = '.$this->data['curtune'].',  curpolish = '.$this->data['curpolish'].',  curbalance = '.$this->data['curbalance'].',  curdispl = '.$this->data['curdispl'].',  curcomp = '.$this->data['curcomp'].',  curnos = '.$this->data['curnos'].',  curtrans = '.$this->data['curtrans'].',  curclutch = '.$this->data['curclutch'].',  curflywheel = '.$this->data['curflywheel'].',  curturbine = '.$this->data['curturbine'].',  curcooler = '.$this->data['curcooler'].',  cursusp = '.$this->data['cursusp'].',  curtires = '.$this->data['curtires'].',  curreduction = '.$this->data['curreduction'].',  curbrakecontrol = '.$this->data['curbrakecontrol'].',  odometer = '.$this->data['odometer'].' WHERE  id = '.$this->data['id'].'');
            }
        }
        
        public function basecar(){
            return single(pluq(from(inres(mysql_query('SELECT * FROM basecars WHERE id = "'.$this->basecarid.'"'), 'basecar'))));
        }
        
        public function organisedraceevents(){
            return pluq(from(inres(mysql_query('SELECT * FROM organisedraceevents WHERE carid = "'.$this->id.'"'), 'organisedraceevent')));
        }
        
        public function organisedraceparticipants(){
            return pluq(from(inres(mysql_query('SELECT * FROM organisedraceparticipants WHERE carid = "'.$this->id.'"'), 'organisedraceparticipant')));
        }
        
        public function racerprofile(){
            return single(pluq(from(inres(mysql_query('SELECT * FROM racerprofiles WHERE id = "'.$this->racerprofileid.'"'), 'racerprofile'))));
        }
        
        public function __get($nm){ return $this->data[$nm]; }
        
        public function __set($nm, $val){ $this->data[$nm] = $val; }
        
        public function __isset($nm){ return isset($this->data[$nm]); }
        
 
    }
}
Not really elegent but it worked very well and is in use in a production environment namely a facebook game called Racing Fever. This version never had any type checking, i had to do everything by myself, but since there was very little input by the user it was quite simple.
crazycoders
Forum Contributor
Posts: 260
Joined: Tue Oct 28, 2008 7:48 am
Location: Montreal, Qc, Canada

Re: PSQLx2.0 overview and support

Post by crazycoders »

jshpro2 wrote:I'm not sure what the context of the $a->v = 'string' example you posted is.
Simple accessor example where $a was a boxed version of something and V, VAL, VALUE short accesses to the setValue and GetValue from the boxed object. You have to start somewhere to build something.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: PSQLx2.0 overview and support

Post by josh »

I prefer to use dynamic typing the language provides, but I think reading about design patterns would definitely be worthwhile, an example of what I would considered a good ORM is hibernate for java
crazycoders
Forum Contributor
Posts: 260
Joined: Tue Oct 28, 2008 7:48 am
Location: Montreal, Qc, Canada

Re: PSQLx2.0 overview and support

Post by crazycoders »

After reading the first 26 design patterns i found at http://sourcemaking.com/design-patterns-and-tips. i'm still not convinced what road to take to make the best possible ORM. It did give me some ideas as to what i can and should do but nothing more.

In the case of the ORM. I know for sure that object can be created manually using the new keyword. The user must do it by himself or the table adapter will do it for him. Objects represent a row in the database and update themselves directly. I think i will implement the object pool pattern but not in it's entirety. I will do it for table adapters and the rows it creates so that when the table adapter is called to ->save_changes(), it will call a save_changes() to all other rows.

For the row object i'm still debating if the memento is well indicated. Either it is a memento or simply a refresh from the database if the user wants to roll back. The Observer pattern will be implemented in all objects related to data persistence, it is necessary if we want to build a good object model in most cases anyway. Iterator pattern is for all datasources and it will implement the SPL iterator for PHP.

Else than that, i'm a little bit dumbstruct. I wonder how i can easily allow a user to superseed the ORM classes by creating extensions of them and still forcing the orm to create the new classes instead of the old one.

In the old ORM (v1) i used a simple

Code: Select all

If class_exists($tablename.'ex')){ $obj = new $tablename.'ex'($data); }else{ normal creation }
But i'm not sure thats a wise idea, it limits the user to a naming convention that i don't like. What do you think?
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: PSQLx2.0 overview and support

Post by josh »

crazycoders wrote:For the row object i'm still debating if the memento is well indicated. Either it is a memento or simply a refresh from the database if the user wants to roll back.
sounds like Active Record
crazycoders wrote:The Observer pattern will be implemented in all objects related to data persistence, it is necessary if we want to build a good object model in most cases anyway. Iterator pattern is for all datasources and it will implement the SPL iterator for PHP.
What will be observing what? This sounds like either Data Mapper or Unit of Work ( or both )
crazycoders
Forum Contributor
Posts: 260
Joined: Tue Oct 28, 2008 7:48 am
Location: Montreal, Qc, Canada

Re: PSQLx2.0 overview and support

Post by crazycoders »

Well if you want to extend the ORM object model to include business logic, it is always better to include events into it so that later objects may bind to those events and receive input from dataChanged events or any BLL event.

For the Memento, you are talking about ActiveRecords, what is that? The reason i'd put that in is to mimic the MS.NetFramework's System.Data.DataSet feature where a dataset keeps the previous data so that a applychanges can be done on disconnected data. At that point all the validation rules come into effect and may reject the changes. If so, you can do a rollback to the previous data. I see the Memento pattern as a way to rollback.

I will not implement it the way MS does but i will implement it as a rollback mechanism. I was thinking about it... while sleeping... and i think memento will be implemented using this strategy:

ORM objects keep their data in a private array called $data. This array is automatically indexed with versions version 0 being the first one. If a user calls "BeginTransaction", it will copy the data from the current index to the next and 0 becomes the new version. The user can call BeginTransaction on the object as many times as he wants but must call an equal number of times the Commit before pushing the changes to the database.

Each Commit or Rollback discards either the current dataset to return to the previous one or discards the previous dataset to keep the active one thus allowing you to commit changes incrementally without actually using more memory at a base. It uses memory only when the user asks to create a new copy of the object data.

This will allow easy incremental validation when importing form data into an object.

Code: Select all

 
//Try saving the data
try{
$obj->begintransaction();
$obj->index(0) = $_REQUEST[0];
$obj->index(1) = $_REQUEST[1];
$obj->index(2) = $_REQUEST[2];
$obj->index(3) = $_REQUEST[3];
$obj->commitchanges();
}catch(FieldValidationException $ex){
$obj->rollbackchanges();
}
 
//Continue processing normally
$obj->save_changes();
 
Here, if Field(2) fails validation, the object gets rolled back to it's original state without having to access the database again to retrieve the original data. When the trycatch is done, the obj saves itself to the database. So if nothing changed, the only problem is that a rather large SQL could be sent uselessly, but we could simply put that save_changes() in the try block. thats just a way of coding...
crazycoders
Forum Contributor
Posts: 260
Joined: Tue Oct 28, 2008 7:48 am
Location: Montreal, Qc, Canada

Re: PSQLx2.0 overview and support

Post by crazycoders »

I just checked activerecords (which is pattern it seems) and yes, about 90% of the pattern would be implemented, the only thing that would not is the retrieval. I leave the retrieval of the data to the datasource object. The object class representing a row of data would only be used to send CUD operations. Retrieve (SELECT) operations are too wild in flavors to be implemented in such a class because apart from the PK based retrieval, no other function is worth to be implemented into this.

Retrievals would be easier like this:
$mycar = $cars->where($cars->id->equals($carid))->single();

And in fact, they would be more intuitive than:
$mycar = car::getcarbyid($carid);

A secondly, retrieval in my system can only be made on datasources and a row reprensentation is not a datasource because it is not enumerable, it is only one element.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: PSQLx2.0 overview and support

Post by josh »

I'd recommend reading POEAA, its a catalog of Enterprise patterns, with a whole section on ORM patterns

http://www.amazon.co.uk/Enterprise-Appl ... 0321127420
http://martinfowler.com/eaaCatalog/
crazycoders
Forum Contributor
Posts: 260
Joined: Tue Oct 28, 2008 7:48 am
Location: Montreal, Qc, Canada

Re: PSQLx2.0 overview and support

Post by crazycoders »

You really are full of resources! Thanks i'll read that tonight!
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: PSQLx2.0 overview and support

Post by josh »

This guy is doing something similar to you
http://www.maxhorvath.com/2008/09/type- ... eased.html
crazycoders
Forum Contributor
Posts: 260
Joined: Tue Oct 28, 2008 7:48 am
Location: Montreal, Qc, Canada

Re: PSQLx2.0 overview and support

Post by crazycoders »

Thats cool, but it is only type hinting, it doesn't feature boxing and unboxing yet :) I might look into that PHAR feature later on. I think it allows you to load extensions into the language in a dynamic way. It could be then possible to create a boxing unboxing mechanism that would definitely reduce the overhead of PSQLx2.0.
crazycoders
Forum Contributor
Posts: 260
Joined: Tue Oct 28, 2008 7:48 am
Location: Montreal, Qc, Canada

Re: PSQLx2.0 overview and support

Post by crazycoders »

Ahhh nope, phar is an archive... wonder what is in there that allows him to override PHP like that.
Post Reply