Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

realnsleo's avatar

How to use a non-laravel PHP package in a Laravel Application

Hello, Please forgive me as I will be a bit particular here. I am looking to integrate a third party package into my Laravel application. From their documentation, this is how it is initialized:

require __DIR__ . '/vendor/autoload.php';

use InvoiceNinja\Config as NinjaConfig;
use InvoiceNinja\Models\Client;

NinjaConfig::setURL('https://ninja.dev/api/v1');
NinjaConfig::setToken('My Token');

And this is how it is called, for example to retrieve a list of all Clients from the Invoice Ninja API:

$clients = Client::all();

When I use this in all my controllers, everything works. However, is there a recommended way of calling the API by only having the NinjaConfig set once in my whole application? That way I do not have to call NinjaConfig wherever I need it in my controllers. I have tried using a ServiceProvider like this:

public function register()
{
    $this->app->singleton('invoice-ninja', function ($app) {
        NinjaConfig::setURL('https://ninja.dev/api/v1');
        NinjaConfig::setToken('My Token');
    });
}

And then in my controller:

public function index()
{
    return view('home')->with([
        'clients' => \InvoiceNinja\Models\Client::all()
    ]);
}

But it does not seem to work. I would appreciate any guidance or assistance in figuring out a good way to achieve this. Thank you.

0 likes
3 replies
rodrigo.pedra's avatar
Level 56

You are registering a singleton into the service container, but you never get it out from the service container. So its registration closure is never called.

I took a look on their SDK at:

https://github.com/invoiceninja/sdk-php

And they use a lot of static calls with no dependency injection. So it won't be feasible to call it on demand. One could wrap all of their models around a class that gets added to service container, but that would be a lot of work for little benefit.

One very easy solution is to:

  1. Drop the singleton registration you sent on your code sample
  2. Move the code inside the closure to you AppServiceProvider's boot method:
class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        NinjaConfig::setURL('https://ninja.dev/api/v1');
        // I would read the token from a config file here
        NinjaConfig::setToken('My Token');
    }
} 

These lines would be called for every request, but from their config class it is very lightweight (just saving to a class static variable) so the difference in performance should not be noticeable.

2 likes
realnsleo's avatar

Hello @rodrigo.pedra, thank you so much for your swift response and for your answer. I added both lines to the AppServiceProvider class and now my controllers do not have to call the NinjaConfig all the time. It works great!

I was intrigued by your comment that said: "One could wrap all of their models around a class that gets added to service container". I do want to learn more about Service Containers. Would you mind explaining briefly how I would go about this? I would like to try and implement that too for the benefit of seeing how it would work.

Thanks a lot once again.

2 likes
rodrigo.pedra's avatar

Hi @realnsleo ,

First you're welcome, glad it worked out.

The big picture would be to create a service class (don't mind the "service" qualifier, it would be just a regular class) that decorates the calls to this third-party API.

Then you would register this service class into the container, calling the config before returning a new instance of it, and from this service class you would delegate the calls to the InvoiceNinja SDK classes.

This way every time you request this class from the container, you are sure the config would be run before calling it, and by consequence before any InvoiceNinja class is called.

Something like this:

First a Factory class

<?php

namespace App\Services;

use InvoiceNinja\Config;

class NinjaInvoiceService
{
    public function __construct($url, $token)
    {
        Config::setUrl($url);
        Config::setToken($token);
    }

    public function model($modelClass)
    {
        return new NinjaInvoiceModelDecorator($modelClass);
    }
}

Then a Decorator class:

<?php

namespace App\Services;

use InvoiceNinja\Models\AbstractModel;

class NinjaInvoiceModelDecorator
{
    private $modelClass;

    public function __construct($modelClass)
    {
        if (! \is_subclass_of($modelClass, AbstractModel::class)) {
            throw new \InvalidArgumentException('Model should be a InvoiceNinja model');
        }

        $this->modelClass = $modelClass;
    }

    /**
     * This would create a new instance for when you need
     * to call instance methods
     *
     * @param  mixed  ...$params
     * @return \InvoiceNinja\Models\AbstractModel
     */
    public function newInstance(...$params)
    {
        $modelClass = $this->modelClass;

        return new $modelClass(...$params);
    }

    /**
     * @return array
     */
    public function all()
    {
        return forward_static_call([$this->modelClass, 'all']);
    }

    /**
     * @param $id
     * @return \InvoiceNinja\Models\AbstractModel
     */
    public function find($id)
    {
        return forward_static_call([$this->modelClass, 'find'], $id);
    }

    // ... add every other public static methods 
    // from \InvoiceNinja\Models\AbstractModel
}

In your AppServiceProvider you would register the Factory class as a singleton:

<?php

namespace App\Providers;

use App\Services\NinjaInvoiceService;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(NinjaInvoiceService::class, function () {
            // get this values from a config file
            return new NinjaInvoiceService(
                'https://ninja.dev/api/v1',
                'My Token'
            );
        });
    }

    public function boot()
    {
        //
    }
}

Lastly, in a controller you could inject this service class that would be resolved from the container:

public function index(NinjaInvoiceService $service)
{
    return view('home')->with([
        'clients' => $service->model(\InvoiceNinja\Models\Client::class)->all(),
    ]);
}

As the NinjaInvoiceService class is resolved from the container instead of using the new operator, the config is called before calling any model class.

This is a just proof-of-concept, might present some rough edges, and you would need to keep your service classes in sync with the InvoiceNinja SDK, so not much benefit on doing that other than having the config ran before.

On the other hand, one pro of using a decorator is actually decorating method calls instead of just forwarding it. For example you could add a log entry before every call forwarded to the underlying model.

As an example I hope it makes things clearer.

1 like

Please or to participate in this conversation.