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
#12

@sintakonte Your last post *is* describing how your relations work, and is a good starting point Smile

A proposed iterface would be those classes or interfaces that would need to be implemented conforming to CI4, i.e. a CI4-specific API. There is no panic to this, as we will be just a bit busy for the next few weeks getting 4.0.0-alpha.1 out the door!
Reply
#13

I personally think this is an interesting topic, and worthy of further thought - if CI4 wants to compete and stay relevant.

Now the problem is, what do people consider the absolute essentials for a MVC framework? I think this is a question we need to answer. Some think needing a permission control built in and ORM built in ARE essential?

The authors, who are doing an excellent job so far with CI4, are against including these, personally I agree, but many disagree.

How do we as developers decide what are the bare bone tools and what are not? As the technological landscape changes we have to keep addressing these to stay relevant, in the not so distant future I believe concurrency and light weight threaded applications will be considered essential. Keep asking those questions.

Thanks
Practical guide to IgnitedCMS - Book coming soon, www.ignitedcms.com
Reply
#14

(10-02-2018, 10:29 PM)ignitedcms Wrote: Now the problem is, what do people consider the absolute essentials for a MVC framework? I think this is a question we need to answer. Some think needing a permission control built in and ORM built in ARE essential?

I personally don't think CodeIgniter should go down the route of providing loads of built in functionality out of the box.

You will never cover all the possibilities. For example, permissions. You get your basic per user or per group permissions, but then you have companies with very weird pipeline, article author had to submit their article for review, if review passed, it was sent for review in legal department, etc and only then author could publish the article publicly, but not edit it after review.

I don't think anyone could have for-seen that scenario until I was working on a website project that worked that way.

The most it should do is have some basics in place (which from what I've seen it already does), that allow quick in/out between model and DB, but make it easily extendable for developers, depending on their project. Need specific rules before save? Sure, here's the "hook" you can use to add your custom checks before we save data to DB.

It would be better for CI eco-system if all that ORM stuff comes in form of helper libraries developers write. Then maybe I'll release this really fancy workflow one, and you might release something else, and 3rd developer could choose what fits best with their current project, or write their own.

I completely understand the need to have some "plug-ins" available so you could just pick and mix and get project off the ground really quick, but I do not think it should be part of core framework.
Reply
#15

This should be an add in through Composer. FuelPHP doe's a similar thing but calls them Packages.

We need to keep CodeIgniter lean and mean not bloated.
What did you Try? What did you Get? What did you Expect?

Joined CodeIgniter Community 2009.  ( Skype: insitfx )
Reply




Theme © iAndrew 2016 - Forum software by © MyBB