M. Niyazi Alpay
M. Niyazi Alpay
M. Niyazi Alpay

I've been interested in computer systems since a very young age, and I've been programming since 2005. I have knowledge in PHP, MySQL, Python, MongoDB, and Linux.

 

about.me/Cryptograph

  • admin@niyazi.org
Laravel and MongoDB Integration

Hello, due to my busy schedule, I haven't been able to write blog posts for a long time. In this article, I will explain how to use MongoDB in your Laravel project.

Laravel is installed by default with MySQL support. Additionally, in the config/database.php file, we can see support for PostgreSQL, SQLite, and SQL Server. It's better to choose the database type at the beginning of the project, considering factors such as project size, how often you want to access the data, and the ability to easily scale up the database size in the future if needed.

Since we anticipated that the data size of the project would increase in the future, we decided to choose a database that would further reduce the load on the database and, in case of problems, allow users to feel it as lightly as possible until a solution is found. Therefore, we opted for a database suitable for horizontal scaling.

After conducting some research, I found that MongoDB and Cassandra are among the databases that work well with Laravel, and MongoDB has an official Composer package. Searching on Google for "Laravel with NoSQL" brings MongoDB to the forefront, and MongoDB has its own Composer package. So, we decided to proceed with MongoDB for the project.

You can check out the MongoDB installation guide I explained here.

If the php-mongodb extension is not installed on the server (whether it's your local machine or a remote server), you need to install it. The installation process may vary depending on the PHP version or the server you are using. In cPanel, you can install it via EasyApache or Pecl installer, while in Plesk Panel, you can install it using Pecl for the PHP version you're using. I'll continue explaining the installation on an Ubuntu server.

apt install php-mongodb -y

Then, in the directory where our Laravel project is located, we use the following command in the terminal to install the necessary Composer package:

composer require mongodb/laravel-mongodb

After the installation is complete, we add the following configurations to the place where the connections are defined in the config/database.php file.

'mongodb' => [
       'driver' => 'mongodb',
        'dsn' => env('MONGODB_URI', 'mongodb+srv://username:password@server_address/database_name'),
        'database' => env('MONGODB_NAME'),
],

After these configurations, you can specify your database address in the MONGODB_URI definition in the .env file. If you haven't done any authentication, you can directly enter the server address. Also, you can write your database name in the MONGODB_NAME definition. Finally, in the .env file, change the DB_CONNECTION definition to mongodb.

Lastly, in the config/app.php file, add the following definition to the providers section.

MongoDB\Laravel\MongoDBServiceProvider::class,

We are creating a new model called Post.

php artisan make:model Post

Models created in Laravel are extended from the "Illuminate\Database\Eloquent\Model" class by default. After creating the model, we need to change this definition by editing our file. We now need to extend the models we created from this class MongoDB\Laravel\Eloquent\Model. So the new version of the created model should be as follows.

namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;
class Post extends Model
{
}

In Laravel, we create migrations to create database tables and seeders to add default data to tables. However, in MongoDB, there is no table structure; instead, each piece of data is referred to as a document, stored in collections, and maintained in JSON format. For those familiar with relational databases, the table structure corresponds to collections, rows to documents, and columns to fields in MongoDB.

When we invoke the model mentioned above to perform a create operation, if there is no collection named "post," it will be created, and data will be added to it. However, if we want to perform operations on a collection with a different name, we modify the previously used "protected $table = 'table_name';" declaration as follows:

protected $collection = 'blog_posts';

To clarify what I explained earlier: there is no collection named "post" in the database (unlike tables in SQL databases), and we want to add data to this collection. We can directly perform an insert operation as if this collection already exists. After this operation, the collection will be created. However, we can still run a migration. In this case, the migration will help us index the fields in the collection. However, in some cases, it may be necessary to manually create indexes in the database.

For example, suppose you have fields like category_id, attribute_id, and name. If you have data with the same attribute_id but different categories, you will need to perform the select operation with both category_id and attribute_id. Indexes created in the database are created separately. Therefore, when you perform a select operation, the indexes are checked based on the first condition in the query.

db.attribute_list.find({ $and: [ {category_id: 15}, {attribute_id: 25} ] })

Here we need to add hint to the query by defining both values in the same index.

db.attribute_list.createIndex({category_id:1, attribute_id:1})

index has been created, you can run getIndexes() to see the name of the index.

db.attribute_list.getIndexes()

We have a new index named category_id_1_attribute_id_1, now when we reorganize the previous query as follows, we will get faster results than before.

db.attribute_list.find({ $and: [ {category_id: 15}, {attribute_id: 25} ] }).hint('category_id_1_attribute_id_1')

Now, returning to the topic, no additional changes are needed on the Laravel side. You can continue performing tasks just as you would in the standard procedure. However, if you are storing sessions in the database, there's a minor issue with "1ff/laravel-mongodb-session". While installing this composer package would resolve the issue, I don't endorse using unofficial packages. In case you're using a distributed server structure, it's advisable to store sessions in a centralized location such as Memcache or Redis.

Lastly, if you've executed a like query in the database on the Laravel side and the collection size is substantial, we need to apply the hint definition I mentioned earlier to the query.

Standard query:

Post::where('title', 'LIKE', '%'.request('search').'%')->get();

Query with the hint definition:

 
Post::where('title', 'LIKE', '%'.request('search').'%')->hint('index_adı')->get();

 

User Registration and Login Procedures

I continue by assuming that registration and login operations are done through the model named User as standard.

Before the MongoDB migration, that is, in the standard, the structure of the User model is as follows

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\AuthUser as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
    protected $guarded = 'users';
}

 

As you can see, the User class is extended from Illuminate\FoundationAuth\User class. This class is also extended from Illuminate\Database\Eloquent\Model class. Therefore, we need to make a change here because the database connection cannot be provided correctly. We need to extend the User class from MongoDB\Laravel\Auth\User class. In this case, the new version of the class should be as follows.

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use MongoDB\Laravel\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
    protected $guarded = 'users';
}

User registration and login processes will be provided smoothly. In addition, a change is required in the authorization section with sanctum on the API side. If you don't have anything to do with the API, you don't need to make any changes, but if we think about the future stages of the project, the API will be required when something like a mobile application is needed. Sanctum uses \Laravel\Sanctum\PersonalAccessToken class and this class is extended from Illuminate\Database\Eloquent\Model class as usual. This class is defined in the PersonalAccessToken file under the vendor/laravel/sanctum/src/ directory, but we cannot edit this file, a composer package installation or update will restore it. We cannot edit this file, but we can make the necessary changes by making a copy of this file in the app/Models directory.

We copied PersonalAccessToken.php file under app/Models/ and changed the Model class it is extended to, just like we did with the other classes. The final version of the file is as follows:

<?php

namespace App\Models;


use Laravel\Sanctum\Contracts\HasAbilities;
use MongoDB\Laravel\Eloquent\Model;

class PersonalAccessToken extends Model implements HasAbilities
{
    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'abilities' => 'json',
        'last_used_at' => 'datetime',
        'expires_at' => 'datetime',
    ];

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'token',
        'abilities',
        'expires_at',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'token',
    ];

    /**
     * Get the tokenable model that the access token belongs to.
     *
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
     */
    public function tokenable()
    {
        return $this->morphTo('tokenable');
    }

    /**
     * Find the token instance matching the given token.
     *
     * @param  string  $token
     * @return static|null
     */
    public static function findToken($token)
    {
        if (strpos($token, '|') === false) {
            return static::where('token', hash('sha256', $token))->first();
        }

        [$id, $token] = explode('|', $token, 2);

        if ($instance = static::find($id)) {
            return hash_equals($instance->token, hash('sha256', $token)) ? $instance : null;
        }
    }

    /**
     * Determine if the token has a given ability.
     *
     * @param  string  $ability
     * @return bool
     */
    public function can($ability)
    {
        return in_array('*', $this->abilities) ||
            array_key_exists($ability, array_flip($this->abilities));
    }

    /**
     * Determine if the token is missing a given ability.
     *
     * @param  string  $ability
     * @return bool
     */
    public function cant($ability)
    {
        return ! $this->can($ability);
    }
}

We recreated the PersonalAccessToken class, but Laravel does not recognize this newly created class, so our API authorization is still not working correctly. We will define an alias for PersonalAccessToken in the boot() function in AppServiceProvider.

Final version of app/Providers/AppServiceProvider.php file:

<?php

namespace App\Providers;

use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {

        $loader = AliasLoader::getInstance();
        $loader->alias(\Laravel\Sanctum\PersonalAccessToken::class, \App\Models\PersonalAccessToken::class);
    }
}

The Laravel project is now fully operational with MongoDB.

Author

Cryptograph

You may also want to read these

There are none comment

Leave a comment

Your email address will not be published. Required fields are marked *