Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

The ObjectModel class

Writing insert() and update() by hand for every table is tedious, and it is easy to forget to check the values. ObjectModel is a core base class that represents one table row as an object: fields become properties, and the class takes care of saving, validating and protecting values. Products, orders and customers in the core itself are built the same way.

Where the class lives

Module models are kept in the classes folder. Create the file by hand:

modules/acmehello/
├── acmehello.php
├── classes/
│   └── AcmeReview.php
├── upgrade/
└── views/

Describing the model

<?php
// modules/acmehello/classes/AcmeReview.php
class AcmeReview extends ObjectModel
{
    public $id_product;
    public $id_customer;
    public $author;
    public $rating = 5;
    public $content;
    public $date_add;
 
    public static $definition = [
        'table' => 'acmehello_review',
        'primary' => 'id_review',
        'fields' => [
            'id_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
            'id_customer' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
            'author' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 100],
            'rating' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'],
            'content' => ['type' => self::TYPE_STRING, 'validate' => 'isMessage'],
            'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
        ],
    ];
}

The public properties mirror the table columns. The $definition array names the table without the prefix, the primary key and the fields. The class name starts with the module prefix so that it does not clash with a core class.

Loading the file

The engine does not know about the file until it is included. Add a line at the top of the main file, after the _PS_VERSION_ check:

// modules/acmehello/acmehello.php
require_once __DIR__ . '/classes/AcmeReview.php';

Now the AcmeReview class is available wherever the module is loaded.

Field description keys

KeyWhat it sets
typeThe type: TYPE_INT, TYPE_BOOL, TYPE_STRING, TYPE_FLOAT, TYPE_DATE, TYPE_HTML
validateThe name of a Validate class method used for the check
requiredThe field is mandatory
sizeThe maximum string length

Creating a record

Rewrite the addReview() method of the main class to use the model:

// modules/acmehello/acmehello.php
public function addReview(int $productId, string $author, int $rating, string $content): bool
{
    $review = new AcmeReview();
    $review->id_product = $productId;
    $review->id_customer = (int) $this->context->customer->id;
    $review->author = $author;
    $review->rating = $rating;
    $review->content = $content;
 
    return $review->add();
}

add() checks the fields against $definition, escapes the values and runs the insert. The date_add field is filled with the current time automatically. After the insert the id of the new row is in $review->id.

Loading, changing, deleting

$review = new AcmeReview(7);
 
if (Validate::isLoadedObject($review)) {
    $review->rating = 4;
    $review->update();
}
 
$review->delete();

An id in the constructor loads the row from the database. If there is no row with that id, the object is created empty — that is why the result is checked with Validate::isLoadedObject().

MethodWhat it does
add()Inserts a new row
update()Saves changes
save()Chooses add() or update() itself
delete()Deletes the row
validateFields(false)Checks the fields, returns true or false

Validation errors

If a value fails the check, add() and update() throw a PrestaShopException. User data is checked beforehand:

if (!$review->validateFields(false)) {
    return false;
}

With the false argument the method does not throw but returns the result.

Lists still go through DbQuery

A model works with one row. The list of a product's reviews is still fetched with the getReviews() method from the previous lesson — a DbQuery query.

Common mistake

A field is added to $definition, but a public property with the same name is not declared in the class, or the other way round. The value is silently not saved or not loaded. Every column must be in three places: in the table, in the class properties and in fields.

Thanks for reading!

Donate to the site — we work hard for you.