Skip to content

Conversation

@Log1x
Copy link
Member

@Log1x Log1x commented Feb 6, 2025

What started off as a weekend hobby project has grown significantly in features and had become a slight burden to maintain and contribute to due to minimal modularization and excessive logic residing in a single Laracord.php god class.

Today we right those wrongs. Laracord v3 is a near complete refactor of the architecture of the framework with (hopefully) minimal breaking changes. It boasts plugin support, a full custom console implementation utilizing proper ReactPHP streams (props to @QWp6t), proper logging support, improved error rendering/handling, command/interaction middleware using pipelines, full DI support in component handlers, improved bot configuration, basic sharding support, proper command cooldown support, and more.

Prior to v3, the Laracord skeleton shipped with a Bot.php that extended the parent Laracord class allowing you to override methods. This has been removed and all configuration is now done within' the BotServiceProvider. You can still technically override the Laracord class, but you'd need to do it within the container and a service provider and I'd generally recommend against it.

This PR closes #131 #121 #119 #71 #67 #65 #64 #18 #124 #129 #132 #53 so far.

❤️ If you're able, please consider supporting my development on Laracord: https://github.com/sponsors/log1x

Try It Out

$ composer create-project laracord/laracord:dev-next

Configuration

<?php

namespace App\Providers;

use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Routing\Router;
use Laracord\Bot\Hook;
use Laracord\Laracord;
use Laracord\LaracordServiceProvider;
use Example\ExamplePlugin;

class BotServiceProvider extends LaracordServiceProvider
{
    /**
     * Configure the bot instance.
     */
    public function bot(Laracord $bot): Laracord
    {
        return $bot
            ->plugins([
                new ExamplePlugin,
            ])
            ->registerHook(Hook::AFTER_BOOT, function (Laracord $bot) {
                $bot->logger->info('Laracord is ready!');
            })
            ->registerCommands([
                //
            ])
            ->registerSlashCommands([
                //
            ])
            ->registerContextMenus([
                //
            ])
            ->registerEvents([
                //
            ])
            ->registerTasks([
                //
            ])
            ->registerCommandMiddlewares([
                //
            ])
            ->registerPrompts([
                //
            ])
            ->withRoutes(function (Router $router) {
                //
            })
            ->withMiddleware(function (Middleware $middleware) {
                //
            });
    }
}

Plugins

I find the introduction of plugins exciting as it gives Laracord the possibility of having its own little ecosystem. Plugin's have the same capabilities as you would in the bot provider but using the new Plugin interface:

<?php

namespace Example\ExamplePlugin;

use Laracord\Contracts\Plugin;
use Laracord\Laracord;

class ExamplePlugin implements Plugin
{
    /**
     * Register the plugin with the bot.
     */
    public function plugin(Laracord $bot): Laracord
    {
        return $bot
            ->registerCommands([
                //
            ])
            ...
            ->registerSlashCommands([
                //
            ]);
    }
}

You would then register the plugin in your BotServiceProvider.php.

Components are also able to be discovered via directory/namespace with Laracord registering the following defaults:

$bot
    ->discoverCommands(in: app_path('Commands'), for: 'App\\Commands')
    ->discoverSlashCommands(in: app_path('SlashCommands'), for: 'App\\SlashCommands')
    ->discoverContextMenus(in: app_path('Menus'), for: 'App\\Menus')
    ->discoverEvents(in: app_path('Events'), for: 'App\\Events')
    ->discoverTasks(in: app_path('Tasks'), for: 'App\\Tasks');

The above component discovery (and a lot of other changes) are heavily inspired by the Filament team. Huge shoutout to them for easing some of my pain in figuring out where I wanted to go with the structure of the project.

Console

Already existing in Laracord were (undocumented) built-in commands you could execute in the bot's console while it was running such as status. For v3, this will now be a documented and you can now make your own bot console commands called Prompts using laracord make:prompt and registering them in your BotServiceProvider.

Note

Sorry Windows users, you will have to use WSL.

<?php

namespace App\Console\Prompts;

use Laracord\Console\Console;
use Laracord\Console\Prompts\Prompt;
use Laracord\Laracord;

class ExamplePrompt extends Prompt
{
    /**
     * The name of the prompt.
     *
     * @var string
     */
    protected $name = 'example';

    /**
     * Handle the prompt.
     */
    public function handle(Console $console, Laracord $bot): void
    {
        $console->info('Hello world!');
    }
}

Logging

With the new console implementation, I was able to implement proper logging support rendering logs as you'd expect them in both the console as well as the configured Laravel logger (defaults to laracord.log).

With this change, you will likely want to move any console logging to the logger() instead of console():

- $this->console()->log('Hello world');
+ $this->logger()->info('Hello world');

When rendering in the console, logging is now a little more colorful with it properly distinguishing debug and other logs with a unique color:

Screenshot

Error Handling

With the above, I was able to get rid of hack-y solutions such as Laracord::handleSafe() and now depend on Laravel's native rescue() helper when handling exceptions throughout the framework. This will then properly pass the exception through a try/catch and to our logger which now renders in console. This is great because we can now take advantage of Collision for error rendering and promise rejection while inside the loop while also logging the stack trace to the configured logger:

Screenshot 2

Command Middleware

Commands can now have individual or global middleware. This allows for implementations such as global rate limiting, etc. without overriding parent classes or resorting to less than desirable methods.

Take a simple example where if the command is ping– we short-circuit it's handler and give a different message instead using the new Message facade:

<?php

namespace App\Commands\Middleware;

use Closure;
use Laracord\Commands\Middleware\Context;
use Laracord\Commands\Middleware\Middleware;
use Laracord\Discord\Facades\Message;

class ExampleMiddleware implements Middleware
{
    /**
     * Handle the command.
     *
     * @return mixed
     */
    public function handle(Context $context, Closure $next)
    {
        if ($context->isCommand() && $context->command->getName() === 'ping') {
            Message::content('Not this time!')->reply($context->source);

            return;
        }

        return $next($context);
    }
}

Basic Sharding Support

I'm talkin' real basic here. In the future I'd like to implement a ShardManager that allows for IPC through child processes but today is not that day. For now, you can now pass your shard id/count during boot:

$ laracord bot:boot --shard-id=0 --shard-count=3
$ laracord bot:boot --shard-id=1 --shard-count=3
$ laracord bot:boot --shard-id=2 --shard-count=3

You can pass a custom token using --token= now too, but this typically wouldn't be recommended.

Remaining Tasks

  • Implement proper hook registration.
  • Improve User model handling.
  • Implement a non-blocking file logging channel.
  • Add file log rotation.
  • Add ability to set the denied handler.
  • Add ability to customize the default !help command.
  • Add support for Discord v2 Components
  • Fix HTTP Middleware merging.
  • Fix booting when no TTY is available.
  • Move components into repository classes.
  • Consider renaming Services to Tasks.
  • Do a PR for Laracord skeleton. (v3 laracord#16)
  • Create a plugin skeleton. (https://github.com/laracord/plugin-skeleton)
  • Write a migration guide.
  • Update documentation.
  • ???

Log1x added 30 commits February 5, 2025 23:11
🔧 Add missing DiscordPHP options to the `discord.php` config
🎨 Utilize the `Laracord` facade when fetching token while resolving a user
🔊 Utilize the logger instead of console for logs
🧑‍💻 Improve HTTP server middleware implementation
🧑‍💻 Implement command component discovery
🎨 Improve command registration
🧑‍💻 Implement context menu component discovery
🎨 Improve context menu registration
🧑‍💻 Implement event component discovery
🎨 Improve event registration
🧑‍💻 Implement service component discovery
🎨 Improve service registration
🧑‍💻 Implement slash command component discovery
🎨 Improve slash command registration
🏗️ Move interaction logic to a trait
🧑‍💻 Improve command cooldown support (Fixes #18)
🎨 Improve abstract command structure/types
🏗️ Move Discord logic to a trait
✨ Add configuration methods for routing/middleware
🏗️ Move HTTP server boot/configuration logic to a trait
✨ Implement `Prompts` for interacting with the bot console while running
🏗️ Move console logic to a trait
@brandonjjon
Copy link

👀 Any movement on this?

@Log1x
Copy link
Member Author

Log1x commented Aug 23, 2025

👀 Any movement on this?

Just launched the new site & docs redesign yesterday: https://laracord.com

I just have to write docs for v3 and I can tag at least a beta.

@brandonjjon
Copy link

Nice! Yeah the site & docs look great. I'm definitely interested in the sharding support even if it's only a basic implementation right now. Looking forward to that being expanded on as well with the ShardManager too. Any idea how scalable Laracord actually is in it's current state, versus something like discord.js?

@Log1x
Copy link
Member Author

Log1x commented Aug 23, 2025

I don't have any way to really know, but I can only assume discord.js is far easier to scale. DiscordPHP will eat memory for truly large bots. Stuff like that is difficult to optimize or even begin to think about if you don't have access to a bot with the reach to actually test though. Extremely small community of developers/users in comparison.

@brandonjjon
Copy link

Gotcha. With proper sharding support and good caching, I'd think a few instances could support a few thousand servers. I'd hope anyway!

@Log1x
Copy link
Member Author

Log1x commented Aug 23, 2025

I think a few thousand servers would be doable for sure. You might have to shard a little more aggressive than other libs, but I still don't really know. Another big factor is what Intents you have enabled on the bot.

Feel free to join the Discord if you're not! https://laracord.com/discord

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment