A Practical Approach to Renaming a Model in Laravel
Tags: laravel, php
Published: 2024-03-06 00:39:45

Code is messy, language is messy, and sometimes you want to rename a model in a codebase that a team has sunk years into...for consistency's sake.

Once a codebase hits a certain size or has multiple active participants, it becomes unrealistic to make sweeping changes like these. These types of sweeping changes inevitably run into problems. An all of nothing approach on this scale can lead to hefty merge conflicts, a massive PR that is hard to review and test, or a feature branch that starts diverging heavily from the main trunk and becomes hard to maintain.

A more manageable approach to a large change like this, is to break it into smaller pieces that can merge into the main trunk while keeping the project in a working state.

Doing a quick find and replace isn't enough in this case, as there are some tricky places our model's name might live.
- Database relationship columns may be referencing the model name, think  `{model}_id`, `user_id`, `team_id` etc.
    - Quick Tip: If you're using MySQL, you can find all tables referencing the model's name in your database using the following query. `SELECT table_name, column_name FROM information_schema.columns WHERE column_name LIKE '%model_name%;`
- Database indexes may be referencing the same fields.
- Polymorphic relationships in the database may be referencing the model name.
- The model may be serialized in the database or used in our queues.

In this example, we'll be renaming a Team model to Organization in a Laravel codebase.

To keep the project in a working state, while introducing the new name, we'll take advantage of a PHP built in function,  [class_alias](https://www.php.net/manual/en/function.class-alias.php). This will allow both the old model name and the new one to be used in parallel.
## Add an alias
We could use the `class_alias` function directly, in somewhere like the boot method of our AppServiceProvider, but Laravel provides a way to add aliases in the app config. So we'll stick to conventions here.

In `configs/app.php`, look for `aliases` and add an alias from the old model to the new model.
It should look something like this.

```php
'aliases' => Facade::defaultAliases()->merge([
	...
    'App\Models\Team' => 'App\Models\Organization',  
])->toArray(),
```

This is where Laravel is loading all of the facades. Under the hood, it is using the PHP function, class_alias.

## Update the model
Rename the Team model to Organization — both the file and class name.

We aren't writing any migrations today and to keep the code running, we need to override some of the Laravel auto table name and foreign key features. We want the model's table name to point to the existing table and relationships to use the existing columns in our database.

Add the table property to the model.
```php
protected $table = 'teams';
```

Override the default getForeignKey function in the model.
```php
public function getForeignKey()
{
	return 'team_id';
}
```

## Update morph maps
Morph maps are aliases used to reference a class in our database without using the actual class path in the database. This could mean using 'Team' instead of 'App\Models\Team'. You'll most commonly see these used for polymorphic relationships but they could be used for other things as well.

We need to allow the value that is used in the database to remain the same while mapping to our new model.

In the morph map, change from: `'Team' => Team::class` to: `'Team' => Organization::class`.
``` php
Relation::enforceMorphMap([
	'Team' => Organization::class`
])
```

Both `(new Team())->getMorphClass()` and `(new Organization())->getMorphClass()` should now return 'Team'.


##  Using the alias
After these steps, our app should be in a working state, only now, we can use both model names interchangeably. References to both Team and Organization will work.

From here we can chip away at updating our codebase and database to use the new name in bite sized bits. This lets us keep the codebase moving in the direction we want to go, without putting a stop to everything else.

The downside of course, is that we now have two ways to reference the same model, making things a little confusing in the interim. So don't sleep on the rest of the work.

Consistency...here we come.