Welcome Guest, Not a member yet? Register   Sign In
[split] Arguing for an ORM
#11

(This post was last modified: 09-18-2018, 04:04 AM by sintakonte.)

alright i try to describe it in a pictured way

i came up with this topic because i saw the development in CI4 - your new model structure represents already about 70% of our current CI3 solution - and just because of this similarities i thought i might start a discussion to eventually add the remaining 30% (if this should be a composer package - its fine too).

Right now its a CI3 only application (but pretty much abstracted and extendable).

I can show you our structure and concrete examples what this "thing" is doing

Every model extends a model called CrudDb_Model which after all extends from CI_Model (to guarantee that this is always optional)

for every action we've interfaces such as
  • beforeRead_model
  • afterRead_model
  • beforeCreate_model
  • afterCreate_model
  • beforeUpdate_model
  • afterUpdater_model
  • beforeSave_model
  • afterSave_model
  • beforeDelete_model
  • afterDelete_model
this is already implemented in CI4 (although i might prefer interfaces rather than declaration of functions in arrays - but thats a matter of taste i guess)


Basic process is (based on our book example)
PHP Code:
class Book extends CI_Controller
{
    public function 
books()
    {
        
$this->load->model('resources/Book_model');
        
        
$colBooks $this->Book_model->read();
        
$objBook $colBooks->first();
        
    }



The method read returns always a Collection - with or without Entities from the called model. (The Collection system is built on the experience with backbone.js and merely laravel)
A collection is an ArrayObject with additional functionalities.

The connection system is built on a Relations Factory and provides the following types:
  • One Relation (e.g. a book has one author)
  • Many Relation (a book has many chapters)
  • ManyByIntermediate Relation (a book has many publishers - a publisher has many books)
There are 3 other relationtypes right now but those are customized and its not necessary to talk about them right now.

The Querybuilder is fully integrated and can be used as usual e.g. if you want all books with a chapter the devils advocate you simply call 


PHP Code:
$colBooks $this->Book_model->where('chapter:title''the devils advocate')->read(); 

As you can see - our current connection name is the prefix followed by a colon (as a indicator) and after that the db column (this is fully recursive and you can call a sub connection, subsub connection etc. 
For example if a chapter has pages and you defined this connection in the Chapters model you simply write

PHP Code:
$colBooks $this->Book_model->where('chapter:page:title''the devils advocate')->read(); 

The generated sql would look like

Code:
select books.* from books
left join chapters on (books.id = chapters.book_id)
where
chapters.title = 'the devils advocate'

Our implementation handles the n+1 select query issue and is always lazy loaded - which means as long as you don't call a connection - it won't be loaded.

Every entity knows about their relations - based on our $colBooks i can do the following

PHP Code:
foreach($colBooks as $objBook)
{
    
$colChapters $objBook->get('chapter');
    
    foreach(
$colChapters AS $objChapter)
    {
        echo 
$objChapter->get('title');
    }

    
$objAuthor $objBook->get('author');
    echo 
$objAuthor->name;
    
//alternative
    
echo $objBook->get('author:name');



There is a difference about the relationtype - if this is a Many Relation - you get a Collection - and if its a one relation you get the related Entity Object.

About actions like save, create, update - you can use all 3 methods, but they have one thing in common - they need to know the primary-key

There are 4 possibilities how this could happen:
  • a variable called primaryKey as a string
  • a variable called primaryKey as an array
  • based on the underlying Datasource - it looks e.g. for mysql for an field with a primary key and autoincrement attribute
  • if not given it tries to assume its id
you can save an Entity_Object or an array - array would look like

PHP Code:
$arrData = [
    
'author_id' => 1,
    
'title' => 'The Comedy of Errors',
    
'chapter' => [
        [
            
'title' => 'act 1'
        
],
        [
            
'title' => 'act 2'
        
],
        [
            
'title' => 'act 3'
        
]
    ]
];


try
{
    
$this->Book_model->save($arrData);
}
catch(
FormValidation_Exception $e)
{
    echo 
$e->getMessage();
    
print_r($e->getMessageArray());


And thats it - it would save the book and all of the connections which are related to.
Some might ask - and what happens if i want to save an author too - then you have to change your point of view - because you now want to save actually an author and not just a book which means you've to call the save method auf the Author_model and their relations;

The next thing is validation:

Here we made a mistake (our validation rules are defined in the entity object) - in CI4 - wich is much cleaner - its defined in the model, but nevertheless if you save something it always tries to validate.

Our system uses CIs Formvalidation Library and if you try to save data - it loops through the data structure - tries to find the related connections - collects the validation rules of each model/entity and validates against that - if something goes wrong, a FormValidation_Exception gets thrown and you get a Collection of FormValidation Errors.

some key facts:

  • every model is able to make connections to models which are using another database/source
  • every model uses as standard the db query builder object - but it doesn't matter if you inject another one - as long as you implement the query builder interface wich provides the following functions: select, from, where, or_where, where_in, or_where_in, where_not_in, or_where_not_in, like, or_like. The constructor of our CrudDb_Model looks like

    public function __construct(QueryBuilderInterface $objDb = null, $strDriverType = false, Relation_Manager $objRelationManager = null)
  • for example, we wrote our own query builder for Elastic Search and the Elastic Models talk to the Sql Models pretty easily 
    (its although a bit dirty  because the CI Querybuilder doesn't use the QueryBuilderInterface per default - so i needed to override the database function in MY_Loader.php in order to get that managed.
  • this only works with entities - because arrays are so tough to handle with referenceing etc.  - so every CrudDb_Model uses as standard a DbTable_Object - and every model can define their own (e.g. as Example in our Book_model i called the entity Book_Object)
This solution works for nearly 90-95% of our daily tasks. The rest is similar to that what we did before because you can't handle all functionality with this approach e.g. (complex subselects with subqueries and so on) - but we were fine with that.

Since the CrudDb_Model extends from CI_Model you always can bypass the Crud functionality. This is also what i meant before - it has to be optional, but it helps a lot if you use it.

(09-17-2018, 07:41 AM)ciadmin Wrote: @sintakonte What you are saying makes a lot more sense than most of the "ORM want" posts I have seen. I suggest you propose/describe an interface that would suit your perspective, for community discussion. If we can come up with a practical & generally acceptable way of tackling this, it could make its way into a future version of the framework.

i would like to - but i'm not sure if i can follow you. What do you understand with propose/describe an interface ?
Is there a standard procedure you follow?

(09-17-2018, 08:32 PM)kilishan Wrote: @ sintakonte What functionality does your current solution provide? And any chance you're able to open source that for a kick start to the project?

I, personally wouldn't be opposed to a simple solution that doesn't try to do everything that Eloquent/Doctrine/whatever does. I've started playing with such a project in my spare time, but have not gotten very far because it's at the bottom of my list of priorities for this project currently. If we did this, and I'm not saying we would by any stretch, it would be a separate repo that could be included through Composer.

Tbh the opensource idea just arised yesterday - because i saw the similarities in CI4 and i had finally some time to study CI4. But right now its a Ci3 only project - so i've to do a lot of work in order to get this implemented in CI4. The next question is - i can't manage single handed such a repo - so what would be the support of you guys here - or in other words would you take over this ? 
Beside the fact - i studied your code, and i'm really convinced you are by far the better developer.
So i'm not sure what i should do in order to get such a thing started Wink
Reply


Messages In This Thread
[split] Arguing for an ORM - by sintakonte - 09-17-2018, 12:39 AM
RE: [split] Arguing for an ORM - by ciadmin - 09-17-2018, 03:12 AM
RE: [split] Arguing for an ORM - by sintakonte - 09-17-2018, 04:04 AM
RE: [split] Arguing for an ORM - by Pertti - 09-17-2018, 06:33 AM
RE: [split] Arguing for an ORM - by sintakonte - 09-17-2018, 07:07 AM
RE: [split] Arguing for an ORM - by ciadmin - 09-17-2018, 07:41 AM
RE: [split] Arguing for an ORM - by skunkbad - 09-17-2018, 02:38 PM
RE: [split] Arguing for an ORM - by InsiteFX - 09-17-2018, 02:40 PM
RE: [split] Arguing for an ORM - by php_rocs - 09-17-2018, 03:40 PM
RE: [split] Arguing for an ORM - by kilishan - 09-17-2018, 08:32 PM
RE: [split] Arguing for an ORM - by sintakonte - 09-18-2018, 01:49 AM
RE: [split] Arguing for an ORM - by ciadmin - 09-18-2018, 07:03 AM
RE: [split] Arguing for an ORM - by ignitedcms - 10-02-2018, 10:29 PM
RE: [split] Arguing for an ORM - by Pertti - 10-03-2018, 12:23 AM
RE: [split] Arguing for an ORM - by InsiteFX - 10-03-2018, 03:36 AM



Theme © iAndrew 2016 - Forum software by © MyBB