Search code examples
phpmodel-view-controllercakephp

CakePHP 3: Properly writing functions for the Entity Model


I have a blog model, and I suspect I'm not writing my code correctly to best take advantage of CakePHP's MVC structure.

Here are some snippets from my Posts controller.

public function view() {
    $posts = $this->Posts->find('all')->contain([
        'Comments' => ['Users'],
        'Users'
    ]);
    $this->set(compact('posts'));
}

public function index() {
    $post = $this->Posts->find('all')->contain([
        'Users'
    ])->limit(20);
    $this->set(compact('post'));
}

This is a snippet from the index.ctp template

foreach ( $posts as $post ) {
    <div class="post">
        <h1 class="title>
            <?= h($post->title) ?>
            <small><?php echo $post->getCommentCount(); ?> comments</small>
        </h1>
        <div class="body">
            <?= h($post->body) ?>
        </div>
    </div>
 }

In my Post Entity, I have the following function

public function getCommentCount(){
    $count = 0;
    foreach ($this->comments as $comment){
        if ( $comment->isPublished() ) {
            $count += 1;
        }
    }
    return $count;
}

My problem is I need to call the getCommentCount function from the index.ctp where the $posts object has no comment children (which the function uses).

Am I misunderstanding how the Entity functions should be coded? Instead of accessing expected variables of this object which sometimes aren't there, should I be querying the database from the Entity? Is there another approach I should be doing?


Solution

  • Am I misunderstanding how the Entity functions should be coded?

    Yes, you do, because...

    Instead of accessing expected variables of this object which sometimes aren't there, should I be querying the database from the Entity? Is there another approach I should be doing?

    ...an entity should be a dumb data object. When you fetch data from there you add business logic. Any data manipulation should happen somewhere in the model layer, or a service. Most of the time people using CakePHP put code into the table object, which is somewhat OKish. I'm creating additional classes in the model layer for different things and namespace them App\Model\SomeModule or directly in my app root App\SomeModule and inject whatever else I need there (request, tables...).

    This code is also absolute inefficient:

    public function getCommentCount(){
        $count = 0;
        foreach ($this->comments as $comment){
            if ( $comment->isPublished() ) {
                $count += 1;
            }
        }
        return $count;
    }
    
    • It assumes all comments were loaded
    • It is required to get all comments to get an accurate count
    • it iterates over all comments to filter published comments
    • It does it inside the entity

    What if there are 500 comments? Why aren't you simply doing a count query on the database and filter them by their published status? But this would still require you to do one additional query per record. So for counts it is a lot more efficient and easy if you use the Counter Cache Behavior.

    If you really need to manipulate data after you fetched it but before rendering it use map/reduce. You can refactor your getCommentCount() and use map/reduce instead if you would like to stick to your inefficient way of doing it. The linked documentation even contains an example for counting stuff.