Skip to content

Commit

Permalink
Add magic routes
Browse files Browse the repository at this point in the history
  • Loading branch information
marcotas committed Mar 29, 2020
1 parent 6831cbc commit 6a2b08e
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 55 deletions.
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
root = true

[*]
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "marcot89/laravel-magic-routes",
"description": "Magic Routes for Laravel Applications",
"license": "license",
"license": "MIT",
"authors": [
{
"name": "Marco Túlio de Avila Santos",
Expand Down
2 changes: 1 addition & 1 deletion config/laravelmagicroutes.php → config/magicroutes.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

return [
//
];
];
34 changes: 32 additions & 2 deletions src/LaravelMagicRoutes.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,37 @@

namespace MarcoT89\LaravelMagicRoutes;

use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use ReflectionObject;
use Symfony\Component\Finder\SplFileInfo;

class LaravelMagicRoutes
{
// Build wonderful things
}
public static function boot()
{
(new static)->getControllers()->map(function (\Illuminate\Routing\Controller $controller) {
$reflectionClass = new ReflectionObject($controller);

$registerRoutes = $reflectionClass->getMethod('registerRoutes');
$registerRoutes->setAccessible(true);
$registerRoutes->invoke($controller);
});
}

private function getControllers(): Collection
{
$files = collect(File::allFiles(app_path('Http/Controllers')));

return $files->map(function (SplFileInfo $file) {
$controllerClass = Str::of($file->getRealPath())
->replace(app_path(), '')
->replace('/', '\\')
->replace('.php', '')
->prepend('App');
/** @var \Illuminate\Routing\Controller $controller */
return resolve($controllerClass->__toString());
})->filter(fn (\Illuminate\Routing\Controller $controller) => method_exists($controller, 'registerRoutes'));
}
}
53 changes: 2 additions & 51 deletions src/LaravelMagicRoutesServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,7 @@ class LaravelMagicRoutesServiceProvider extends ServiceProvider
*/
public function boot()
{
// $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'marcot89');
// $this->loadViewsFrom(__DIR__.'/../resources/views', 'marcot89');
// $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
// $this->loadRoutesFrom(__DIR__.'/routes.php');

// Publishing is only necessary when using the CLI.
if ($this->app->runningInConsole()) {
$this->bootForConsole();
}
LaravelMagicRoutes::boot();
}

/**
Expand All @@ -31,52 +23,11 @@ public function boot()
*/
public function register()
{
$this->mergeConfigFrom(__DIR__.'/../config/laravelmagicroutes.php', 'laravelmagicroutes');
// $this->mergeConfigFrom(__DIR__.'/../config/magicroutes.php', 'magicroutes');

// Register the service the package provides.
$this->app->singleton('laravelmagicroutes', function ($app) {
return new LaravelMagicRoutes;
});
}

/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['laravelmagicroutes'];
}

/**
* Console-specific booting.
*
* @return void
*/
protected function bootForConsole()
{
// Publishing the configuration file.
$this->publishes([
__DIR__.'/../config/laravelmagicroutes.php' => config_path('laravelmagicroutes.php'),
], 'laravelmagicroutes.config');

// Publishing the views.
/*$this->publishes([
__DIR__.'/../resources/views' => base_path('resources/views/vendor/marcot89'),
], 'laravelmagicroutes.views');*/

// Publishing assets.
/*$this->publishes([
__DIR__.'/../resources/assets' => public_path('vendor/marcot89'),
], 'laravelmagicroutes.views');*/

// Publishing the translation files.
/*$this->publishes([
__DIR__.'/../resources/lang' => resource_path('lang/vendor/marcot89'),
], 'laravelmagicroutes.views');*/

// Registering package commands.
// $this->commands([]);
}
}
217 changes: 217 additions & 0 deletions src/RouteBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<?php

namespace MarcoT89\LaravelMagicRoutes;

use ReflectionMethod;
use ReflectionParameter;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Route;

class RouteBuilder
{
protected $namespace = "App\Http\Controllers";
/** @var Controller */
protected $controller;

/** @var ReflectionMethod */
protected $method;

/** @var string */
public $route;

/** @var Collection */
protected $parameters;

protected $httpMethods = ['get', 'put', 'post', 'delete', 'patch'];

public function __construct(Controller $controller, ReflectionMethod $method)
{
$this->controller = $controller;
$this->method = $method;
$this->parameters = $this->sanitizeParameters($method->getParameters());
}

public function register()
{
$this->createBaseUrl()
->createActionUrl();


Route::{$this->httpMethod()}($this->route, [$this->getClass(), $this->method->name])->name($this->getRouteName());
}

private function createBaseUrl()
{
$this->route = $this->createControllerBaseUrl();

return $this;
}

private function createActionUrl()
{
$routeParams = $this->createRouteParameters();
$firstParam = $routeParams->shift();

$this->route .= $firstParam ? "/$firstParam" : '';

if ($suffix = $this->suffix()) {
$this->route .= "/$suffix";
}

if ($routeParams->isNotEmpty()) {
$this->route .= "/{$routeParams->join('/')}";
}

return $this;
}

private function suffix()
{
if ($this->isCrudAction()) {
return $this->getCrudActions()->get($this->method->name)->suffix
? $this->getActionName() : '';
}

return $this->getActionName();
}

private function httpMethod()
{
if ($this->isCrudAction()) {
return $this->getCrudActions()->get($this->method->name)->verb;
}

return $this->getHttpMethodFromMethodName() ?? 'get';
}

private function getHttpMethodFromMethodName()
{
return collect($this->httpMethods)->map(function ($httpMethod) {
return Str::of($this->method->name)
->kebab()
->startsWith("$httpMethod-")
? $httpMethod : null;
})->filter()->first();
}

private function getActionName()
{
$httpMethod = $this->getHttpMethodFromMethodName();

return Str::of($this->method->name)
->replace($httpMethod, '')
->kebab()
->__toString();
}

private function isCrudAction()
{
return $this->getCrudActions()->keys()->contains($this->method->name);
}

private function getCrudActions(): Collection
{
return collect([
'index' => (object) [
'verb' => 'get',
'suffix' => false,
],
'store' => (object) [
'verb' => 'post',
'suffix' => false,
],
'update' => (object) [
'verb' => 'put',
'suffix' => false,
],
'show' => (object) [
'verb' => 'get',
'suffix' => false,
],
'destroy' => (object) [
'verb' => 'delete',
'suffix' => false,
],
'create' => (object) [
'verb' => 'get',
'suffix' => true,
],
'edit' => (object) [
'verb' => 'get',
'suffix' => true,
],
'forceDestroy' => (object) [
'verb' => 'delete',
'suffix' => true,
],
]);
}

private function createRouteParameters(): Collection
{
return $this->parameters
->map(function (ReflectionParameter $parameter) {
return "{{$parameter->name}}";
});
}

private function sanitizeParameters($parameters): Collection
{
return collect($parameters)
->filter(function (ReflectionParameter $parameter) {
$class = $parameter->getClass();
return $class->name !== Request::class
&& !$class->isSubclassOf(Request::class);
});
}

private function createControllerBaseUrl()
{
$namespacedController = Str::of(get_class($this->controller))
->after($this->namespace)
->trim('\\')
->__toString();

$baseUrl = $this->getResourceUrl();

$prefixArray = collect(explode(
'/',
Str::of($namespacedController)
->replace('\\', '/')
->__toString()
))
->map(fn ($name) => Str::of($name)->snake()->slug()->__toString());

$prefixArray->pop();
$prefix = $prefixArray->join('/');

return $prefix ? "$prefix/$baseUrl" : $baseUrl;
}

private function getResourceUrl()
{
return Str::of($this->getClass())
->afterLast('\\')
->beforeLast('Controller')
->snake()
->slug()
->plural()
->__toString();
}

private function getRouteName()
{
$baseName = Str::of($this->createControllerBaseUrl())->replace('/', '.');
$routeName = Str::of($this->getActionName());

return "$baseName.$routeName";
}

private function getClass()
{
return get_class($this->controller);
}
}
31 changes: 31 additions & 0 deletions src/Traits/MagicRoutes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace MarcoT89\LaravelMagicRoutes\Traits;

use App\MagicRoutes\RouteBuilder;
use ReflectionMethod;
use ReflectionObject;
use Illuminate\Routing\Controller;
use Illuminate\Support\Collection;

trait MagicRoutes
{
protected function registerRoutes()
{
$this->getPublicMethods()->each(function (ReflectionMethod $method) {
/** @var Controller $this */
(new RouteBuilder($this, $method))->register();
});
}

private function getPublicMethods(): Collection
{
return collect((new ReflectionObject($this))->getMethods(ReflectionMethod::IS_PUBLIC))
->filter(function (ReflectionMethod $method) {
return $method->getDeclaringClass()->name == static::class;
})
->filter(function (ReflectionMethod $method) {
return !in_array($method->name, ['__construct']);
});
}
}

0 comments on commit 6a2b08e

Please sign in to comment.