-
Notifications
You must be signed in to change notification settings - Fork 0
The DataModel
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.
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.
$item = $model->find(1);
try {
$item = $model->findOrFail(1);
} catch (FOF30\Model\DataModel\Exception\RecordNotLoaded $e) {
// Handle exception
}
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);
}
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);
}
$howManyItems = $model->where('price', '>=', 12.34)->count();
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
});
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.
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.
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.
$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.
$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);
$model->find(1);
$model->setFieldValue('field1', 123);
// or: $model->field1 = 123;
$model->save();
$model->push();
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();
});
$model->find(1);
$model->delete();
$model->delete(1);
$model->find(1);
$model->touch();
Or tell it which user ID to use:
$model->find(1);
$model->touch(123);
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.
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);
}
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
Please read the Model relations page.
Please read the Model behaviors page.
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.
$items = $model->get();
if ($items->contains(123))
{
// Do something
}
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();
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
});
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);
});
$items = $model->get()->sortBy(function($item)
{
return $item->ordering;
});
or
$items = $model->get()->sortBy('ordering');
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.
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);
}
}
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');
}
}
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);
FOF (Framework on Framework) and its documentation are Copyright © 2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd.
FOF is Open Source Software, distributed under the GNU General Public License, version 2 of the license, or (at your option) any later version.
The FOF Wiki content is provided under the GNU Free Documentation License, version 1.3 of the license, or (at your option) any later version.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be found on the GNU site.