As I prepare to take my terrifying leap into hackademia, I've been freelancing a lot more lately—both as a way to freshen up some of my more rusty skills (Directing leaves little room for slinging code or technical writing), and as a way to offset some of the lost income (teachers are criminally underpaid in this country, so please hire me).
In one of the projects I'm working on—a custom job board for a company in the energy and infrastructure industry—I recently had a need to add a "copy to" feature, allowing hiring managers to copy an application to another listing without modifying or removing the original record. It's not a straight copy, though. Most of the values remain unchanged, but the status of the new application itself needs to be reset, and the relationship to the new Job obviously changes as well.
"Good Enough" Isn't Always Good Enough
Now, this is hardly the most complicated thing in the world. Early in my career, I would have just grabbed each of the values I needed from the record, and re-created it from scratch right there.
As an example, let's say we needed to retrieve a job application by an $id
:
$application = Application::findOrFail($id);
The old-school way I would have solved this would be to create a new record, referencing each of the original $application
values, like so:
Application::create([
'name' => $application->name,
'email' => $application->email,
// ...
]);
You get the idea.
This gets the job done, but is challenging for a few reasons, the most important one is the fragility of the implementation.
What if we add new columns to the applications
table? Or we updated the implementation of the Application
model itself? This "brute-force" method means that we then have to both remember we implemented our copy function like this, and then update it to reflect reality.
It works, but it's not sustainable.
Don't Be Too Clever
Later on in my career, I probably would have grabbed an array of the record values and then removed the elements I didn't need with an unset()
or array_diff_key()
or some other method (there's no one right way to do things, and I don't feel like thinking too hard about how I would have handled this in the past) and created the new record from that. It would have probably looked something like this:
$values = array_diff_key($application->getAttributes(), [
'id' => true,
'job_id' => true,
'status' => true,
]);
This works, and is mostly Vanilla PHP, but is almost a little too clever. You have to know that any key set to true
in the second parameter of array_diff_key
gets removed from the original array. It's a nuance that requires documentation, or a comment, and lowers readability for more junior engineers.
A less clever (less clever is a good thing), yet more readable solution would instead probably look something like this:
$values = $application->getAttributes();
unset($values['id'], $values['job_id', $values['status']);
Again, it relies on more Vanilla PHP, but makes the implementation more clear. We're getting the attributes of the original application, and explicitly unsetting what we don't need.
Know Thy Toolbelt
For a long time, I've felt that it's important to embrace the underlying toolset of the framework you are working under, both for a "best practices" standpoint and an onboarding standpoint (you can argue all you want with your coworkers about the "right" way to do things, but basing your standards off of the ones encouraged by your tools gives you a higher foundation to start from.
In the case of Laravel, there is a built-in method for copying an Eloquent model, leaving you with a new instance that you can work with. It's called replicate()
, and it does the job of automatically excluding the primary key, lets you define what other attributes you'd like to not copy over, and creates a new model instance that you can interact with (instead of having to create one yourself).
See below:
$application->replicate(['job_id', 'status'])
->fill([
'job_id' => $job_id
])
->save();
As you can see, the solution is readable (we are literally replicating the $application
object without the job_id
and status
attributes, filling in a new job_id
, and saving the record). It's a one-liner without all of the maintainability concerns that one-liners often come with.
Pilot to Copilot
One of the biggest challenges of getting spun up on a new language or framework is those sticky "unknown unknowns." You can be a great developer with many years of experience, but that doesn't mean you natively understand the dialect of a technology you've never used before.
In the past, research and mentorship were often the only way to approach this. The best way to know is to make mistakes and be told how to improve.
But, thanks to modern tools like GitHub Copilot and ChatGPT, you can ask the questions you want to ask and let that kick off your research, rather than blindly groping around the documentation or hoping someone asked a question on a forum one day that solves your problem.
A simple Is this the best way accomplish this?
goes a long way.
For myself, replicate()
wasn't something I knew or had needed to use in the past. It was a suggestion by Github Copilot Chat because I knew how to accomplish the task, but I needed to riff off of someone to make sure I was doing it in a way that felt "good" to me.