Add Missing Migrations to Laravel’s Database Without executing

Learn how to add missing Laravel migration entries to the database without re-running them. Perfect for restoring synced environments or imported databases.

Sometimes, when working with Laravel projects, especially when migrating databases manually or syncing environments, the migrations table might miss entries — even though the migration files exist and were executed. This causes Laravel to attempt to re-run migrations or show them as pending. And even sometime, you added new migration and try to migrate them, but migration gives error that previous migrations are pending.

This article will guide you through creating an Artisan command that adds missing migration entries to the database without running them, keeping Laravel in sync with the actual DB schema.

Step 1: Create the Command

To create an artisan command, run the following command in your terminal:

php artisan make:command SyncMigrations

It creates a file at app/Console/Commands/SyncMigrations.php.

Step 2: Add the Logic

Now, open this file and paste the following code inside it:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;

class SyncMigrations extends Command
{
    protected $signature = 'sync:migrations';
    protected $description = 'Add missing migrations to the migrations table without running them';

    public function handle()
    {
        $migrationPath = database_path('migrations');
        $files = File::files($migrationPath);

        $fileMigrations = collect($files)->map(function ($file) {
            return pathinfo($file->getFilename(), PATHINFO_FILENAME);
        });

        $existingMigrations = DB::table('migrations')->pluck('migration');

        $missingMigrations = $fileMigrations->diff($existingMigrations);

        if ($missingMigrations->isEmpty()) {
            $this->info('✅ All migrations are already recorded.');
            return 0;
        }

        $lastBatch = DB::table('migrations')->max('batch') ?? 0;
        $nextBatch = $lastBatch + 1;

        foreach ($missingMigrations as $migration) {
            DB::table('migrations')->insert([
                'migration' => $migration,
                'batch' => $nextBatch,
            ]);
            $this->line("✔️  Added migration: <info>$migration</info>");
        }

        $this->info("🎉 Added " . $missingMigrations->count() . " missing migration(s) to batch $nextBatch.");
        return 0;
    }
}

Explanation:

  • Get all filenames list from the migrations folder.
  • Get all migrations list from the migrations table.
  • Check a difference between two lists and return 0 if no difference found.
  • If there is a difference, get a last migration batch and create new batch by adding 1 to this batch number.
  • Add all migrations available in the difference with new batch number.

Step 3: Run the Command

If you are running Laravel < 11, register the command file in app/Console/Kernel.php:

protected $commands = [
    \App\Console\Commands\SyncMigrations::class,
];

In Laravel 11+, there’s no need to register commands manually.

Now, run the following command in your terminal:

php artisan sync:migrations

You’ll see output like this:

✔️  Added migration: 2024_12_31_235959_create_users_table
✔️  Added migration: 2025_01_01_000000_create_orders_table
🎉 Added 2 missing migration(s) to batch 4.

Caution

This command does not execute the migrations. It assumes the database schema already reflects them. Use this only when you’re sure the migrations were already applied, e.g. from another environment or a database import.

Conclusion

This approach is a safe and Laravel-friendly way to fix out-of-sync migrations. It’s perfect for developers working across multiple environments or restoring production databases.

Want to take it further? Add a prompt or backup feature to this command. Let me know in the comments!

How to Integrate Sentry with CodeIgniter 3

Learn how to integrate Sentry error tracking with CodeIgniter 3 to monitor and debug PHP application issues in real-time.

Sentry is a powerful error-tracking tool that helps you monitor and fix crashes in real-time. In this post, we’ll walk through how to integrate Sentry with a CodeIgniter 3 application and log all errors to sentry.

Prerequisites

Before you begin, make sure you have:

  • A Sentry account with a created project.
  • A working CodeIgniter 3 setup.
  • PHP 7.2+ (recommended).
  • Composer installed.

Step 1: Install Sentry SDK via Composer

Open your terminal and run:

composer require sentry/sentry

If you haven’t initialized Composer yet in your CI3 project, run composer init first.

Step 2: Enable and Configure Hooks

To enable hook in CodeIgniter 3, Edit your application/config/config.php and update the 'enable_hooks' variable to TRUE if it is FALSE.

$config['enable_hooks'] = TRUE;

Now, register the sentry hook into the application/config/hooks.php file:

$hook['pre_system'][] = array(
    'class'    => '',
    'function' => 'init_sentry', // Function to be called
    'filename' => 'sentry.php', // Filename of the hook
    'filepath' => 'hooks'
);

As mentioned in the hook file, create application/hooks/sentry.php and add the following code:

use Sentry\ClientBuilder;
use Sentry\State\Hub;

function init_sentry()
{
    require_once APPPATH . '../vendor/autoload.php';

    \Sentry\init([
        'dsn' => 'https://your-dsn@sentry.io/project-id',
        'environment' => ENVIRONMENT,
        'error_types' => E_ALL & ~E_NOTICE, // Adjust as needed
    ]);
}

Replace 'https://your-dsn@sentry.io/project-id' with your actual Sentry DSN.

Step 3: Capture Errors or Messages

Now, you can log errors to sentry using multiple ways as follows,

Manually Capture Exceptions

try {
    // Your code here
} catch (Exception $e) {
    \Sentry\captureException($e);
}

Manually Capture Messages

\Sentry\captureMessage('Something happened!', \Sentry\Severity::warning());

Step 4: Automatically Capture Uncaught Exceptions

You can log all uncaught exceptions by extending the CI Exception class.

Create a new exception file at application/core/MY_Exceptions.php and add the following content in it:

class MY_Exceptions extends CI_Exceptions
{
    public function show_exception($exception)
    {
        if (class_exists('\Sentry\State\Hub')) {
            \Sentry\captureException($exception);
        }

        return parent::show_exception($exception);
    }
}

This will overwrite the codeigniter exception to log errors in sentry.

Step 5: Test the Integration

To test the integration, add the following code to generate the fake exception:

throw new Exception("Testing Sentry in CI3");

You should see this exception appear in your Sentry dashboard almost immediately.

Pro Tips

  • Use .env files or config variables to store your DSN securely.
  • Configure environments like development, production, staging in the environment key of the config.
  • You can even capture user context (like logged-in user ID or email) with Sentry.

Conclusion

With this setup, your CodeIgniter 3 project is now integrated with Sentry for powerful real-time error tracking. From catching uncaught exceptions to manually logging messages, Sentry gives you the tools you need to debug faster and ship more reliably.

Have questions or need help capturing user context? Drop a comment below!

Implementing JWT Authentication in CodeIgniter 3

Learn how to implement secure JWT authentication in CodeIgniter 3. Step-by-step guide for token generation, validation, and integration.

Securing your mobile API is critical in modern applications. In this guide, we’ll walk through how to implement JWT (JSON Web Token) based authentication in CodeIgniter 3, including access token and refresh token support for long-lived sessions.

Overview of JWT Auth Flow

Here’s the standard flow:

  1. User logs in → server returns an access token and a refresh token.
  2. Mobile app uses the access token in the Authorization header for every request.
  3. When access token expires, the app sends the refresh token to get a new access token.

Prerequisites

  • CodeIgniter 3 installed
  • firebase/php-jwt JWT library via Composer
  • users table for authentication and user_tokens table for refresh tokens

Step 1: Install JWT Library

Use composer to install JWT library as follows:

composer require firebase/php-jwt

Step 2: Create JWT Helper Class

Create a JWT helper class file at application/libraries/Authorization_Token.php and add the following code to it:

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class Authorization_Token {
    private $CI;
    private $token_key;

    public function __construct() {
        $this->CI =& get_instance();
        $this->token_key = 'YOUR_SECRET_KEY';
    }

    public function generateToken($user_data) {
        $issuedAt = time();
        $expirationTime = $issuedAt + 3600; // 1 hour
        $payload = [
            'iat' => $issuedAt,
            'exp' => $expirationTime,
            'data' => $user_data
        ];
        return JWT::encode($payload, $this->token_key, 'HS256');
    }

    public function validateToken() {
        $headers = apache_request_headers();
        if (!isset($headers['Authorization'])) return false;
        
        $token = str_replace('Bearer ', '', $headers['Authorization']);
        try {
            $decoded = JWT::decode($token, new Key($this->token_key, 'HS256'));
            return (array) $decoded->data;
        } catch (Exception $e) {
            return false;
        }
    }
}

Step 3: Create Login API

Create a user_tokens table for storing refresh tokens.

CREATE TABLE user_tokens (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    refresh_token VARCHAR(255) NOT NULL,
    expires_at DATETIME NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Create a login API file anywhere inside app/controllers folder and add the following content inside it.

class Auth extends CI_Controller {
    public function login_post()
    {
        $email = $this->post('email');
        $password = $this->post('password');

        $user = $this->db->get_where('users', ['email' => $email])->row();

        if (!$user || !password_verify($password, $user->password)) {
            return $this->response(['status' => false, 'message' => 'Invalid credentials'], 401);
        }

        $this->load->library('Authorization_Token', null, 'authToken');
        $access_token = $this->authToken->generateToken(['id' => $user->id, 'email' => $user->email]);

        $refresh_token = bin2hex(random_bytes(64));
        $this->db->insert('user_tokens', [
            'user_id' => $user->id,
            'refresh_token' => $refresh_token,
            'expires_at' => date('Y-m-d H:i:s', strtotime('+30 days'))
        ]);

        return $this->response([
            'status' => true,
            'access_token' => $access_token,
            'refresh_token' => $refresh_token,
        ], 200);
    }
}

Step 4: Protect API Routes

Create a base controller file BaseApi_Controller.php inside app/controllers folder. Add the following code to base controller file.

class BaseApi_Controller extends REST_Controller
{
    public $user_data;

    public function __construct()
    {
        parent::__construct();
        $this->load->library('Authorization_Token', null, 'authToken');
        $user_data = $this->authToken->validateToken();

        if (!$user_data) {
            $this->response([
                'status' => false,
                'message' => 'Access token expired',
                'token_expired' => true
            ], 401);
            exit;
        }

        $this->user_data = $user_data;
    }
}

This file handles token validations for all requests. But, it will not automatically intercept all requests. So, all your secure API files need to extend this BaseApi_Controller.

class Orders extends Authenticated_Controller {
    public function list_get() {
        $user_id = $this->user_data['id'];
        $orders = $this->db->get_where('orders', ['user_id' => $user_id])->result();

        $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($orders));
    }
}

Step 5: Token Refresh

Create new api file AuthController.php for refresh token and add the following code in it.

class Auth extends CI_Controller {
    public function refresh_token_post()
    {
        $refresh_token = $this->post('refresh_token');

        $token_data = $this->db->get_where('user_tokens', [
            'refresh_token' => $refresh_token
        ])->row();

        if (!$token_data || strtotime($token_data->expires_at) < time()) {
            return $this->response([
                'status' => false,
                'message' => 'Invalid or expired refresh token'
            ], REST_Controller::HTTP_UNAUTHORIZED);
        }

        // Generate new access token
        $this->load->library('Authorization_Token', null, 'authToken');
        $access_token = $this->authToken->generateToken([
            'id' => $token_data->user_id,
            'email' => 'user@email.com' // Fetch if needed
        ]);

        return $this->response([
            'status' => true,
            'access_token' => $access_token,
            'expires_in' => 900
        ], REST_Controller::HTTP_OK);
    }
}

Summary

  • JWT access tokens: short-lived (e.g., 15 minutes)
  • Refresh tokens: long-lived (e.g., 30 days), stored securely
  • On access token expiry: client uses refresh token to get a new one
  • REST_Controller is used to simplify JSON responses in CodeIgniter 3

Final Thoughts

Implementing access and refresh tokens properly ensures secure and scalable mobile API sessions. Using CodeIgniter 3 with JWT and refresh tokens gives you full control over session lifecycle, security, and logout behavior.

Develop Contact Form using Livewire in Laravel

Learn how to create a responsive contact form using Livewire in Laravel. Step-by-step tutorial with validation, form submission, and real-time updates.

Developing a Contact form using Livewire in Laravel is straightforward and involves a very few key steps.

In this post, we will learn to develop contact form using Livewire component in Laravel. Follow the below steps:

Step 1: Install Livewire

To create any Livewire form, you must have Livewire installed over Laravel project. If you haven’t installed Livewire yet, you can do so via Composer:

composer require livewire/livewire

It will install Livewire package to you Laravel project.

Step 2: Create a Livewire Component

Now, we need to create a Livewire component for contact form. You can generate a Livewire component using Artisan command as follows:

php artisan make:livewire ContactForm

This will create 2 files as follows:

  • A Livewire component file at app/Http/Livewire/ContactForm.php
  • A Blade view at resources/views/livewire/contact-form.blade.php

Your contact form component is now ready to be used. But, we haven’t added any logic or any design to this component. So, if you add this component to any file, it will display blank page.

Step 3: Define the Livewire Component Logic

Open app/Http/Livewire/ContactForm.php and add all contact form related logic to the file as follows:

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Contact;
use Illuminate\Validation\Rule;

class ContactForm extends Component
{
    public $name, $email, $message;

    protected $rules = [
        'name' => 'required|min:3',
        'email' => 'required|email',
        'message' => 'required|min:5',
    ];

    public function submit()
    {
        $this->validate();

        // Save data
        Contact::create([
            'name' => $this->name,
            'email' => $this->email,
            'message' => $this->message,
        ]);

        // Reset the form
        $this->reset(['name', 'email', 'message']);

        session()->flash('success', 'Your message has been sent!');
    }

    public function render()
    {
        return view('livewire.contact-form');
    }
}

Step 4: Create the Livewire Blade View

Open resources/views/livewire/contact-form.blade.php and update the HTML form as per your requirement. I have added my blade file design as follows:

<div>
    @if (session()->has('success'))
        <div class="p-3 mb-4 text-green-600 bg-green-200 border border-green-600 rounded">
            {{ session('success') }}
        </div>
    @endif

    <form wire:submit.prevent="submit">
        <div class="mb-4">
            <label for="name" class="block font-bold">Name:</label>
            <input type="text" id="name" wire:model="name" class="w-full p-2 border rounded">
            @error('name') <span class="text-red-600">{{ $message }}</span> @enderror
        </div>

        <div class="mb-4">
            <label for="email" class="block font-bold">Email:</label>
            <input type="email" id="email" wire:model="email" class="w-full p-2 border rounded">
            @error('email') <span class="text-red-600">{{ $message }}</span> @enderror
        </div>

        <div class="mb-4">
            <label for="message" class="block font-bold">Message:</label>
            <textarea id="message" wire:model="message" class="w-full p-2 border rounded"></textarea>
            @error('message') <span class="text-red-600">{{ $message }}</span> @enderror
        </div>

        <button type="submit" class="px-4 py-2 text-white bg-blue-600 rounded">Send</button>
    </form>
</div>

I have added some additional features like displaying success message and added error message display elements to each fields.

Step 5: Add Livewire to a Page

Now it is ready to be included to any page. You can include this component using @livewire directive provided by Livewire package. Include this Livewire component in your Blade view (e.g., resources/views/contact.blade.php):

@extends('layouts.app')

@section('content')
    <div class="container mx-auto p-4">
        <h1 class="text-xl font-bold">Contact Us</h1>
        @livewire('contact-form')
    </div>
@endsection

Still it is not visible to the page. Because, Livewire scripts and styles are missing.

Step 6: Include Livewire Scripts

You have to add Livewire scripts and styles to the layout, where you want to display any Livewire Component. You can add Livewire scripts and styles to the layout using their directives @livewireScripts and @livewireStyles respectively.

Add Livewire scripts in your layouts/app.blade.php file before the closing </body> tag of the layout file:

@livewireScripts
</body>
</html>

And add the Livewire styles in <head> tag of the layout file:

@livewireStyles

Note: If you have multiple layouts and these layouts are used to display Livewire Compoment pages, then you have to add Livewire scripts and styles in all of these layouts.

Step 7: Run Migrations and Serve the App

In component file, we have added a code to save contact form details to the database. So, If you don’t have contacts table already, create a migration for the contacts table:

php artisan make:migration create_contacts_table

Open the migration file (database/migrations/xxxx_xx_xx_xxxxxx_create_contacts_table.php) and add the necessary fields as follows:

public function up()
{
    Schema::create('contacts', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email');
        $table->text('message');
        $table->timestamps();
    });
}

public function down(): void
{
    Schema::dropIfExists('contacts');
}

Run the migration:

php artisan migrate

It will create the contacts table into the project database. Now, create a model for this migration:

php artisan make:model Contact

It will create contact model file app/models/Contact.php. Open this model file and update it as follows,

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Contact extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'email',
        'message',
    ];
}

Finally, start your Laravel development server:

php artisan serve

Now, visit http://127.0.0.1:8000/contact, and you should see your Livewire-powered Contact Form working dynamically!

Additional Features You Can Add

  • Real-time validation: Add wire:model.blur="name" to inputs.
  • Loading indicators: Use wire:loading to show a spinner while submitting.

Install the Laravel Filament Panel Builder

Learn to install and set up Laravel Filament, a tool for creating customizable admin panels and CRUD applications. This guide covers requirements, installation steps, and user creation. Follow this concise tutorial to get started with Filament in your Laravel project quickly and efficiently.

Laravel Filament is a powerful tool designed to create Admin panels and manage content in Laravel applications. It provides a highly customizable and developer-friendly interface for creating CRUD applications, dashboards, and various business-related applications. Filament is known for its flexibility and ease of use, allowing developers to scaffold forms, tables and pages quickly without writing a lot of boilerplate code.

This article describes the installation process filament panel over laravel with most of the possible configations and steps.

Requirements

Install and configure the following components, before running any filament command.

  • PHP v8.1+
  • Laravel v10.0+
  • Livewire v3.0+ (Filament composer command will install this package automatically. So, no need to install this package separately.)

Install Laravel Filament Panel

To install the filament panel over laravel, run the following command at the root of the project folder,

composer require filament/filament:"^3.2" -W

This command will install the base package of filament. This will also install livewire package, which is in the requirements.

php artisan filament:install --panels

This command will install the filament panel after some information required to install the panels. It will ask the following questions,

What is the ID?

On the basis of this ID, it will create the panel provide for the filament panel and also register this panel provider.

For Example:
If ID is admin, it will create the panel provide to he following location, app/Providers/Filament/AdminPanelProvider.php

If you encounter an error when accessing your panel, ensure that the service provider is registered in bootstrap/providers.php (for Laravel 11 and above) or config/app.php (for Laravel 10 and below). If it isn’t, you’ll need to add it manually.

Create a User

Next step is creating a user to access this panel. But, before running the create user command, check the following laravel configuration and update the configuration as per the requirements,

  • Add Database credentials to .env file.
  • Run the following command to run the migration. It will create users table into the database.
    php artisan migrate

Run the following command to create a user after checking above requirements,

php artisan make:filament-user

It will ask some basic questions like name, email, password, etc. for creating a user.

After creating a user, run php artisan serve, open http://127.0.0.1:8000/admin in your web browser, sign in using the created user credentials, and start building your app!