2

I recently started learning plugin development for Craft CMS. I'm creating a plugin that deals with video content. For this example I'll use two models: MyPlugin_ChannelModel and MyPlugin_VideoModel. I've also setup two record classes that mirror the model's properties (MyPlugin_ChannelRecord and MyPlugin_VideoRecord).

The two record classes have a many to many relationship with each other. So, a channel can have multiple videos and a video can be in multiple channels.

I can create, delete and modify records. So far, so good. Now, this next part confuses me. I want to use these records in my templates. Ideally, I'd like to do something like:

{% for video in channel.videos %}
    {{ video.someProperty }}
{% endfor %}

However, I think the "rule" is that only services can interact with records. Models should be used in all other parts of a plugin to pass data (like passing data from a service to a template).

Since models do not have the relationship properties, I've written some getter methods on those models to help me get to my channels/videos from the opposite end of the relationship. An example for MyPlugin_ChannelModel:

public function getVideos()
{
    if ($this->_videos === null && $this->id !== null)
    {
        // Helper method that gets the record for this model
        $channelRecord = craft()->myPlugin_helpers->findRecordForModel($this);

        // Convert each related record back to a model
        $videoModels = array();
        foreach ($channelRecord->videos as $videoRecord) {
            $videoModel = new MyPlugin_VideoModel($videoRecord);
            $videoModels[] = $videoModel;
        }

        $this->_videos = $videoModels;
    }

    return $this->_videos;
}

It's nice that these getter methods automatically map to their corresponding property names ($model->getVideos() becomes model.videos in Twig), but this means I have to write each of these helper methods by hand on all my model classes to get the related models. Am I missing something, is there an easier way?

2

I would move that logic out of your model class and into a service method.

Your model could still have a thin wrapper for it:

public function getVideos()
{
    return craft()->myPluginVideoService->getVideosByChannelId($this->id);
}

Then your plugin's video service would have:

public function getVideosByChannelId($channelId)
{
    // Can memoize by channelId if this will be called frequently

    $result = craft()->db->createCommand()
        ->select('*')
        ->from('myVideoTable')
        ->where('channelId = :channelId', array(':channelId' => $channelId))
        ->queryAll();

    if ($result)
    {
        return MyPlugin_VideoModel::populateModels($result);
    }
}

The benefits are:

  1. Less cruft in your models, which should (as much as possible) be used as data transport objects

  2. You're not leaking records out of your service layer (like your original model was doing)

  3. You're building a consistent API at your service layer that any other classes (other models, other template variables, other services, other plugins, etc.) can go through.

But essentially you're correct. You'll need to write methods to expose the relationships you're looking for to the templates.

what is the reason you use the direct database API, in stead of the Record::model()->findAllByPk($pksArray) method?

Habit. Active records, while convenient for simple use-cases, have performance overhead associated with them, so usually I just default to DbCommand.

|improve this answer|||||
  • Ah right, that's a nicer way to structure it. Side question: what is the reason you use the direct database API, in stead of the Record::model()->findAllByPk($pksArray) method? My original question still stands though: am I right that the way to get the relations of your models is through your own service methods? Which you have to add manually for every relationship? – Nelis86 Aug 29 '16 at 10:04
  • Updated my answer. – Brad Bell Aug 29 '16 at 17:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.