Learn the difference between entities and models in CodeIgniter 4.x and how they are distinct from other programming frameworks.
published on 26 Mar 2023 in CodeIgniter PHPIn many programming frameworks that use the Model-View-Controller (MVC) paradigms and are database-driven using an Object-Relational Mapping (ORM) system, the terms "entity" and "model" are often used interchangeably to refer to the same thing: a class that represents a data entity in the database. For example, in the Laravel framework, "Eloquent models" are used to represent both the data entity and the data access and manipulation logic. Similarly, in the .NET Entity Framework, "entity types" represent both the data entity and the associated behavior. However, in CodeIgniter 4.x, there is a distinct separation between entities and models, and understanding this distinction is essential to building effective applications.
In CodeIgniter 4, which is sometimes referred to in a shorter form as CI4, entities, and models serve different purposes and have some key differences.
- Entities:
Entities in CodeIgniter4 represent individual objects within the system. They are used to define the structure and behavior of these objects, allowing for them used for easier manipulation of data. This makes them ideal to be used also as data transfer objects also known by the acronym DTO. Entities typically encapsulate the properties and methods associated with a specific type of object, such as a user or a blog post.
CodeIgniter supports Entity classes as a first-class citizen in it’s database layer, while keeping them completely optional to use. They are commonly used as part of the Repository pattern, but can be used directly with the Model if that fits your needs better.
Entities can be used for data manipulation as well. They provide a structured way to work with data, ensuring that data is processed and stored consistently throughout the application.
Some key points about entities in CodeIgniter4:
- They are plain PHP classes that encapsulate the properties and methods of a specific object.
- They can define getter and setter methods to control how properties are accessed and modified.
- They can include data type casting and data transformation logic.
- They are optional and can be used with or without models.
- Models:
Models in CI4 are responsible fo handling the interaction between the application and the database, as well as determining what object type should be mapped to a database row. For example a database row can be mapped to an entitiy like in an ORM use-case. CodeIgniter 4.x allows mapping database rows to arrays or generic objects other than entities.They provide a layer of abstraction that simplifies database queries and makes it easy to work with data without writing raw SQL queries.
Models define the structure of the database table and handle the CRUD (Create, Read, Update, Delete) operations for the corresponding table. They can also define relationships between different tables, making it easier to work with related data.
Some key points about models in CodeIgniter 4:
- They basically extend the
CodeIgniter\Model
class, which provides built-in methods for CRUD operations. - They can and should be used for data validation (such as validation following form submissions) which would otherwise become the responsibility of the controller.
- They are used to interact with the database, handling data retrieval, insertion, updating, and deletion.
- They can be used to define relationships between tables, such as one-to-one, one-to-many, or many-to-many relationships - although
- They work well with entities, allowing you to retrieve and store entities directly from and to the database.
You can find example code for a CI4 entity and model below. In this case, these are the City.php entity and CityModel.php, which we generated using CodeIgniter Wizard reverse-engineering a city table where the 'enabled' column was tinyint(1) :
<?php
namespace App\Entities\Admin;
use CodeIgniter\Entity;
class City extends \CodeIgniter\Entity\Entity {
protected $attributes = [
"id" => null,
"city_name" => null,
"country_code2" => null,
"country_code3" => null,
"district" => null,
"enabled" => false,
"additional_info" => null,
"created_at" => null,
"updated_at" => null,
];
protected $casts = [
"enabled" => "boolean",
];
}
The source code of the model counterpart of the "cities" table is:
<?php
namespace App\Models\Admin;
class CityModel extends \App\Models\BaseModel {
protected $table = "city";
/**
* Whether primary key uses auto increment.
*
* @var bool
*/
protected $useAutoIncrement = true;
protected $allowedFields = [
"city_name",
"country_code2",
"country_code3",
"district",
"enabled",
"additional_info",
];
protected $returnType = "App\Entities\Admin\City";
public static $labelField = "city_name";
protected $validationRules = [
"additional_info" => [
"label" => "Cities.additionalInfo",
"rules" => "max_length[4294967295]",
],
"city_name" => [
"label" => "Cities.cityName",
"rules" => "trim|required|max_length[60]",
],
"country_code2" => [
"label" => "Cities.countryCode2",
"rules" => "trim|max_length[2]|permit_empty",
],
"district" => [
"label" => "Cities.district",
"rules" => "trim|max_length[42]",
],
];
protected $validationMessages = [
"additional_info" => [
"max_length" => "Cities.validation.additional_info.max_length",
],
"city_name" => [
"max_length" => "Cities.validation.city_name.max_length",
"required" => "Cities.validation.city_name.required",
],
"country_code2" => [
"max_length" => "Cities.validation.country_code2.max_length",
],
"district" => [
"max_length" => "Cities.validation.district.max_length",
],
];
public function findAllWithCountry( string $selcols = "*", int $limit = null, int $offset = 0 ) {
$sql =
"SELECT t1." .
$selcols .
",t2.country_name AS country FROM " .
$this->table .
" t1LEFT JOIN country t2 ON t1.country_code3 = t2.iso_code_alpha3";
if ( !is_null( $limit ) && intval( $limit ) > 0 ) {
$sql .= " LIMIT " . $limit;
}
if ( !is_null( $offset ) && intval( $offset ) > 0 ) {
$sql .= " OFFSET " . $offset;
}
$query = $this->db->query( $sql );
$result = $query->getResultObject();
return $result;
}
}
The above model, generated by CodeIgniter Wizard , is not only substantially rich with a complete documentation of the associated database table and fields that are allowed to be used as well as the form validation rules, but it also contains some methods for looking up the database conforming the 'active record' pattern.
In OOPP (object-oriented PHP programming), model classes in modern frameworks, such as Laravel and CodeIgniter, do not necessarily indicate all of their underlying fields explicitly, and even if the fields are coded as class attributes, their data types can remain unknown - at least until PHP v.8.2 which brings strongly-typed attributes.
When a query method like "findAllWithCountry" is invoked, the return type is explicitly indicated as the City entity. Although an initial mapping from a MySQL result-set will set the "enabled" attribute to 1 or 0 (because it is tinyint(1) in the DB), the entity casts attribute indicates that the 'enabled' field is supposed to be a boolean, therefore, 1s and 0s coming from the DB will set it to true and false. Casts therein are not limited to booleans, but they could have been mapped to other types, including but not necessarily limited to integers, strings, and dates.
In summary, entities in CI4 represent individual objects and their behavior, while models handle the interaction between the application and the database. Entities are optional and can be used alongside models to provide a more structured approach to data manipulation and validation. CodeIgniter Wizard, our MySQL/MariaDB-driven CRUD code generator, generates models and entities in full with fillable attributes and form validation logic for every database table you choose to include in CRUD modules to be created.
Dave
Prior to engaging with this enlightening article, I had often perceived the terms entity and model to be synonymous in the realm of CodeIgniter4; however, this comprehensive explanation has clearly illuminated their distinctive roles for me.Harvey
I agree. Kudos to the author for providing a comprehensive explanation.