SitePoint Sponsor

User Tag List

Results 1 to 11 of 11
  1. #1
    SitePoint Wizard
    Join Date
    Mar 2008
    Posts
    1,149
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    ORM with custom data types, other things

    I'm looking for an ORM that supports "custom data types." The data is stored in the database with regular ol' fields, but there is a transparent layer that transforms the data into something more useful when you access it, but it's transformed back into its intermediary form when saved back to the DB. Propel and Doctrine don't seem to do this, and the few others I've looked at in PHP don't do this either.

    I'd also prefer an ORM where I would be able to write my own logic for the classes (my own methods) that can act on their own object instance, so the objects are not just dumb data storage objects. Preferably, I don't want to have to extend some base class either, because I would like to extend my own classes. It'd also be cool if I could use getter and setter methods (but not actual getter and setters) such as getName() and setName(), although I know this is a bit awkward with PHP (since functions aren't first class objects), so I'm willing to compromise.

    They're both features of SQLAlchemy, and just SQLAlchemy alone makes me want to use Python for web development over PHP. *sighs*

    Oh, and preferably I'd like to be able to pass in an already instantiated PDO instance.

  2. #2
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,187
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    Seems like a reason to write your own.

    Quote Originally Posted by sk89q
    I'm looking for an ORM that supports "custom data types." The data is stored in the database with regular ol' fields, but there is a transparent layer that transforms the data into something more useful when you access it, but it's transformed back into its intermediary form when saved back to the DB. Propel and Doctrine don't seem to do this, and the few others I've looked at in PHP don't do this either.
    This is something I was also after so I implemented the concept of a transformation. Transformations may be specified in the model and they can be set for saving, filtering and selecting data. A simple example, of how that is defined for my common user class is shown below.

    Whenever the password field is selected its select transformation is applied to decrypt the password.

    When the password is saved to the database the password is encrypted. This is all done behind the scenes and only requires the transformations be declared in the model.

    The same is true for the created field. However, it only has transformation for turning time() into a appropriate MySQL timestamp when saving.

    {this} is a special string and refers to the value for that column. $1,$2,$3,... refer to a item at the position in the transformation array to bind to that spot. In the save transformation there is also the inclusion of {field name}. What that will do is reference the field and include its transformation as a substring. Particular useful if a password is being updated, yet the created column didn't change or the both changed in which case the created transformation would need to applied rather then referencing the fields value for the row.

    PHP Code:
    class User {

        const 
    salt 'salt';
        
        public static 
    $transformations = array(
            
    'pwd'=>array(
                
    'select'=>array('AES_DECRYPT(User.pwd,SHA1(CONCAT($1,User.created)))',self::salt)
                ,
    'filter'=>array('AES_ENCRYPT({this},SHA1(CONCAT($1,User.created)))',self::salt)
                ,
    'save'=>array('AES_ENCRYPT({this},SHA1(CONCAT($1,{created})))',self::salt)
            )
            ,
    'created'=>array(
                
    'save'=>'FROM_UNIXTIME({this})'
            
    )
        );

    You could make a request for this project: http://www.sitepoint.com/forums/showthread.php?t=619461

  3. #3
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Have done some exploration on stuff like this... basically use separate mappers for them. The mappers just map objects to a subset of columns of a table.


    PHP Code:
        /* Extending value objects... */
        
    class Length
        
    {
            public 
    $unit;
            public 
    $length;
            function 
    __construct($unit$length) { $this->unit $unit$this->length $length; }
        }

        class 
    LengthMapper extends mcMapper
        
    {
            function 
    __construct(mcSqlColumn $unitmcSqlColumn $length$className 'Length')
            {
                
    parent::__construct($className);
                
    $this->map('unit'$unit);
                
    $this->map('length'$length);
            }
        }

        class 
    Area extends Length
        
    {
            public 
    $width;

            function 
    __construct($unit$length$width)
            {
                
    parent::__construct($unit$length);
                
    $this->width $width;
            }

            function 
    getArea() { return $this->width $this->length; }
        }

        class 
    AreaMapper extends LengthMapper
        
    {
            function 
    __construct(mcSqlColumn $unitmcSqlColumn $lengthmcSqlColumn $width$className 'Area')
            {
                
    parent::__construct($unit$length$className);
                
    $this->map('width'$width);
            }
        }

        class 
    Volume extends Area
        
    {
            public 
    $height;

            function 
    __construct($unit$length$width$height)
            {
                
    parent::__construct($unit$length$width);
                
    $this->height $height;
            }

            function 
    getVolume() { return $this->getArea() * $this->height; }
        }

        class 
    VolumeMapper extends AreaMapper
        
    {
            function 
    __construct(mcSqlColumn $unitmcSqlColumn $lengthmcSqlColumn $widthmcSqlColumn $height$className 'Volume')
            {
                
    parent::__construct($unit$length$width$className);
                
    $this->map('height'$height);
            }
        }

        class 
    Cubicle
        
    {
            protected 
    $id;
            protected 
    $size;

            function 
    getSize() { return $this->size; }
            function 
    setSize(Volume $size) { $this->size $size; }
        }

        class 
    CubicleMapper extends mcEntityMapper
        
    {
            function 
    __construct(mcSqlTable $table$className 'Cubicle')
            {
                
    parent::__construct($table$className);
                
    $this->map('id'$table->id);
                
    $this->map('size', new VolumeMapper($table->unit$table->width$table->length$table->height));
            }
        } 

  4. #4
    SitePoint Wizard
    Join Date
    Mar 2008
    Posts
    1,149
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I would like to write my own, but that would take an awfully long amount of time. I've been thinking about it.

    The way SQLAlchemy does it is by having you define a type on the same level of the basic types (String, Integer, etc.).
    Code python:
    import sqlalchemy.types as types
    class HTMLEncodedString(types.TypeDecorator):
        impl = types.String
        def unescape(self, value):
            value = value.replace(">", ">")
            value = value.replace("&lt;", "<")
            value = value.replace("&quot;", '"')
            value = value.replace("'", "'")
            value = value.replace("<br>", "\n")
            value = value.replace("&amp;", "&")
            return value
        def escape(self, value):
            value = value.replace("&", "&amp;")
            value = value.replace(">", "&gt;")
            value = value.replace("<", "&lt;")
            value = value.replace('"', "&quot;")
            value = value.replace("'", "'")
            value = value.replace("\n", "<br>")
            return value
        def process_bind_param(self, value, engine):
            return self.escape(value)
        def process_result_value(self, value, engine):
            return self.unescape(value)
    Then...
    Code python:
    from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
    metadata = MetaData()
    users_table = Table('users', metadata,
        Column('id', Integer, primary_key=True),
        Column('name', String),
        Column('htmlname', HTMLEncodedString),
    )

    The advantage of this is that you can re-use HTMLEncodedString anywhere, and the transformation is done in PHP rather than in the DBMS. It's also less dependent on the DBMS, and it's a bit more abstracted. It doesn't combine multiple columns though (or, at least to my knowledge).

  5. #5
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by sk89q View Post
    I'm looking for an ORM that supports "custom data types." The data is stored in the database with regular ol' fields, but there is a transparent layer that transforms the data into something more useful when you access it, but it's transformed back into its intermediary form when saved back to the DB. Propel and Doctrine don't seem to do this, and the few others I've looked at in PHP don't do this either.

    I'd also prefer an ORM where I would be able to write my own logic for the classes (my own methods) that can act on their own object instance, so the objects are not just dumb data storage objects. Preferably, I don't want to have to extend some base class either, because I would like to extend my own classes. It'd also be cool if I could use getter and setter methods (but not actual getter and setters) such as getName() and setName(), although I know this is a bit awkward with PHP (since functions aren't first class objects), so I'm willing to compromise.

    They're both features of SQLAlchemy, and just SQLAlchemy alone makes me want to use Python for web development over PHP. *sighs*

    Oh, and preferably I'd like to be able to pass in an already instantiated PDO instance.
    Arborint and I are working on an ORM that fulfills every one of your requirements. If you have any other ideas for what we should include, feel free to post in this thread.

    Could you elaborate on what you mean by "custom data types"?

  6. #6
    SitePoint Wizard
    Join Date
    Mar 2008
    Posts
    1,149
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, for example, there might be a field in the table where everything is HTML encoded, but when I access the data, I don't want it to be HTML encoded. When I commit to the table, then the data has to be HTML encoded so that it works with everything else. All of this is entirely transparent to whatever uses the object in question.

    Or a UNIX timestamp could be converted to a PHP DateTime object when pulled out, and changed back into a timestamp when committed.

    I'll look through your thread. Ideally, it'd be a clone of SQLAlchemy...

  7. #7
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by sk89q View Post
    Well, for example, there might be a field in the table where everything is HTML encoded, but when I access the data, I don't want it to be HTML encoded. When I commit to the table, then the data has to be HTML encoded so that it works with everything else. All of this is entirely transparent to whatever uses the object in question.

    Or a UNIX timestamp could be converted to a PHP DateTime object when pulled out, and changed back into a timestamp when committed.
    Well we definitely have plans to support all that. It might be tricky without closures, though, as we're trying to make things work equally when specifying mappings in code and with metadata (definitely xml, possibly other formats).

    Currently our mappings look like this:

    PHP Code:
    class ConcreteMapper extends A_Orm_DataMapper    {
        
        public function 
    __construct($db)    {
            
    parent::__construct($db,'Post','posts');
            
    $this->mapProperty('id')->toColumn('id')->setKey();
            
    $this->mapMethods('getTitle','setTitle')->toColumn('title');
            
    $this->mapMethods('getBody','setBody')->toColumn('body');
            
    $this->mapMethods('getDatePublished','setDatePublished')->toColumn('date_published');
        }


    Maybe to do what you're asking, we could do something like this?
    PHP Code:
    $this->mapMethods('getBody','setBody')->toColumn('body')->encodeAs('html'); // use htmlencode()/htmldecode()
    $this->mapMethods('getDatePublished','setDatePublished')->toTimestamp('date_published')->useDateTime(); //hydrates setDatePublished with DateTime object, and inserts back in original format 
    We are really just at the beginning of this project (though the first mappings I showed are all working currently) and any suggestions you may have are more than welcome.

  8. #8
    SitePoint Wizard
    Join Date
    Mar 2008
    Posts
    1,149
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Does that imply that encodeAs(), toTimestamp(), etc. are implemented by the library out of the box?

  9. #9
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by sk89q View Post
    Does that imply that encodeAs(), toTimestamp(), etc. are implemented by the library out of the box?
    No but they will be, or something with similar functionality. I was just tossing around ideas though. I know we need to do what you're asking, I just don't know the best way to implement it.

  10. #10
    SitePoint Wizard
    Join Date
    Mar 2008
    Posts
    1,149
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I don't know how you'd make it extensible that way if you do it that way, and it also discounts your goal of trying to make it lightweight.

    Perhaps you define data types as classes, and if you want to define your own data type, you just make a class?

    Something like this:
    PHP Code:
    $this->mapMethods('getTitle','setTitle')->toColumn('title'SQLString);
    $this->mapMethods('getBody','setBody')->toColumn('body'HTMLEscapedString);
    $this->mapMethods('getDatePublished','setDatePublished')->toColumn('date_published'Timestamp); 
    Although without namespaces, it'd look very ugly since you can't just name the classes String or Integer. Unless you write the ORM for just 5.3+.

    P.S. I assume you can reference classes like that before 5.3, but I haven't been writing too much PHP lately, and I'm been used to more... err... capable languages.

  11. #11
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by sk89q View Post
    I don't know how you'd make it extensible that way if you do it that way, and it also discounts your goal of trying to make it lightweight.
    Well, we've got more important goals than trying to make it lightweight. I think our primary goal is to be as hands off as possible with domain objects, and that means supporting the most common transformations (timestamp to DateTime class is definitely on our list).

    Quote Originally Posted by sk89q View Post
    Perhaps you define data types as classes, and if you want to define your own data type, you just make a class?
    That's what I'm thinking right now, but I'd also like some common transformations to be available. So maybe encodeAs('html') above would use a class like this?

    PHP Code:
    class HtmlEncoding    {
        
        public function 
    encode($value)    {
            return 
    htmlspecialchars($value);
        }
        
        public function 
    decode($value)    {
            return 
    htmlspecialchars_decode($value);
        }


    Or maybe even:
    PHP Code:
    class HtmlEncoding    {

        public function 
    __construct($encoder$decoder)    {
            
    $this->encoder $encoder;
            
    $this->decoder $decoder;
        }
        
        public function 
    encode($value)    {
            return 
    call_user_func($this->encoder$value);
        }
        
        public function 
    decode($value)    {
            return 
    call_user_func($this->decoder$value);
        }




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
  •