DEV Community

Cover image for How I Accidentally Found Myself Rewriting Laravel (Just to Fix Laravel Modules 😅)
HichemTech
HichemTech

Posted on

1

How I Accidentally Found Myself Rewriting Laravel (Just to Fix Laravel Modules 😅)

Alright, buckle up folks, because this is the tragic tale of how I thought I was fixing Laravel Modules… only to realize I'd have to rewrite Laravel itself. 🙃

Did I succeed? Nope.

Did I at least break something? Oh, absolutely.

Did I still release a package out of it? You bet. 😎

The Beginning: A New Project 🌟

So here I am, about to start a new project at my company—kind of a big one. And since I’m the most experienced with Laravel (not the boss dev lol, just the Laravel wizard 🧙‍♂️), I had the honor of setting up the project structure.

Now, you might ask, "Why Laravel if the team isn't fully experienced?" Well, actually, they know it—but let's say I'm a bit ahead of them. Plus, honestly, Laravel is just amazing for quick, flexible, easy development. (If you're still thinking, "eeeh PHP nvm," no—get your ass on the computer, and run:

composer create-project laravel/laravel
Enter fullscreen mode Exit fullscreen mode

Try it. Try ittttt. You'll thank me later. 😏

Anyway, back to the story. So I start preparing our setup and, of course, Laravel Modules comes to mind. Perfect for modular architecture, right?

Only... I hadn’t used it in a while. And I totally forgot about the artisan issue. 😭

The Artisan Problem: Oh No, Here We Go Again

First things first: I open my console and run:

laravelfs new alpha
Enter fullscreen mode Exit fullscreen mode

(Alpha is the fake project name; I can’t share the real one, so let’s just pretend it’s some next-gen AI startup or whatever.)

(Oh, and by the way, laravelfs is my own Laravel installer (LaravelFS)! 🚀 I open-sourced it because Laravel 12 removed Breeze, and I was like, *"Oh hell no!" So I made my own installer to fix that and add some cool features. You should check it out! [LaravelFS]) and the story behind :

I picked "none starter," meaning just pure Blade. Why not Inertia? Well, for this scale, Blade feels safer. No big risks, just flexibility.

So, with the project ready, I opened Google and searched "Laravel Modules" (yep, I forgot the vendor name, haha). Nice! The website updated since last time I used it (it was v6, now v12—mostly compatibility changes, but still cool). I quickly typed the usual:

composer require nwidart/laravel-modules
composer dump-autoload
Enter fullscreen mode Exit fullscreen mode

Everything’s great! 🎉

Time to create our first module:

php artisan module:make IOT
Enter fullscreen mode Exit fullscreen mode

Boom, module created! So easy, right? Next step: creating a model called Device. Without thinking, I went:

php artisan make:model Device
Enter fullscreen mode Exit fullscreen mode

❌ ERROR? No. But... wrong path.

Instead of going into Modules/IOT/Models/Device.php, it gets created in App\Models\Device.php.

Wait... hold on, oh damn! 🤦‍♂️ It hit me immediately. This command places the model in \App\Models, not the module I created. How did I forget this?! Right, Artisan doesn’t care about Laravel Modules.*

The Annoyance: Long Commands & No Third-Party Support

The right command was:

php artisan module:make-model IOT Device
Enter fullscreen mode Exit fullscreen mode

So yeah, instead of just using Laravel’s built-in make:model, I now have to specify the module name every single time.

Which is already annoying, but wait, there’s more!

👉 Not all Artisan commands have a "module" version.

👉 Third-party package commands won’t work inside modules.

👉 Spatie packages? Say goodbye to easy module integration.

At this moment, my genius brain (remember that ts-runtime-picker story? Yeah, genius mode activated again 😅) woke up and said, "Hichem, why can't you fix this? No one ever thought of it before, right?"

(spoiler: no, you won’t, Hichem. 😂).

(Hichem is my name btw, no shit sharlok :D).

The Overconfident Attempt: Let’s Hack Artisan! 😎

So here's the deal: the real issue is Artisan commands run only from Laravel's root, So I had this brilliant idea:

"What if I make Artisan work everywhere, dynamically detecting the module?"

💡 Genius, right?! (Totally never been done before, right? Haha. Ha.)

I didn’t even test the theory—I just dove straight in and started coding.

🚀 Introducing… Artisan Everywhere! 🚀 (Yes, another package. Because why not? 😂)

Opened my console, typed composer init and created the pacakge (hichemtab-tech/artisan-everywhere—epic naming skills, I know 😎).

Opened a new PhpStorm window, created a bin folder, and inside it, an artisan.php. At first, I tried to make a batch file (don't ask why, haha), but PHP made more sense for Composer.

What my script did was genius:

  • It caught any "artisan" command you typed.
  • Then, it searched upward through your directories, found the root artisan file, and executed commands from anywhere inside the project—no more annoying cd ../../../... Genius, right?

Here’s the script I wrote:

#!/usr/bin/env php
<?php

/**
 * Global Artisan Wrapper - Artisan Everywhere
 *
 * This script searches upward from the current working directory
 * for a local "artisan" file. When found, it delegates all command-line
 * arguments to that artisan file.
 */

// Get the current working directory.
$cwd = getcwd();
$foundArtisan = false;
$artisanPath = '';

// Traverse up the directory hierarchy looking for a local artisan file.
while (true) {
    $possibleArtisan = $cwd . DIRECTORY_SEPARATOR . 'artisan';
    if (file_exists($possibleArtisan) && is_file($possibleArtisan)) {
        $foundArtisan = true;
        $artisanPath = $possibleArtisan;
        break;
    }

    // If we've reached the root directory, stop.
    $parent = dirname($cwd);
    if ($parent === $cwd) {
        break;
    }
    $cwd = $parent;
}

if (!$foundArtisan) {
    fwrite(STDERR, "No local artisan file found in the directory hierarchy.\n");
    exit(1);
}

// Capture command-line arguments (excluding the script name).
$arguments = array_slice($argv, 1);

// Escape each argument to safely pass them to the shell.
$escapedArguments = array_map('escapeshellarg', $arguments);
$argumentsString = implode(' ', $escapedArguments);

// Prepare the command.
// If the artisan file is executable, run it directly; otherwise, use PHP.
if (is_executable($artisanPath)) {
    $command = escapeshellcmd($artisanPath) . ' ' . $argumentsString;
} else {
    $command = 'php ' . escapeshellarg($artisanPath) . ' ' . $argumentsString;
}

// Optionally, you can enable a debug output to see the final command.
// fwrite(STDERR, "Executing command: $command\n");

// Execute the command, relaying its output and exit status.
passthru($command, $returnVar);
exit($returnVar);
Enter fullscreen mode Exit fullscreen mode

🥳 🎉 IT WORKED!

Now I could run Artisan commands from anywhere in my project. No more cd ../../../ nonsense!

The “Oh No” Moment: Laravel’s Internal Paths :))))

Super hyped, I went to my project and ran:

cd Modules/IOT
artisan make:model Device
Enter fullscreen mode Exit fullscreen mode

And...

INFO  Model [C:\xampp\htdocs\alpha\app\Models\Device.php] created successfully.
Enter fullscreen mode Exit fullscreen mode

Wait...

WHAT?!! 😳

Why did it go into app/Models/ instead of Modules/IOT/Models/?!

And then it hit me.

💀 Laravel doesn’t let you change the base path for Artisan commands. 💀

Turns out, when you run make:model, Laravel internally hardcodes the app/ path. It does not dynamically detect modules.

Here’s the problem code inside Illuminate\Console\GeneratorCommand:

protected function getPath($name)
{
    $name = Str::replaceFirst($this->rootNamespace(), '', $name);
    return $this->laravel['path'].'/'.str_replace('\\', '/', $name).'.php';
}
Enter fullscreen mode Exit fullscreen mode

So even though I could now run artisan commands from anywhere…

🥲 Artisan would still default to app/Models/.

The Conclusion: I Gave Up. 😂

I had two choices:

  1. Rewrite a ton of Laravel’s internal command logic.
  2. Accept defeat and keep using php artisan module:make-model.

😂 I took Option 2.

No way Taylor Otwell is accepting a PR that modifies the entire Artisan command structure.

So yeah, I failed. BUT! At least I made Artisan Everywhere, which is still useful. 🎉

And hey, if anyone actually solves this issue, please let me know. I’d love to see it work. But for now… I’ll just keep manually moving files. 😭


That’s it. My failed attempt at rewriting Laravel. Hope you enjoyed my suffering. 😂

Postmark Image

20% off for developers who'd rather build features than debug email

Stop wrestling with email delivery and get back to the code you love. Postmark handles the complexities of email infrastructure so you can ship your product faster.

Start free

Top comments (1)

Collapse
 
xwero profile image
david duymelinck

It was a very entertaining read!

I' m also in the situation I'm not liking the file structure that comes out of the box with frameworks. And like you I'm thinking about a way to make it possible make file creation possible however the directories are structured.

I'm taking the long way around by building a command that is framework agnostic. So it will work with any directory structure you can throw at it.
For the future I'm thinking about a templates feature, that will be more like the artisan make commands.
But for now I want to have a good base to build upon.

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more