Absolutely, your use case is perfect for a plug-n-play, class-based approach: each challenge type as a self-contained class ("plugin"), with its own validation logic and static metadata. In Laravel or any normal PHP app, you can make this both scalable and simple—no need for a complex service provider setup for each new type.
Recommended Solution:
1. Define a ChallengeType interface (contract):
This defines what any challenge type must provide: static info, validation, etc.
<?php
namespace App\ChallengeTypes;
interface ChallengeType
{
public static function type(): string;
public static function name(): string;
public static function view(): string;
// For challenge configuration (for creators)
public static function creatorView(): string;
// For player interaction
public static function playerView(): string;
// Validation (for player submitted answers)
public function validateAnswer(array $challengeData, $userAnswer): bool;
}
2. Implement concrete challenge types:
Example: MultipleChoiceChallenge
<?php
namespace App\ChallengeTypes;
class MultipleChoiceChallenge implements ChallengeType
{
public static function type(): string
{
return 'multiple_choice';
}
public static function name(): string
{
return 'Multiple Choice';
}
public static function view(): string
{
return 'challenges.multiple_choice.show';
}
public static function creatorView(): string
{
return 'challenges.multiple_choice.creator';
}
public static function playerView(): string
{
return 'challenges.multiple_choice.player';
}
public function validateAnswer(array $challengeData, $userAnswer): bool
{
// $challengeData might contain the correct answer, etc.
return $userAnswer === $challengeData['correct_answer'];
}
}
3. Challenge Type Registry / Discovery
Have a central place to register available challenge types, e.g. in a config/challenge_types.php:
return [
\App\ChallengeTypes\MultipleChoiceChallenge::class,
\App\ChallengeTypes\TextAnswerChallenge::class,
// just add to this array as you add new classes!
];
(You could also auto-discover classes in a directory if you want.)
4. Usage in your code
Any time you need to render/create/validate a challenge, you look up the right class (by type), instantiate it, and use the methods:
$type = 'multiple_choice'; // from DB or user input
$challengeClass = collect(config('challenge_types'))
->first(fn($class) => $class::type() === $type);
$challenge = new $challengeClass();
// For rendering views
$view = $challengeClass::playerView(); // or creatorView()
// For validation
$isCorrect = $challenge->validateAnswer($challengeData, $userAnswer);
5. Adding a new challenge type:
Just make a new class implementing the interface, add it to your array. No central code changes needed.
6. Views
Store your Blade templates for each challenge type in subfolders or with type-based names, as above.
Why This Works Well
- No need to edit central logic or controllers.
- Adding a type: Just 1 class + 3 blade views, that's it!
- Very scalable, easy to maintain.
- Pluggable and testable design.
If you want to make it even simpler, you can autoload all classes in a directory; but the above is already much simpler than a service provider/complex plugin discovery system.
Let me know if you want code snippets for auto-discovery, or a working example repo!