I am using league/route 4.2.
I am trying to implement a lazy loading controller using wildcard.
class Router
{
private $container;
private $router;
function __construct()
{
$this->container = new Container();
$this->router = new \League\Route\Router();
$this->mapRouters();
$this->dispatch();
}
private function mapRouters() {
$this->router->map('GET', '/', [MainController::class, 'index']);
//$this->router->map('GET', 'Main', 'Nanitelink\Module\Main\MainController::index');
$this->router->map('GET', '{module}', 'Nanitelink\Module\{module}\{module}Controller::index');
}
private function dispatch() {
$request = $this->container->get('Zend\Diactoros\ServerRequest');
$response = $this->router->dispatch($request);
$emitter = $this->container->get('Zend\HttpHandlerRunner\Emitter\SapiEmitter');
$emitter->emit($response);
}
}
Now I know for a fact that the commented route works.
I tried to replace it with wildcard, but probably I am not getting the syntax correctly, but it throws following exception.
Class 'Nanitelink\Module\{module}\{module}Controller' not found
What is the right way to use wildcard routing in league/route?
Documentation does not explain how to use wildcard in lazy loading way.
This was the discussion on github with the package manager.
https://github.com/thephpleague/route/issues/247
So to keep the answer short, the package does not and will not allow the requirement to replace the callable to be named using variables matched to the wildcard.
I did suggest a source code change in github, if anyone need the information.
Related
Recently I am upgrading the system to Symfony 4.2, in one of my page, I encountered the error saying
Call to protected method Symfony\Bundle\FrameworkBundle\Controller\Controller::generateUrl() from context 'Acme\Bundle\Security\Listener\SecurityListener'
The line at which the error is showing:
$this_url = $controller[0]->generateUrl($event->getRequest()->get('_route'), $event->getRequest()->get('_route_params'));
Please let me know, is there anything that I have left.
You are trying to call the controller helper method from a listener. As #ArtisticPhoenix has said in the comments, this is not allowed. What you could do here is instead rewrite your listener to use not the controller's method (which is just intended to be a helper for your actions), but the actual router method that generates the URL. Take a look at the Controller (or ControllerTrait depending on your Symfony version). The generateUrl() method makes a call on the #router service:
/**
* Generates a URL from the given parameters.
*
* #see UrlGeneratorInterface
*
* #final
*/
protected function generateUrl(string $route, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
{
return $this->container->get('router')->generate($route, $parameters, $referenceType);
}
That's what you actually have to use instead of calling the controller's method. You could try something like this:
$this_url = $this->container->get('router')->generate($event->getRequest()->get('_route'), $event->getRequest()->get('_route_params'));
Although, you'd have to make sure you either have the container available (which in general is not a good pattern), or you pass the Router into your service as a dependency. This way, you'd change $this->container->get('router') to the prop you're injecting the router into.
If you just want to generate an url in your Listener, you can simply inject the router component to you class :
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
...
// Use it like this
$url = $this->router->generate(
$event->getRequest()->get('_route'),
$event->getRequest()->get('_route_params')
);
In my Symfony project I'll have lot of classes that have similar dependecies (however, the classes are not directly related to each other). For example, most of them requires access to EventBus.
In other framework I was able to specify an interface for the class, for example:
interface EventBusAwareInterface
{
public setEventBus(EventBus $bus);
public getEventBus() : EventBus
}
and then configure DI container to recognize such objects that implements this interface, and call their setEventBus() method with proper argument.
I wonder if there's a method to do the same in Symfony4.
You can use _instanceof directive in your services.yaml like that:
services:
_instanceof:
App\EventBusAwareInterface:
calls:
- method: setEventBus
arguments:
- '#event.bus.service'
My original comment was not quire correct. You can use #inject but it seems to require an additional jms bundle. Could have sworn the container supported it out of the box but I guess not.
However, autowire supports a #required annotation which seems to do the trick.
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
trait RouterTrait
{
/** #var RouterInterface */
protected $router;
/**
* #param RouterInterface $router
* #required
*/
public function setRouter(RouterInterface $router)
{
$this->router = $router;
}
// Copied directly from Symfony ControllerTrait
protected function generateUrl(
string $route,
array $parameters = array(),
int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
{
return $this->router->generate($route, $parameters, $referenceType);
}
protected function redirect($url, $status = 302) : RedirectResponse
{
return new RedirectResponse($url, $status);
}
protected function redirectToRoute($route, array $parameters = array(), $status = 302) : RedirectResponse
{
return $this->redirect($this->generateUrl($route, $parameters), $status);
}
}
Now, any autowired service that uses the RouterTrait will automatically get the router injected.
Yes, something even simpler is very possible. However, I would not encourage over-usage as it can very quickly introduce things like method name collisions and reduce code readability.
That said, Symfony introduced service auto-wiring concept starting with 3.3 (I think), which can be used to have dependency injection with zero-config. In PHP, interfaces cannot contain implementation, but, traits can. So, you could do something like this:
trait FooTraitHandler
{
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
And then:
class RealService
{
use FooTraitHandler;
public function multiply($a, $b)
{
$this->logger->log(LogLevel::ALERT, "Doing some basic math!");
return $a * $b;
}
}
And finally, for example, your controller could inject this RealService service and use multiply method as usual.
So, couple of things worth mentioning:
You do not need pair of getter/setter - trait's member is visible in class utilizing it.
You can utilize many traits, achieving just what you wanted with many interfaces
Finally, if some of utilized traits have methods with same name, you'll get a fatal error. As per official docs:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved. To resolve naming conflicts between Traits used in the same class, the insteadof operator needs to be used to choose exactly one of the conflicting methods.
But, in my opinion, doing so deteriorates code readability substantially.
Hope this helps...
I want to swap out my client call or better i try to make a wrapper around this package, so i dont have to write this everytime, so i made a new ServiceProvider which should call
// Create a new client,
// so i dont have to type this in every Method
$client = new ShopwareClient('url', 'user', 'api_key');
on every request i make.
// Later after the Client is called i can make a Request
return $client->getArticleQuery()->findAll();
SwapiServiceProvider
<?php
namespace Chris\Swapi;
use Illuminate\Support\ServiceProvider;
use LeadCommerce\Shopware\SDK\ShopwareClient;
class SwapiServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
}
/**
* Register any package services.
*
* #return void
*/
public function register()
{
$this->app->singleton(ShopwareClient::class, function () {
return new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
);
});
}
}
My Class
...
use LeadCommerce\Shopware\SDK\ShopwareClient as Shopware;
class Swapi
{
public function fetchAllArticles(Shopware $shopware)
{
return $shopware->getArticleQuery()->findAll();
}
}
Testing
I just call it in my routes.php for testing
use Chris\Swapi\Swapi;
Route::get('swapi', function () {
// Since this is a package i also made the Facade
return Swapi::fetchAllArticles();
});
But i get everytime the error
FatalThrowableError in Swapi.php line 18: Type error: Argument 1
passed to Chris\Swapi\Swapi::fetchAllArticles() must be an instance of
LeadCommerce\Shopware\SDK\ShopwareClient, none given, called in
/Users/chris/Desktop/code/swapi/app/Http/routes.php on line 7
So i am asking why this
return new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
);
is not called everytime i call a method e.g $shopware->getArticleQuery()->findAll();
Does anyone know why?
I think there might be some confusion here about Laravel's IoC. When you use return Swapi::fetchAllArticles();, Laravel doesn't know what you are doing because you haven't used the container to build out the Swapi class (even though you have registered one with the container) nor do you have a facade built to access it in that manner. Otherwise PHP is going to complain because your function isn't static.
I just wrote this code and verified that it works as far as Laravel putting it all together.
In my service provider, my register function was this...
public function register()
{
$this->app->singleton('swapi', function($app) {
return new SwapiRepository(
new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
)
);
});
}
Keep in mind, swapi is really just a key the container will use to find the actual class. There's no need to pass in the entire qualified class name when you can keep it simple and easy.
My SwapiRepository which is really the wrapper for the Shopware SDK.
use LeadCommerce\Shopware\SDK\ShopwareClient;
class SwapiRepository
{
protected $client;
public function __construct(ShopwareClient $client)
{
$this->client = $client;
}
public function fetchAllArticles()
{
return $this->client->getArticleQuery()->findAll();
}
}
At this point, you are basically done. Just add App\Providers\SwapiServiceProvider::class, in the providers array (which you probably have done already) in app/config.php and use your wrapper like so...
$swapi = app('swapi');
$swapi->fetchAllArticles();
Or you can have Laravel inject it into other classes as long as Laravel is building said class.
If you want to build out a facade for this to save yourself a line of code each time you want to use this or for snytactical sugar...
use Illuminate\Support\Facades\Facade;
class Swapi extends Facade
{
protected static function getFacadeAccessor() { return 'swapi'; }
}
Make sure to update your aliases array in app/config.php so that it contains 'Swapi' => App\Repositories\Swapi::class,
And finally you should be able to use it like so...
Swapi::fetchAllArticles();
Please note your namespaces are different than mine so you may need to replace mine with yours. You should also now be able to easily inject Swapi into other classes and even method injected into your controllers where needed.
Just remember if you do that though, make sure you are grabbing instances of those classes from Laravel's service container using the app() function. If you try to build them out yourself using new SomeClass, then you have the responsibility of injecting any dependencies yourself.
I need to use a custom Implementation of UrlGenerator. So how can I change the default binding of laravel, that is implemented somewhere deep in the core as
'url' => ['Illuminate\Routing\UrlGenerator', 'Illuminate\Contracts\Routing\UrlGenerator'],
against my own implementation?
Furthermore I am not shure. I assume this line above does actually two things. it will store the bindinung under the key "url" and it will also do the mapping of the Interface to the class. So I actually need to override both! How to do that? Furthemore how to find out if this must be bound as "shared"(singleton) or "new instance every time"?
Thanks very much!
Take a look at the Service Container guide http://laravel.com/docs/5.1/container
In this specific case I think all you need to do is to tell the app to replace the alias that already exists.
To do that I would recommend creating a ServiceProvider, registering int the config/app.php file and inside that one in the register method put something like:
$this->app->bind('Illuminate\Routing\UrlGenerator', 'yourownclasshere');
Let us know if it works.
Update: I removed the option that didn't work and left only the one that worked.
I did what Nestor said in his answer, but it didn't quite work for me. So this is what I did to make it work.
Inside my service provider in method register I first tried this:
$this->app->bind('url', MyCustomProvider::class);
This did register my URL provider instead of the default one. The problem was that now my provider didn't have any access to routes. I checked the Laravel code for \Illuminate\Routing\RoutingServiceProvider because it has a method registerUrlGenerator for registering the URL provider. This method did a direct instantiation of the Laravel URL generator Illuminate\Routing\UrlGenerator and giving proper parameters in the constructor.
So, I did the same in my service provider. Instead of doing $this->app->bind I did $this->app->singleton('url', function ($app) { ... }) and provided basically the same code in the closure function as in RoutingServiceProvider::registerUrlGenerator but created the instance of my URL generator. This then worked properly, and my generator is now called every time. The final code was this:
// the code is copied from the \Illuminate\Routing\RoutingServiceProvider::registerUrlGenerator() method
$this->app->singleton('url', function ($app) {
/** #var \Illuminate\Foundation\Application $app */
$routes = $app['router']->getRoutes();
$app->instance('routes', $routes);
// *** THIS IS THE MAIN DIFFERENCE ***
$url = new \My\Specific\UrlGenerator(
$routes,
$app->rebinding(
'request',
static function ($app, $request) {
$app['url']->setRequest($request);
}
),
$app['config']['app.asset_url']
);
$url->setSessionResolver(function () {
return $this->app['session'] ?? null;
});
$url->setKeyResolver(function () {
return $this->app->make('config')->get('app.key');
});
$app->rebinding('routes', static function ($app, $routes) {
$app['url']->setRoutes($routes);
});
return $url;
});
I hate copying the code, so it seems to me that the problem is in the base implementation. It should take the correct contract for URL generator instead of making direct instantiation of a base class.
I tried the Kosta's approach but it didn't fully work for me because it somehow created an endless recursion loop in the framework. Nonetheless, I ended up with this code:
namespace App\Providers;
use App\Routing\UrlGenerator;
use Illuminate\Support\ServiceProvider;
class UrlGeneratorServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton("url", function($app) {
$routes = $app['router']->getRoutes();
return new UrlGenerator( // this is actually my class due to the namespace above
$routes, $app->rebinding(
'request', $this->requestRebinder()
), $app['config']['app.asset_url']
);
});
}
protected function requestRebinder()
{
return function ($app, $request) {
$app['url']->setRequest($request);
};
}
}
And of course, registered the above provider in config/app.php under 'providers'
My team likes the idea of constructor-injected dependencies because it makes deps very clear when looking at a class. With the use of the facades, I'm aware they can be mocked and swapped, but one would have to examine every line of a class to figure out what it depends on! I discovered that I could find the true class behind the facade with, for instance, Form::getFacadeRoot().
The controller code that I've ended up with is:
use Illuminate\Html\FormBuilder as Form;
use Illuminate\Validation\Factory as Validator;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage as Session;
use Illuminate\Http\Request as Input;
use Illuminate\Routing\Redirector as Redirect;
use Illuminate\View\Environment as View;
class HomeController extends BaseController {
protected $form;
protected $validator;
protected $session;
protected $input;
protected $redirect;
protected $view;
protected $layout = 'layouts.master';
protected $validationRules = array(
'name' => array('required', 'min:3'),
'email' => array('required', 'regex:/^.+#.+\..{2,4}$/')
);
public function __construct(Form $form, Validator $validator, Session $session,
Input $input, Redirector $redirect, View $view
) {
$this->form = $form;
$this->validator = $validator;
$this->session = $session;
$this->input = $input;
$this->redirect = $redirect;
$this->view = $view;
}
...
}
When my test does $this->client->request('Get', '/');, it errors out:
Illuminate\Container\BindingResolutionException: Unresolvable dependency resolving [Parameter #2 [ <required> $csrfToken ]].
Am I on even close to the right track here? I'm sort of making this up as I go along because I don't see much discussion on this issue. Feel free to comment on my reason for even trying; I could be sold on facades, yet.
Thanks !
You need to map the class dependencies to a class using Laravel's IoC container. This can be done using the App facade. So in your example above with the constructor
public function __construct(Form $form, Validator $validator, Session $session, Input $input, Redirector $redirect, View $view)
You would create a binding that would look something along the lines of:
App::bind('Form', function(){
return new Illuminate\Html\FormBuilder()
});
Taylor Otwell recommends using Interfaces as contracts for the class dependencies. So Ideally your finished code would look something like that below (I've slimed it down a bit for the example).
For your controller:
use Namespace\For\FormInterface;
class HomeController extends BaseController {
public function __construct(FormInterface $form)
{
$this->form = $form;
}
public function myHomePage()
{
$this->form->myFormFunction()
}
}
For the interface:
namespace Namespace\For;
interface FormInterface(){
public function myFormFunction()
}
The class to be injected:
use Namespace\For\FormInterface;
class MyFormClass implements FormInterface{
public function myFormFunction()
{
// Do some stuff here
}
}
And then finally you create the binding that brings it all together:
App::bind('Namespace\For\FormInterface', function()
{
return new MyFormClass();
});
What's happening here is every time Laravel sees an instance of FormInterface type hinted in a controller if creates a new myFormFunction() and passes it in as the param. By using interfaces it gives your class dependencies a contract to follow to ensure that they can be easily swapped without causing errors. So say your team later develops a new and improved form class you would simply update your binding like so:
App::bind('Namespace\For\FormInterface', function()
{
return new MyNewFormClass();
});
I would highly recommend looking into Service Providers as they provide an excellent way to manage packages and IoC bindings. A good article on Service Providers can be found here:
http://fideloper.com/laravel-4-where-to-put-bindings
And you can read more about Laravel's IoC container here:
http://laravel.com/docs/ioc
Furthermore if you can get your hands on a copy of the book From Apprentice to Artisan. Advanced Application Architecture With Laravel 4, by Taylor Otwell I would highly recommend a read. Its easy to follow and really goes into detail about managing dependency injection.
Hope that helps.
I think you're going to sacrifice quite a bit on readability, if you choose this route.
Ultimately, dependency injection is just a pattern to allow for testability. The facades are easily testable without injection, so I don't see much value in doing this...