Skip to content
Daniele Rosario edited this page Sep 5, 2016 · 2 revisions

The DataModel is a data extension to the Model. It is designed to talk directly to a database table and provides the necessary methods to handle all the data manipulation tasks.

Credits: the vernacular used by DataModel is heavily influenced by Laravel's Eloquent.

Database table naming conventions

FOF uses convention over configuration to figure out which database table each DataModel needs to talk to. By default, database tables are named #__component_view, e.g. #__example_items for a component named com_example and a Model named Item.

Please note that the table name always uses the same word in plural (items).

The (auto increment) primary key field is named component_view_id, e.g. example_item_id for a component named com_example and a model named Item. If your table does not have an auto incrementing field you will not be able to use the default implementation of FOF's data processing methods in DataModel.

If you need to supply a custom table name you can override the protected $this->tableName property in your model's constructor. Likewise, the name of the primary key field can be overridden by setting the $this->idFieldName property in your constructor.

Basic usage

Retrieving a record by Primary Key

$item = $model->find(1);

Retrieving a record by Primary Key or throw an Exception

try {
    $item = $model->findOrFail(1);
} catch (FOF30\Model\DataModel\Exception\RecordNotLoaded $e) {
    // Handle exception
}

Query the table through the Model using fluent interface

Important: This method automatically attaches the Filters behaviour to your Model.

$items = $model->where('price', '>=', 12.34)->take(10)->get();

foreach ($items as $item)
{
    var_dump ($item->title);
}

Query the table through the Model using raw WHERE clauses

You can attach as many raw SQL WHERE clauses to your Model before querying it.

$db = $model->getDbo();
$items = $model->whereRaw(
    $db->qn('price') . ' >= ' . $db->q(12.34)
)->take(10)->get();

foreach ($items as $item)
{
    var_dump ($item->title);
}

Count items

$howManyItems = $model->where('price', '>=', 12.34)->count();

Chunk results

If you get hundreds of results you may want to process them in small chunks to not hit the PHP memory limit.

$items = $model->chunk(200, function($item) {
    // Do something
});

Automatically assigning from the Model state

When creating new records you can have its fields automatically populated by the Model state. This can, however, be a security issue because the model state is by default populated from the request. This could result in a malicious user from manipulating the values of any field on your model! This is why this feature is turned off by default. You need to explicitly enable it through the fillable or guarded properties of the model.

Setting either fillable or guarded turns on automatic filling of fields in the constructor. If both are set only guarded is taken into account. Fields are not filled automatically outside the constructor.

Fillable attributes

The fillable property contains an array of fields which should be automatically populated from the Model state.

class Items extends DataModel
{
    protected $fillable = array('title', 'alias');
}

In this example only the title and alias fields will be automatically populated from the model's state.

Guarded attributes

This is the inverse of fillable. It tells the model which fields should never be populated by the model's state.

class Items extends DataModel
{
    protected $guarded = array('price', 'enabled');
}

In this example the price and enabled fields will never be automatically populated from the model's state.

Create, update and delete

Save a new record

$newItem = clone($model);
$newItem->reset();
// ...manipulate its properties here, then...
$newItem->save();

Alternatively

$dataForTheNewRecord = array('field1' => 1, 'field2' => 'whatever'); // New record's data
$newItem = clone($model);
$newItem->reset();
$newItem->save($dataForTheNewRecord);

In both cases get the new item's ID with

$newId = $newItem->getId();

Note: it's much easier using create() if you don't mind overwriting the attributes of your current model.

Create new records

$dataForTheNewRecord = array('field1' => 1, 'field2' => 'whatever'); // New record's data

// Create a new item and save it to the database
$newItem = $model->create($dataForTheNewRecord);

// Get a record by searching the database or create the record in the database if it doesn't exist
$newOrExistingItem = $model->firstOrCreate($dataForTheNewRecord);

// Get a record by searching the database or create a new record in memory, NOT in database
$newOrExistingItem = $model->firstOrNew($dataForTheNewRecord);

Update a record

$model->find(1);
$model->setFieldValue('field1', 123);
// or: $model->field1 = 123;
$model->save();

Save a record and its relations

$model->push();

Mass update records

For example, add 10% to the price of all items whose price is less than 12.34

$model->where('price', '<=', 12.34)->get(true)->transform(function ($item){
    $item->price += 0.1 * $item->price;
    $item->save();
});

Delete a record already loaded

$model->find(1);
$model->delete();

Delete a record by ID

$model->delete(1);

Update only the modified_on / modified_by time and user stamps

$model->find(1);
$model->touch();

Or tell it which user ID to use:

$model->find(1);
$model->touch(123);

Trashing (soft delete)

When the model's softDelete property is set to true the delete() method will perform a soft-delete, i.e. set the enabled field to -2 (trashed).

You can permanently delete a record by calling the forceDelete() method instead of delete().

You can forcibly trash, instead of hard delete, a record by using trash() instead of delete(). Calling trash() directly does not take the softDelete property into account.

You can untrash a record by calling the publish() or unpublish() method, depending on whether you want to mark the record as published or unpublished.

Handling errors

FOF3 leaves to the underlying JDatabaseDriver class the error management. This means that, in order to manage errors that comes from a db operation, like saving, you will have to catch the exception that the database throws.

...

try {
    $model->save($data);
} catch (\JDatabaseExceptionExecuting $e) {
    // $e contains ($query, $errorMessage, $errorNumber);
}

Magic fields

Some fields have special meaning for FOF. If your table has a field for a similar function under a different name please remember to map them using the aliasFields property of your model. The magic fields used by FOF are:

  • asset_id The Joomla! asset ID, used with the Assets behaviour.
  • access The Joomla! view access level
  • created_on Timestamp of record creation, auto-filled by save()
  • created_by User ID who created the record, auto-filled by save()
  • modified_on Timestamp of record modification, auto-filled by save(), touch()
  • modified_by User ID who modified the record, auto-filled by save(), touch()
  • locked_on Timestamp of record locking, auto-filled by lock(), unlock()
  • locked_by User ID who locked the record, auto-filled by lock(), unlock()
  • ordering Record ordering
  • enabled Publish status: -2 trashed, -1 archived, 0 unpublished, 1 published

The following fields are used by Joomla!'s UCM when you are using the ContentHistory behaviour. Their meaning depends on Joomla!. We just list them by name:

  • title
  • body
  • hits
  • publish_up
  • publish_down
  • params
  • featured
  • metadata
  • language
  • images
  • urls
  • version
  • ordering
  • metakey
  • metadesc
  • cat_id
  • xreference
  • asset_id

Relationships

Please read the Model relations page.

Behaviours (observers)

Please read the Model behaviors page.

Collections

Whenever you use the get method or a relationship FOF will return a FOF30\Model\DataModel\Collection object. It implements the ArrayAccess and IteratorAggregate interfaces, meaning that you can use it just like an array. On top of that it comes with a host of useful methods.

Credits: The collection class is ported from Laravel 4.2.

Tip: Even though the following examples demonstrate the effects on get(), the methods presented have better practical use against relations. It's a bad idea post-processing records after fetching them from the database using get() if you can do the filtering while fetching them from the database, using WHERE clauses.

Checking if a key exists

$items = $model->get();

if ($items->contains(123))
{
    // Do something
}

Converting to arrays and JSON

You can convert a collection to a dumb PHP array or a JSON string

$itemsArray = $model->get()->toArray();
$itemsJson = $model->get()->toJson();

Typecasting a collection to a string converts it to JSON:

$itemsJson = (string) $model->get(); 

Iterating elements

Using each() you can iterate through the collection's records. This allows you to modify each record (record post-processing):

$items = $model->get()->each(function($item)
{
    // Do something
});

Filtering items

You can filter the elements of a collection. The callback needs to return true for the element to be included in the filtered collection, false to dispose of it.

$items = $model->get()->filter(function($item)
{
    return $item->enabled || ($item->created_by == \JFactory::getUser()->id);
});

Sort a collection

$items = $model->get()->sortBy(function($item)
{
    return $item->ordering;
});

or

$items = $model->get()->sortBy('ordering');

Data transformations

Sometimes you need to store data in your table in text format when their natural format is something different, e.g. an array or a JRegistry object. Two practical cases are:

  • Joomla!-style params columns which hold the JSON-encoded contents of a JRegistry object. When getting the record off the database you want to instantiate it into a JRegistry object. When saving the record to the database you want to wrap it back to a JSON string.
  • Poor man's many-to-many relations without a glue table. OK, this is a bad idea from a database normalisation perspective but we all need to deal with legacy code. For example, assigning multiple user groups to a record and encoding it as a comma separated string such as "1, 2, 3, 4". When getting the record off the database we want it converted to array(1, 2, 3, 4) and when saving the record we want it rolled back into a string.

In FOF 1.x and 2.x handling these cases was a very precarious affair, involving all sorts of Table and Model trickery. FOF 3.0 uses magic methods to transform the data after bind and before save.

Transform on bind

FOF provides a simple way to transform your field data when they are bound to a Model object. Define a public or protected getSomethingAttribute method where something is the name of your field. Do note that the field name needs to be camelCased even if your field is snake_cased.

For example, handling a params field which stores JSON data in the database but is accessible as a JRegistry object through your model:

<?php

namespace Acme\Example\Admin\Model;

class Maker extends FOF30\Model\DataModel
{
    protected function getParamsAttribute($value)
    {
        // Make sure it's not a JRegistry already
        if (is_object($value) && ($value instanceof \JRegistry))
        {
            return $value;
        }
        
        // Return the data transformed to a JRegistry object
        return new \JRegistry($value);
    }
}

Transform before save

FOF provides a simple way to transform your field data to a suitable database representation before it's saved to the database. Define a public or protected setSomethingAttribute method where something is the name of your field. Do note that the field name needs to be camelCased even if your field is snake_cased.

For example, handling a params field which stores JSON data in the database but is accessible as a JRegistry object through your model:

<?php

namespace Acme\Example\Admin\Model;

class Maker extends FOF30\Model\DataModel
{
    protected function setParamsAttribute($value)
    {
        // Make sure it a JRegistry object, otherwise return the value
        if (!is_object($value) || !($value instanceof \JRegistry))
        {
            return $value;
        }
        
        // Return the data transformed to JSON
        return $value->toString('JSON');
    }
}

Converting to array and JSON

You can convert a model to a dumb PHP array or a JSON string

// Conversion to dumb array
$dumbArray = $model->toArray();

// Conversion to JSON data, unformatted (compact)
$compactJsonData = $model->toJson();

// conversion to JSON data, formatted for pretty printing (with indentations, newlines etc)
$formattedJsonData = $model->toJson(true);
Clone this wiki locally