ORM
Introduction
Dreamfork includes an object-relational mapper (ORM) that enhances the experience of interacting with your database. In the realm of ORM, each database table is associated with a corresponding "Model" used for interactions. Besides retrieving records, ORM models provide capabilities for inserting, updating, and deleting records from the associated table.
Generating Model Classes
To begin, let's create an ORM model. Models are conventionally stored in the App\Models directory and extend the Framework\Database\ORM\Model class. You can generate a new model using the make:model dfork command:
php dfork make:model Order
ORM Model Conventions
Models generated by the make:model command will be located in the App/Models directory. Let's take a look at a basic model class and discuss some key conventions within the ORM:
<?php
namespace App\Models;
use Framework\Database\ORM\Model;
class Order extends Model
{
// ...
}
Table Names
fter reviewing the example above, you might have observed that we didn't explicitly inform the ORM about the database table associated with our Order model. By convention, the "snake case" plural name of the class is used as the table name unless another name is explicitly specified. In this case, ORM assumes that the Order model stores records in the orders table, while an OrderController model would store records in an order_controllers table.
If your model's corresponding database table deviates from this convention, you can manually specify the table name by defining a table property on the model:
<?php
namespace App\Models;
use Framework\Database\ORM\Model;
class Order extends Model
{
protected $table = 'my_orders';
}
Primary Keys
ORM also assumes that each model's corresponding database table has a primary key column named id. If your database uses a different primary key column, you can define a primaryKey property on your model to specify the correct column:
<?php
class Order extends Model
{
protected $primaryKey = 'order_id';
}
Moreover, ORM assumes that the primary key is an incrementing integer value, implying that ORM automatically casts the primary key to an integer. If you intend to use a non-incrementing or a non-numeric primary key, you must define a incrementing property on your model and set it to false:
<?php
class Order extends Model
{
public $incrementing = false;
}
Timestamps
By default, ORM expects created_at and updated_at columns to exist on your model's corresponding database table. ORM will automatically set these column's values when models are created or updated. If you don't want these columns to be automatically managed by ORM, you should define a timestamps property on your model with a value of false:
<?php
class Order extends Model
{
public $timestamps = false;
}
If you need to customize the names of the columns used to store the timestamps, you may define CREATED_AT and UPDATED_AT constants on your model:
<?php
class Order extends Model
{
const CREATED_AT = 'created_date';
const UPDATED_AT = 'updated_date';
}
Default Attribute Values
By default, a newly instantiated model instance will not contain any attribute values. If you wish to set default values for certain attributes, you can define an attributes property on your model. Attribute values placed in the attributes array should be in their raw, "storable" format as if they were just read from the database:
<?php
class Order extends Model
{
protected $attributes = [
'status' => 'new'
];
}
Fillable Attributes
By default, a model doesn't have any defined attributes that it can edit or fill. If you plan to create new instances or update existing ones for a given model, you must explicitly define the fillable attributes. The convention is to specify attributes with which you intend to work.
<?php
class Order extends Model
{
protected $fillable = [
'order_id', 'user_id', 'name', 'status'
];
}
If you want to allow editing or filling of all attributes in your model without specifying them one by one in the fillable array, you can use the guarded property and set it to false. This way, all attributes of the model will become automatically editable.
<?php
class Order extends Model
{
protected $guarded = false;
}
If you don't define fillable attributes or guarded property, operations like
insertorupdatewill not be executed due to the lack of available attributes. This situation won't trigger any exceptions in the framework, so it's crucial to ensure the proper completion of these attributes.
Hidden & Visible Attributes
By default, all attributes of a model are utilized for serialization. However, there are situations where we want to hide certain attributes, such as the password attribute, which should never be sent anywhere during serialization. In such cases, we can use the hidden property by specifying an array of attributes to be concealed in serialization.
<?php
class Order extends Model
{
protected $hidden = [
'credit_card_number'
];
}
If the majority of attributes in our model should not be utilized for serialization, we can use the visible property. By specifying the attributes in the visible array, we indicate which ones are allowed for serialization. Consequently, all other attributes not defined in visible will be automatically hidden.
<?php
class Order extends Model
{
protected $visible = [
'name', 'date'
];
}
Working With ORM Models
Working with ORM models is quite similar to working with the query builder, as ORM essentially extends the capabilities of the builder. Therefore, using either the query builder or ORM model can yield nearly identical results. As a result, we won't reiterate every functionality, as they are already described in the previous section. Instead, we will focus on specific features dedicated to ORM models.
Differences Between ORM Model And Query Builder
As mentioned earlier, the results obtained using the query builder and ORM model are nearly identical, but there is an important distinction to be aware of. Consider the following example:
$resultQuery = DB::table('users')->where('id', 1')->first(); // stdClass
$resultORM = User::where('id', 1)->first(); // User
Code using the query builder will return an object of the stdClass class, whereas an ORM model will return an object of the class corresponding to the model in use.
In the case of retrieving not just one but multiple records from the database, the situation is similar. The Query Builder will return an object of the Framework\Support\Collections\Collection class, while the ORM model will return an object of the Framework\Database\ORM\Collection class, which is an extension of the Framework\Support\Collections\Collection class.
$resultQuery = DB::table('users')->get(); // Framework\Support\Collections\Collection
$resultORM = User::get(); // Framework\Support\Collections\Collection
Retrieving Models
Once you have created a model and its associated database table, you are ready to start retrieving data from your database. Each ORM model serves as a powerful query builder, enabling you to fluently query the database table associated with the model. The model's all method will retrieve all records from the model's associated database table:
foreach (User::all() as $user) {
echo $user->name;
}
Building Queries
The ORM all method will return all the results in the model's table. However, since each ORM model functions as a query builder, you can add additional constraints to queries and then invoke the get method to retrieve the results:
$users = User::where('active', 1)->limit(10)->get();
Retrieving Single Model
In addition to retrieving all records matching a given query, you can also fetch single records using the find or first methods. Instead of returning a collection of models, these methods provide a single model instance:
$users = User::find(1); // Retrieve a model by its primary key
$user = User::where('id', 1)->first();
Inserting New Model
Certainly, when using ORM, it's not just about retrieving models from the database; we also need to insert new records. Thankfully, ORM makes it simple. To insert a new record into the database, instantiate a new model instance, set attributes on the model, and then call the save method on the model instance:
$user = new User;
$user->name = 'John';
$user->save();
In this example, we assign value to the name attribute of the App\Models\Flight model instance. When we call the save method, a record will be inserted into the database. The model's created_at and updated_at timestamps will automatically be set when the
Alternatively, you can use the create method to "save" a new model using a single PHP statement. The inserted model instance will be returned to you by the create method:
$user = User::create([
'name' => 'John'
)];
If you don't define fillable attributes or guarded property, operations like
insertorupdatewill not be executed due to the lack of available attributes. This situation won't trigger any exceptions in the framework, so it's crucial to ensure the proper completion of these attributes.
Updating Existing Model
The save method can also be used to update models that already exist in the database. To update a model, retrieve it, set any attributes you wish to update, and then call the model's save method. Once again, the updated_at timestamp will automatically be updated, eliminating the need to manually set its value:
$user = User::find(1);
$user->name = 'Adam';
$user->save();
Updates can also be performed against models that match a given query. In this example, all users that are inactive will be marked as active:
User::where('active', 0)->update(['active' => 1]);
The update method expects an array of column and value pairs representing the columns that should be updated. It returns the number of affected rows.
Deleting Model
To delete a model, you may call the delete method on the model instance:
$user = User::find(1);
$user->delete();
Certainly, you can construct an ORM query to delete all models matching your query's criteria. In this example, we will delete all users that are marked as inactive.
$deleted = User::where('active', 0)->delete();
Serialization
To convert a model to an array, you should use the toArray method. This method is recursive, so all attributes will be converted to arrays:
$user = User::find(1);
return $user->toArray();
Also the attributesToArray method may be used to convert a model's attributes to an array:
$user = User::find(1);
return $user->attributesToArray();
You can also convert entire collections of models to arrays by calling the toArray method on the collection instance:
$users = User::all();
return $users->toArray();
Since models and collections are converted to JSON when cast to a string, you can return ORM objects directly from your application's routes or controllers. Dreamfork will automatically serialize your ORM models and collections to JSON when they are returned from routes or controllers. It's worth noting that in certain situations, the automatic conversion may not work as expected. In such cases, you may need to manually use the toArray method to ensure proper conversion.
Ensure to customize attributes that may appear in serialization using the hidden/visible properties. These properties allow you to control which attributes will be included or excluded during the serialization process.