etelford's avatar

Laravel 5: Service Provider

I had created a server provider in my L5 project that included some HTML macros. With some recent updates, this broke.

What's the recommended method of creating a service provider? Or, is it better to not put HTML and Form macros in a service provider? If not, where would they go?

0 likes
25 replies
etelford's avatar

Looking elsewhere, I see similar questions about macros and service providers being asked recently.

I have this:

<?php namespace App\Providers;

use Log, HTML, Request;
use Illuminate\Support\ServiceProvider;
use Carbon\Carbon;

class HtmlServiceProvider extends ServiceProvider {

    /**
     * Configure the application's logging facilities.
     *
     * @return void
     */
    public function boot()
    {
        HTML::macro('active', function ($route) {
            return strpos(Request::url(), route($route)) !== false ? 'class="active"' : '';
        });
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        //
    }

}

But I get an error when I run php artisan. Even so, I am able to use the macro in my code without errors.

Any ideas?

milon's avatar

I have got binding resolution exception.

bart's avatar

Binding resultion in imho means, that you didn't bind an interface (contract) to a specific implementation like so:

public function register()
 {
  $this->app->bind(
   'App\Repositories\Contracts\MyRepositoryContract',
   'App\Repositories\DbMyRepository'
  );
 }
3 likes
bart's avatar

@Milon521 You're welcome! Btw, you can mark a post as answer, so that others don't need to read the whole thread.

milon's avatar

@bart, I am not the creator of the thread. I can't mark your answer. :(

Devon's avatar

HTML should typically be kept in templates, partials if necessary...

However, you can still use macros and presenters (lesson).

etelford's avatar

Here's the error I get when running any artisan command:

PHP Catchable fatal error: Argument 2 passed to Illuminate\Routing\UrlGenerator::__construct() must be an instance of Illuminate\Http\Request, null given

Again, the macro works and is applying correctly in the browser. The only error I get is when I run artisan.

Devon's avatar

I was getting that error earlier today, though I don't remember what I did to cause it. It was while I was debugging the namespacing issue that has resulted from the refactor Taylor did a few days go. Therefore, I'd say check that you're binding things properly and check that everything is properly namespaced. Unfortunately, I can't be much more help than that.

pmall's avatar

@etelford same problem here. I can run artisan command if I comment custom service providers.

Devon's avatar

Few things...

  • Check that the Kernel is binded using the correct namespace in bootstrap/app.php.
  • After, check that your composer.json app namespace is correct.
  • Finally, run composer dump-autoload.

I'm still trying to remember what I did to get that error, but I remember debugging it and it wasn't instantiating the Request object properly.

etelford's avatar

@Devon: Everything in app.php and composer.son looks fine and matches the current dev version.

Anyone remember how they debugged this?

1 like
Devon's avatar

@etelford, if you already ran composer dump-autoload and it still doesn't work, create a new Laravel project and compare differences.

etelford's avatar

That's a good suggestion. However, I think the difference is going to be my service provider I mention above. If I comment out this service provider all together in my config/app.php then artisan runs without errors.

I'll try to create a project and see if there's a difference somewhere else.

pmall's avatar

In my situation it comes from a service provider that load a file thats contrains a form macro. This one :

Form::macro('select_with_data', function($name, $list = array(), $selected = null, $options = array()){
//...
});

With that code above if I run any artisan command :

PHP Catchable fatal error: Argument 2 passed to Illuminate\Routing\UrlGenerator::__construct() must be an instance of Illuminate\Http\Request, null given, called in /home/pierre/workspace/laravel/rune2/vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php on line 56 and defined in /home/pierre/workspace/laravel/rune2/vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php on line 70

Without it, it works fine. What the hell ?

Devon's avatar

Hmm, just tried it myself and got the same thing. I'm not sure what's causing it but a quick fix for ServiceProviders that do not need ran in the CLI can use:

if ($this->app->runningInConsole()) return false;

For instance, I'd probably use something like this until I figure out the CLI issue:

<?php namespace App\Providers;

use ReflectionClass;
use Illuminate\Support\ServiceProvider;

class MacroServiceProvider extends ServiceProvider {

    /**
     * To keep things organized, we're going to separate each macro
     * into it's own method. We'll then use the Reflection class
     * to find and run through each method to create macros.
     *
     * @return void
     */
    public function boot()
    {
        if ($this->app->runningInConsole()) return;

        $methods = (new ReflectionClass($this))->getMethods();

        foreach ($methods as $method)
        {
            if (substr($method->name, -5) == 'Macro')
                $this->{$method->name}();
        }
    }

    /**
     * Determine if the given route matches the current page.
     *
     * @return void
     */
    public function activeLinkMacro()
    {
        $this->macro('active', function ($route)
        {
            $currentUrl = $this->app['request']->url();

            return strpos($currentUrl, route($route)) !== false ? 'class="active"' : '';
        });
    }

    /**
     * Some other method that will auto-run.
     * 
     * @return void
     */
    public function someOtherMacro()
    {
        //
    }

    /**
     * A little shortcut.
     *
     * @param $name
     * @param callable $callable
     * @return void
     */
    public function macro($name, Callable $callable)
    {
        $this->app['html']->macro($name, $callable);
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        //
    }

}

Devon's avatar

From what I've found, this is caused by the refactor last week which tries to separate concerns and create a separate stack when loading from the CLI rather than HTTP. Basically, the Request object is not instantiated and therefore doesn't exist to be passed to the URLGenerator while trying to resolve it as a dependency.

You can see that this is the case by instantiating it manually in your ServiceProvider's boot() method:

$this->app->instance('request', \Illuminate\Http\Request::capture());

However, I'd stick with using the runningInConsole() conditional for now.

I've submit a pull request that should fix the issue by automatically instantiating the Request object if it's required.

martinbean's avatar

The issue is using a macro that may generate a URL, such as Form::open.

When you run Artisan, a Request instance isn’t instantiated as it’s a CLI environment and not a HTTP request. Therefore, when the form helper comes to generate any URLs, it doesn’t have the Request object available.

The only option at this point is to re-write your helpers/service provider to use a helper that tries to generate a URL.

vinnizworld's avatar

@edubuc In my case also it was because of using url() in services.php

I fixed it by generating base URL on top of file by checking the application type.

$base_url = $app->runningInConsole() ? config('app.url') : url('/');

$app is a global variable so we can use this snippet wherever we want to generate URLs, especially in config files.

1 like
powerupneo's avatar

having url() function in any of the config files creates this problem. I simply moved my url() in my config files to .env files and the error is gone.

2 likes

Please or to participate in this conversation.