Quest
Access resources directly without defining routes, thanks to PHP attributes.
🪬Introdiction
Quest, the Master Guru which simplifies your quest, it gives you a short route to follow to reach your goal (resource).
I know, you don’t need to lie to me 🤥, you have remembered when you are brainstorming to implement a functionality or recover resources and ask you: but … how do I will organize my Routes?
The question of the routes, I do not hide you, me, it fucks the laziness. Because I must be defined a road for any call and suddenly I find myself with many of the defined Routes.
I know, it’s not perfect, and neither is Quest, but… it will make your job a lot easier and eliminates all that mental overload, useful but boring.
✨ Installation
Prerequisites
- PHP 8.0+
- Laravel 9.x+
- Have already made use of the Facade Route. Ex:
Route::get('route/to/x/{param}', fn(string $param) => X)
Install Quest from composer
composer require hacp0012/quest
Publish the config files
Quest needs a few files to work properly.
php artisan vendor:publish --tag=quest
The file route quest.php
is a base file that can be useful to you to register your classes. Because the classes registered in this list are public of the second level, because they have a priority that comes after the list passed in your route Quest:spaw(routes: [])
These references are accessible from all requests.
Experimental: It is now possible to pass directories, whose base starts from the base directory (of the project) of Laraval. Very useful if you do not want to specify each time a class that contains your references, You just have to specify a directory or several directories.
Provided that the punched method is in a class and the class is in a namespace. Only the first class is considered in a .php file.
This file is generated automatically but you can generate it manually.
The config quest.php
Contains some settings you can apply if you have made patterns in your project’s bootstrap/provider.php for custom targeting of your route files (/routes/web.php or /routes/api.php).
Because Reference Tracker needs to know your targets to track your referenced (punched) methods.
To publish the configuration files type the command php artisan vendor:publish
This will create the file config/quest.php
(which contains some configuration bits) and the global quest routing file in routes/quest.php
.
Manually, you can publish the config files like this php artisan quest:publish in the configs/ and routes/ directory manually.
🏳️ How is it useful to me?
Quest allows you to access resources or send your resources directly without worrying about Routes. You just need to set Reference Flags or Reference Marks using PHP attributes on your class methods and call 🤙 these methods directly, with the same parameters as those of the method.
Don’t worry, you just need to respect the same types of parameters that you had defined on your method.
Let’s take for example, in a case where you are designing an application and reach a certain level where your application will need to retrieve an up-to-date list of telephone codes. You just have to create a method in a class, reference it and call it; without worrying about creating a route for it.
class PhoneHandler
{
#[QuestSpaw(ref: 'r84d2S1tM')]
function getCodes(): array
{
//...
}
}
// And call it as this :
axios.get('https://myhost.com/r84d2S1tM');
An other exemple :
#[QuestSpaw(ref: 'my quest flag ID', filePocket: 'guidPicture')]
function yogaStage(int $moon, int $sunRise, UploadedFile $guidPicture = null): int
{
# $guidPicture --> Illuminate\Http\UploadedFile
return $moon + $sunRise;
}
// So the call will simply be like this:
// Client code :
dio.post("/quest/my quest flag ID", data: {'moon': 2, 'sunRise': 7});
Note that Quest takes care of passing parameters to your method. (And you can even pass it a file) as parameters, just give the parameter name to your file. (but you have to report it in filePocket)
🚧 How Quest works
Quest is based on PHP attributes. It goes through all your references and creates a registry of the methods you have marked. A method is marked by a reference key that serves as a reference point for quest to call your method.
To create a reference:
#[QuestSpaw(ref: 'reference.key')]
functiton gong(): array
🧩 Usage
Let’s start by defining our route with Quest:
# In your route file
use Hacp0012\Quest\Quest;
Route::get(uri: '/', action: fn() => view('home')); // Exemple ...
$routes = [
Forest::class,
# Or specifie a directory:
// 'app/demo',
];
# Or it can be only the class name.
$routes = Forest::class; // Or directory.
Quest::spawn(uri: 'quest', routes: $routes)->name('my.quest');
# Or
Quest::spaw(uri: 'my/forest', [Forest::class, 'RrOWXRfKOjauvSpc7y']);
// For direct call.
// Note method name, this is `spaw`.
Hacp0012\Quest
is the main namespace. Contains theQuest()
class and theQuestRouter()
class and theQuestSpawMethod
enum. Then there is the namespaceHacp0012\Quest\Attributes
, which contains the Quest attributes. Such asQuestSpaw()
andQuestSpawClass()
.
You can add middlewares and such because Quest’s static spawn
function returns an object of type Illuminate\Routing\Route
so it supports all other methods of the Route facade.
Note that the
Forest
class has been added to the list of routes in thespaw(..., routes: [Forest::class])
method.
Let’s now define our Forest class which will contain our methods referenced by spaw. punched.
// In your class
class Forest
{
#[QuestSpaw(ref:'NAhLlRZW3g3Fbh30dZ')]
function tree(string $color): int
{
return $this->fruits();
}
function fruits(): int
{
return 18;
}
#[QuestSpaw(ref: 'RrOWXRfKOjauvSpc7y', method: QuestSpawMethod::GET, jsonResponse: false)]
function displayAnApples(int $count): View
{
//...
}
}
And that’s it, now you can start calling your methods punched (referenced) by their reference key ref: 'NAhLlRZW3g3Fbh30dZ'
.
Note that you can use any phrase as a reference. Although quest allows you to generate unique keys. You can use something like: forest.app.tree.NAhLlRZW3g3Fbh30dZ. Or see the CLI command reference for more details
As in this example above:
// Code client :
dio.get("/quest/NAhLlRZW3g3Fbh30dZ", data: {'color': 'green'});
// Or from your view blad file:
route('my.quest', ['quest_ref' => 'RrOWXRfKOjauvSpc7y', 'count' => 9]);
# It's simple when you have given a name to your route. `->name('quest')`.
quest_ref
is the parameter key of the route generated by Quest. The kind of parameters that we pass in the url: https://moonsite.com/my/quest/{quest_ref}
🔖 There is another way to call Quest. That is to pass QuestRouter and create a router object, like this:
Route::post('quest/{ref}', function(string $ref) {
$quest = new QuestRouter(questRef: $ref, routes: [QuestTest::class]);
return $quest->spawn();
});
Or
Route::post('quest/{ref}', function(string $ref) {
$quest = new Quest;
$data = $quest->router(questId: $ref, classes: [QuestTest::class]);
return $data;
});
⚠️ Even though this is not the cleanest method, I advise you not to use it because it can give you weird return types that even Laravel’s Service container
won’t be able to interpret.
Service container
Laravel provides an automatic dependency injection system that it calls Service Container. It is able to construct an object that you have declared as a parameter.
Take this as a reminder:
Route::get('/', function(Request $request, int $number) {
// The container service automatically builds $request for you.
});
Well Quest can’t spoil this happiness. Quest also resolves your objects declared in the parameters. In any case feel free to do what you want.
🪄 Try and you will know. 🧙♂️
👽 CLI Commandes
php artisan quest:ref [--list [--no-table] [--index=n]] [--generate=n] [--track='']
see in doc.
php artisan quest:generate-ref [36] [--uuid]
Generate a reference key. But this does not prevent you from taking any text for reference. This is just a help, to allow you to do something unique.
If you add the --uuid
option, it will generate a UUID key and ignore the length you specified. UUIDs are 36 characters long (they are unique anyway)
By default the command generates 36 random characters.
php artisan quest:generate-ref
php artisan quest:track-ref [ref-id]
Track the reference of a pointed method (spawed)
Among the good things, there is the ref tracker. This tracker is great, it allows you to find yourself more easily and find the implementation of your method.
php artisan quest:track-ref RrOWXRfKOjauvSpc7y
Because let’s be serious, the reference key system can be a little more constipating when you don’t have a very solid architecture or when you are a beginner. This is why I advise you not to rely only on the keys generated by the quest:generate-ref
command, get into the habit of adding a few words called human readable. Ex. ‘my.forest.trees.meXRQbm0WQP6ZpAN5U’
To check the quest version:
php artisan about
This is an internal command of Laravel
🔆 Api reference
Best practices
The type of return in comment
Let’s take this example:
/** @return stdClass {state:UPDATED|FAILED} */
#[QuestSpaw(ref: 'com.update.text.628L7cLg1RGTvaxkgg')]
function updateText(string $com_id, string $title, string $text, string $status): stdClass
{
$return = new stdClass;
$state = false;
// ...
$return->state = $state ? 'UPDATED' : 'FAILED';
return $return;
}
Please specify the return type and details about it, because the tracker returns the PHP-Doc comments of the method. This will help you to have a direct idea of what is returned by the call.
Things to add
- Temporary routes.
FAQ
How can I do my request
validations ?
First of all the method parameters are also another type of validation but low level.
You can retrieve all your request parameters
via the Request
object like this:
function myMethod(Request $request, array $myQueryParams)
{
$validateds = $request->validate([...], [...]);
$validateds = request()->validate(...);
# ...
}
By default, quest supports some basic (native) types
['bool', 'int', 'float', 'string', 'null', 'array', 'mixed', UploadedFile::class]
and the one you linked in Service Container via Provider. Other types are not supported. The reason is that over HTTP(S) we don’t often transfer objects. It’s often text and often formatted in JSON. So the basic (native) types are often the same types that the JSON annotation supports.