Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Laravel Cheatsheet - Models, Factories, and Database Queries

Related Posts

Models

Artisan - Create Model Class

php artisan make:model MyModel

Manually Specifying Table Name

If Laravel can't correctly guess your table name from your model's name, then you may need to manually specify it by setting the $table attribute.

protected $table = 'my_table_name';

Using UUID Primary Keys

I created a separate post for creating a trait that one adds to a model for making it use UUIDs for the primary key/identifier, but is you wish to manually add the code to the model, then you would need to perform the steps below:

If you are using UUIDs as your primary key, be sure to add the following to your models:

protected $primaryKey = 'uuid';

Also, be sure to add the uuid to your model's $casts attribute so it remains a string rather than trying to be casted to an integer.

protected $casts = [
    'uuid' => 'string',
];

Also be sure to set (otherwise it will be cast to integer after calling save()):

public $incrementing = false;

Soft Deletes

To make use of Laravel's soft-delete functionality, just make sure to add the \Illuminate\Database\Eloquent\SoftDeletes trait like so:

class User extends Authenticatable
{
    use \Illuminate\Database\Eloquent\SoftDeletes;

Then make sure to have the deleted_at column in the relevant table, allowing null values. If you set Laravel to using unix timestamps (below), then it needs to be an integer type.

Unix Timestamps for created_at and updated_at

If you would rather not use the SQL timestamp type, and prefer to work with unix timestamp integers, you can get Laravel to do this by editing your model and adding:

public function getDateFormat() { return 'U'; }

Disabling Timestamps

By default, Laravel models are expecting the tables to have the created_at and updated_at columns. If you don't want to have these, then add the following to your model:

public $timestamps = false;

Seeding

Artisan - Create Seeder Class

php artisan make:seeder MyTableSeeder

If this seeder is used for populating the database, then you may wish to call it from the DatabaseSeeder.php file like so:

    public function run()
    {
        $this->call([
            MyTableSeeder::class,
        ]);
    }

Factories

Whilst creating your seeder, you are probably going to want to create a lot of model objects with random data, to populate your database to test/dev with. This is where factories come in. Whilst factories could be used for creating "real" models, it is best to use them for creating "fake" models for seeding with. This is why mass assignment protection is automatically disabled when creating models through factories..

Create Factory

Use the following command to create a factory for "MyModel".

php artisan make:factory MyModelFactory --model=MyModel

If a factory is not specific to a given model, then feel free to just remove the --model=MyModel part.

Apply It To the Model

Once you have created the factory, you need to "connect" it to the model (e.g. the model will use that factory). This is because in Laravel, one performs operations on the model, rather than calling the factory directly.

We do this by adding the Illuminate\Database\Eloquent\Factories\HasFactory trait to the model.

Laravel will use its smarts to try and auto-magically figure out the factory that relates to the model, but one can manually specify the factory by overwriting the newFactory method like so:

protected static function newFactory()
{
    return Database\Factories\Administration\MyFactory::new();
}

Using The Factory

Once you have created the factory, you can call it to create a bunch of your models:

$numberOfModelsToCreate = 10;
$generatedModels = MyModel::factory()->count($numberOfModelsToCreate)->make();

Notice that we are calling the factory static method on the model, not calling the factory class itself. This is why we have the "Apply It To the Model" step above. The generated models will have been saved to the database. One does not need to manually save these models in order for them to persist.

This will generate the models with the default values specified by the factory.

If you wish to specify certain attribute values for the generated models, then provide them in an associative array like so:

$generatedModel = MyModel::factory()->make([
    'age' => 18,
]);

This will break as soon as you change the name of the column in the database. E.g. you're building fragile code, but at least it should only affect the dev/testing aspects of the code.

If you wish for your factory to create models with a varying set of values, then you can use a sequence like so.

$sequence = new Sequence(
    ['sex' => 'M'],
    ['sex' => 'F'],
);

$generatedModels = MyModel::factory()
    ->count(10)
    ->state($sequence)
    ->create();

Sequences act in a round-robin manner. E.g. it will create a male, then a female, then a male, etc until you get 5 of each sex for the 10 models in this example.

Alternatively, one could specify a closure for the sequence to use based on the index. More info about that in the official docs.

Using the Factory - Creating The Related Objects

In the previous section, we created one set of objects using the factory. It won't take long before you need to create a set of models, with the other models that relate to it. For example, one may wish to create a set of users, with each user having a set of blog posts that they created. If we have set up the has many relationship, and the factories for both models, then this is as easy as:

$user = User::factory()
    ->has(BlogPost::factory()->count(3))
    ->create();

Obviously, its a bit strange if all of our users have exactly 3 blog posts. It would be good if we can have a random count for the number of sub-objects, but I am not sure if that is possible at this point in time. I will update this post if I figure it out or if someone kindly provides a solution in the comments.

Additional Factory Features

This is just a cheatsheet to cover the basics. You may be interested in some of these topics though:

Database Queries

Where Clauses

The following queries all do the same thing:

User::where('email', $email)->exists();
User::where('email', '=', $email)->exists();

When you need to perform multiple where clauses:

$exists = 
    MyModel::where('user_id', '=', $userId)
    ->where('course_id', '=', $courseId)
    ->exists();

// My preferred way    
$whereClauses = [
    ['user_id', '=', $userId],
    ['course_id', '=', $courseId],
];

$exists = MyModel::where($whereClauses)->exists();

The above examples are all for AND clauses. If you need an OR relationship then you need to use orWhere. E.g.

$exists = 
    MyModel::where('user_id', '=', $userId)
    ->orWhere('course_id', '=', $courseId)
    ->exists();

Checking Uniqueness

Often you want to check if a record already exists before attempting an insert. For example, when adding a new user, you wish to check that a user with the same email doesn't already exist. For this, do the following:

if (User::where('email', '=', $email)->exists()) 
{
    throw new Exception("A user with that email already exists.");
}

Loading By Unique Value

If you have a unique attribute on your table and you want to load based on that, you can put something like this in your model:

/**
 * Fetch an account from the email address
 * @param string $email - the email of the account we wish to fetch.
 * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
 */
public static function loadFromEmail(string $email) : Account
{
    return Account::where('email', '=', $email)->firstOrFail(); 
}

The beauty of this is that you no longer need to use type hinting comments as you have a return type on your function.

Pluck

You can use ->pluck() to just get the values of one column. For example, the code below will get you a collection object of all the titles of posts where the category_id is 3.

$postTitles = DB::table("posts")
    ->where("category_id", 3)
    ->pluck("title");

The specific collection object is one of Illuminate\Support\Collection

Converting Collection To Array

Queries will return Laravel collections. However, you often want the array form instead. You can do this by executing the ->all() method on the collection. The example below will get an array of all the post titles.

$postTitles = DB::table("posts")
    ->where("category_id", 3)
    ->pluck("title")
    ->all();

Raw Select

Sometimes you need to do a complicated select query and it may be easier just to write it raw. Here is an example:

$rawSelectQuery = 
    "SELECT `id` FROM `myTable`" . 
    " WHERE `type_id`={$myTypeObject->id}" . 
    " AND `id` in (" . 
        "SELECT `some_id` FROM `table2` WHERE `someColumnName`={$someValue}" . 
    ")" . 
    " OR `id` in (SELECT `some_id` FROM `global_items`)";

$results = \DB::select($rawSelectQuery);

Inserting Data

This is one way you could insert multiple rows into a database at once.

DB::table('users')->insert([
    ['email' => 'taylor@example.com', 'votes' => 0],
    ['email' => 'dayle@example.com', 'votes' => 0]
]);

References

Last updated: 19th September 2021
First published: 18th September 2021