I'm setting up a small application with Ubiquity Framework in PHP, and I'm trying to disable the default routing system (controller/action/parameters).
The routing system is based on annotations (documented here).
I have a controller with a few routes, which works (don't forget to reset the router cache).
namespace controllers;
class FooController extends ControllerBase
{
/**
* #get("foo")
*/
public function index()
{
echo "I'm on /foo";
}
/**
* #get("bar/{p}")
*/
public function bar($p='default p')
{
echo "I'm on bar/".$p;
}
}
the addresses /foo, /bar and /bar/xxx are accessible, but I would like to disable the default routing system that allows access to the action of an existing controller (without route).
I want to disable the following urls:
/FooController
/FooController/index
/FooController/bar
/FooController/bar/xxx
I didn't find my answer in the doc.
I know the framework is not known (I discovered it through phpbenchmarks website), but the routing system is pretty classic, and it's still php.
If you have any ideas....
Versions:
php 7.3
Ubiquity 2.2.0
I found a solution, indirectly in the doc.
The priority attribute of a route allows you to assign the order in which it will be defined (and therefore requested).
To disable the call of actions on existing controllers, it is therefore possible to define a generic route in the last position returning a 404 error.
namespace controllers;
use Ubiquity\utils\http\UResponse;
class FooController extends ControllerBase
{
...
/**
* #route("{url}","priority"=>-1000)
*/
public function route404($url)
{
UResponse::setResponseCode(404);
echo "Page {$url} not found!";
}
}
If we still want to activate some controllers (the Admin part for example), we must add the requirements attribute, which allows to specify a regex.
namespace controllers;
use Ubiquity\utils\http\UResponse;
class FooController extends ControllerBase
{
...
/**
* #route("{url}","priority"=>-1000,"requirements"=>["url"=>"(?!(a|A)dmin).*?"])
*/
public function route404($url)
{
UResponse::setResponseCode(404);
echo "Page {$url} not found!";
}
}
In this case, the only routes accessible are those defined with annotations + those corresponding to the actions of the Admin controller
With routing problems, routing solution.
Related
i'm new to Symfony. I'm trying to create dynamic universal route that will pick required controller based on part of url. I've found in docs that there is special param {_controller} that can be used in route pattern, but could not find any examples of usage.
// config/routes.yaml
api:
path: /api/{_controller}
So for example for route /api/product i expect ProductController to be initiated.
But as a result i get error "The controller for URI "/api/product" is not callable: Controller "product" does neither exist as service nor as class."
Can somebody please help me understanding how {_controller] param works? Or maybe there is a better way for specifying universal route that can dynamically chose controller without listing controller names in routes.yaml.
Thanks in advance
This isn't really the cleanest way to do what I think you're trying to do. If I am correct in assuming you want to have a /api/product/ point to methods in your product controller, then something like this is more "symfonyish"
// src/Controller/ProductController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/api/product", name="product_")
*/
class ProductController extends AbstractController
{
/**
* #Route("/", name="index")
* --Resolves to /api/product/
*/
public function index(): Response
{
// ...
}
/**
* #Route("/list", name="list")
* --Resolves to /api/product/list
*/
public function list(): Response
{
// ...
}
/**
* #Route("/{productID}", name="get")
* --Resolves to /api/product/{productID}
*/
public function get(string $productID): Response
{
// $productID contains the string from the url
// ...
}
}
Note that this really just scratches the surface of Symfony routing. You can also specify things like methods={"POST"} on the routing directive; so you could have the same path do different things depending on the type of request (e.g. you could have a route /product/{productID} that GETs the product on that request but updates the product on a PATCH request.
Regardless, the takeaway here is that it is unwieldy to have all of your routes defined in routes.yml rather you should define your routes as directives in the controller itself.
We have a Laravel 8 application.
We're using the standard Laravel Auth facade to retrieve the authenticated user.
Our User model has a few custom functions, the most important of which is a shorthand function, hasPermissionTo(). (The reason why is because we have a very custom RBAC setup.)
So in a lot of our controllers, we have something like this...
use Illuminate\Routing\Controller as BaseController;
class ExampleController extends BaseController
{
public function index()
{
if (\Auth::user()->hasPermissionTo('Management:View Users')) {
// do something.
}
// etc.
}
}
That's all well and good until we start running static analysis. We're using Larastan, which is giving me these errors:
------ -------------------------------------------------------------------------------------------
Line Http/Controllers/ExampleController.php
------ -------------------------------------------------------------------------------------------
48 Call to an undefined method Illuminate\Contracts\Auth\Authenticatable::hasPermissionTo().
This also makes sense because the Auth facade proxies Illuminate\Auth\AuthManager and Auth::user(), via __call() magic, normally resolves to Illuminate\Auth\SessionGuard::user() and that typehints this...
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
...
So finally, my question:
Where is the failure here? Do I need to a) configure my static analysis tool better, b) configure Laravel better to more accurately return a specific type, or c) do I need to add explicit if (Auth::user() instanceof User) { ... } clauses all throughout my code?
Is there a correct way to override one of the Laravel stock classes with a more specific one of my own to address more specific functionality? Is there way to type-hint the actual authenticated User into the function declaration so I can declare function index(User $authenticatedUser) and have Laravel autopopulate this in with a more specific type hint?
I understand that I could just add an exclusion for this particular issue in Larastan and move on with my life, but the error is designed to protect against a specific class of error--i.e. if I added Auth0 and replaced App\Model\User with Auth0\Login\User, then I would have an Authenticatable class that fails to run hasPermissionTo(), and I'd have to now fix a bunch of code.
Eventually, this is how we worked around the problem. We added a type-hint for Larastan, so it can infer that $user has this HasRolesContract trait which provides hasPermissionTo().
public function index()
{
/** #var \App\Traits\HasRolesContract */
$user = \Auth::user();
if ($user->hasPermissionTo('Management:View Users')) {
Hopefully this helps someone else!
(Thanks for the nudge, #djjavo)
I don't know the terminology for my problem so I am asking here for the solution since I can't find it by searching.
I am making a simple rest API where I have specified my Routes.php
In my index.php I call the routes:
$routes = new Routes();
$routes->getRoutes();
app/Routes.php
<?php
/* All default application routes go here. Flow specific routes are implemented in the specific flows directory. */
class Routes extends Controller
{
function __construct()
{
/* Inherit stuff from Controller */
parent::__construct();
}
public function getRoutes()
{
/* Routes */
$this->app->group('/api', function () use ($app)
{
$this->app->get('/cron/run', 'Cron:run');
});
}
}
Now I want it to support standalone "modules".
So we add a module and its routes in here:
app/MyModule/Routes.php
Imagine multiple modules in here with their own routes.
Here is the problem.
How can I add routes in all these modules to be included automatically in the application routes?
I do not want to override.
I am using Slim PHP framework if that helps.
You simply need to instantiate each module's Routes class in turn within your bootstrapping process (i.e. before you call run()).
As long as each module defines a unique string for the group's URL (/api in your example), then they won't clash.
I would like to extend Laravels Router class (Illuminate\Routing\Router) to add a method I need a lot in my application.
But sadly I can't get this to work. I already extended other classes successfully so I really have no idea where my wrong thinking comes from.
Anyway, right into the code:
<?php
namespace MyApp\Extensions;
use Illuminate\Routing\Router as IlluminateRouter;
class Router extends IlluminateRouter
{
public function test()
{
$route = $this->getCurrentRoute();
return $route->getParameter('test');
}
}
So as you see I want to get the parameter set by {test} in routes.php with a simple call like:
Router::test();
Not sure how to go on now. Tried to bind it to the IOC-Container within my ServiceProvider in register() and boot() but I got no luck.
Whatever I try I get either a constructor error or something else.
All solutions I found are too old and the API has changed since then.
Please help me!
edit:
I already tried binding my own Router within register() and boot() (as said above) but it doesn't work.
Here is my code:
<?php
namespace MyApp;
use Illuminate\Support\ServiceProvider;
use MyApp\Extensions\Router;
class MyAppServiceProvider extends ServiceProvider {
public function register()
{
$this->app['router'] = $this->app->share(function($app)
{
return new Router(new Illuminate\Events\Dispatcher);
}
// Other bindings ...
}
}
When I try to use my Router now I have the problem that it needs an Dispatcher.
So I have to do:
$router = new Router(new Illuminate\Events\Dispatcher); // Else I get an exception :(
Also it simply does nothing, if I call:
$router->test();
:(
And if I call
dd($router->test());
I get NULL
Look at: app/config/app.php and in the aliases array. You will see Route is an alias for the illuminate router via a facade class.
If you look at the facade class in Support/Facades/Route.php of illuminate source, you will see that it uses $app['router'].
Unlike a lot of service providers in laravel, the router is hard coded and cannot be swapped out without a lot of work rewiring laravel or editing the vendor source (both are not a good idea). You can see its hardcoded by going to Illuminate / Foundation / Application.php and searching for RoutingServiceProvider.
However, there's no reason i can think of that would stop you overriding the router class in a service provider. So if you create a service provider for your custom router, which binds to $app['router'], that should replace the default router with your own router.
I wouldn't expect any issues to arise from this method, as the providers should be loaded before any routing is done. So overriding the router, should happen before laravel starts to use the router class, but i've not this before, so be prepared for a bit of debugging if it doesn't work straight away.
So I was asking in the official Laravel IRC and it seems like you simply can't extend Router in 4.1 anymore. At least that's all I got as a response in a pretty long dialogue.
It worked in Laravel 4.0, but now it doesn't. Oh well, maybe it will work in 4.2 again.
Other packages suffer from this as well: https://github.com/jasonlewis/enhanced-router/issues/16
Anyway, personally I'll stick with my extended Request then. It's not that much of a difference, just that Router would've been more dynamic and better fitting.
I'm using Laravel 4.2, and the router is really hard coded into the Application, but I extended it this way:
Edit bootstrap/start.php, change Illuminate\Foundation\Application for YourNamespace\Application.
Create a class named YourNamespace\Application and extend \Illuminate\Foundation\Application.
class Application extends \Illuminate\Foundation\Application {
/**
* Register the routing service provider.
*
* #return void
*/
protected function registerRoutingProvider()
{
$this->register(new RoutingServiceProvider($this));
}
}
Create a class named YourNamespace\RoutingServiceProvider and extend \Illuminate\Routing\RoutingServiceProvider.
class RoutingServiceProvider extends \Illuminate\Routing\RoutingServiceProvider {
protected function registerRouter()
{
$this->app['router'] = $this->app->share(function($app)
{
$router = new Router($app['events'], $app);
// If the current application environment is "testing", we will disable the
// routing filters, since they can be tested independently of the routes
// and just get in the way of our typical controller testing concerns.
if ($app['env'] == 'testing')
{
$router->disableFilters();
}
return $router;
});
}
}
Finally, create YourNamespace\Router extending \Illuminate\Routing\Router and you're done.
NOTE: Although you're not changing the name of the class, like Router and RoutingServiceProvider, it will work because of the namespace resolution that will point it to YourNamespace\Router and so on.
I'm trying to create a routing system based on annotations (something like on Recess Framework).
<?php
class MyController extends ActionController {
/** !Route GET /hello/$firstname/$lastname **/
public function helloAction($firstname, $lastname) {
echo('Hello '.$firstname.' '.$lastname);
}
}
?>
If I go to http://domain.com/hello/James/Bond I get
Hello James Bond
So I have two questions:
1) Is it a good idea? Pros and cons vs centralized routing system (like Zend Framework). Maybe I don't see problems that my arise later with this routing technique.
2) How to check for duplicate routes if there is regexp in routes
<?php
class MyController extends ActionController {
/**
*!Route GET /test/$id = {
* id: [a-z0-9]
*}
**/
public function testAction($id) {
echo($id);
}
/**
*!Route GET /test/$id = {
* id: [0-9a-z]
*}
**/
public function otherTestAction($id) {
echo($id);
}
}
?>
I get two routes: /test/[a-z0-9]/ and /test/[0-9a-z]/ and if i go to http://domain.com/test/a12/ both routes are valid.
Thanks :)
Try using the Java annotation format which should be much easier to parse uniformly.
It looks something like this:
<?php
class MyController extends ActionController {
/**
#Route(get=/hello/$firstname/$lastname)
#OtherVal(var1=2,var2=foo)
#OtherVal2
**/
public function helloAction($firstname, $lastname) {
echo('Hello '.$firstname.' '.$lastname);
}
}
?>
And parse your annotation out with the following regex:
#(?P<annotation>[A-Za-z0-9_]*)(\((?P<params>[^\)]*))?
And of course cache these where possible to avoid repeated parsing.
Cons:
It may be difficult to keep an overview of URL mappings of all methods in your server.
To change a URL you have to change the source code, mapping is not separated from the application.
If the method signature and mapping are always as related as the example you might use reflection to extract the mapping where helloAction is picked up as /hello and each method argument is a subdirectory of this in the order as they're defined.
Then the annotation wouldn't need to duplicate the URL, only the fact that the method is an endpoint, something like this:
<?php
class MyController extends ActionController {
/** !endpoint(method=GET) **/
public function helloAction($firstname, $lastname) {
echo('Hello '.$firstname.' '.$lastname);
}
}
I think it's a good idea / Decoupling code and entry point seems pretty much used everywhere
Usually you don't check for it: the first one that matches wins.
Doing so is a great idea, as long as you cache the compiled routes in production. There is a cost associated to parsing your files when routing so you want to avoid that when not developing.
As for checking for duplicates, don't check by comparing the declaration. Simply check when routing. If two rules matches, throw a DuplicateRouteException. So, when routing http://domain.com/test/a12/, you'll see that both routes are valid and throw the exception.