IoC resolving issue after upgrading to 5.8 - laravel

Hoping someone can help me,
I am updating from version 5.7 to 5.8 and I am now having issues with our repository setup.
I am using the repository pattern, for each repository we have a contract. I have a service provider (this is setup to be deferred) to bind the contracts to the repositories so when type hinted the interfaces / contracts in the controllers the repository is injected in.
We also have a service provider (also setup to be deferred) that when the repository is resolved it gets some config data from a config file and calls methods on the repository to setup attributes.
This all worked great on 5.7 but after upgrading to 5.8 it doesn't. If I switch the controllers to type hint the repository instead of the contract it works, but obviously if / when we change the repository we would have to amend all places this is referenced, completely against purpose of coding to an interface and injecting this.
Hopefully I've explained that well enough!
Has anyone come up against this or similar? I've checked the upgrade notes and 5.8 no longer uses the defer property as it implements the interface which I am doing but I can't see anything else and I am struggling to debug it any further.
https://laravel.com/docs/5.8/upgrade#deferred-service-providers
Any help any one can offer would be great.
If I switch back to 5.7 there is no issue, or if use the actual repository and typehint this in the controller there is no issue, it is happening only when using the contract / interface
Here is the service provider for the Repository to set the properties of it
<?php
namespace App\Providers\Repositories\Erp;
use App\Models\ERP_OLTP\WorkOrderStatus as WorkOrderStatusModel;
use App\Repositories\Erp\EloquentMaterialRequirements;
use Illuminate\Contracts\Config\Repository as ConfigRepositoryInterface;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
class MaterialRequirementsServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function register()
{
$this->app->resolving(
EloquentMaterialRequirements::class,
function (EloquentMaterialRequirements $repo, $app) {
$config = $app[ConfigRepositoryInterface::class]->get('materialRequirements');
$statusIds = array_reduce(
$config['workOrderStatuses'],
function ($acc, $statusCode) {
$acc[] = WorkOrderStatusModel::STATUS[$statusCode];
return $acc;
},
[]
);
$repo->setDemandStatusIds($statusIds);
$repo->setDemandWeekCount($config['weekCount']);
$repo->setAverageUsageWeekCount($config['averageWeeklyUsageMaxAgeWeeks']);
}
);
}
public function provides()
{
return [
EloquentMaterialRequirements::class,
];
}
}
Here is the service provider for repositories to be binded to contracts
<?php
namespace App\Providers;
use Illuminate\Contracts\Config\Repository as ConfigRepositoryInterface;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
class ErpRepositoriesServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function register()
{
foreach ($this->getBindings() as $contract => $implementation) {
$this->app->bind($contract, $implementation);
}
}
public function provides()
{
$services = [];
foreach (array_keys($this->getBindings()) as $contract) {
$services[] = $contract;
}
return $services;
}
private function getBindings(): array
{
return $this->app[ConfigRepositoryInterface::class]->get('repositories.bindings');
}
}
here is the config.repositories, i've removed other repositories to minify it
<?php
use App\Contracts\Erp\Repositories as Contracts;
use App\Managers\Erp\Repositories as Managers;
use App\Repositories\Erp as Repositories;
return [
'bindings' => [
Contracts\MaterialRequirements::class => Repositories\EloquentMaterialRequirements::class,
],
];
It simply never calls the MaterialRequirementsServiceProvider or if it does it isn't resolving the EloquentMaterialRequirements::class and therefore setting properties. I get no errors, no exceptions, nothing to go off

Related

Laravel Package: How to register a third party facade inside a custom package?

I'm creating a package that uses internally this hashid package.
How can I register a third party facade inside a custom package?
I tried three options and none of them worked.
Version - Composer
"aliases": {
"Hashids": "Vinkla\\Hashids\\Facades\\Hashids"
}
Version - inside my ServiceProvider with alias
class MyPackageServiceProvider extends ServiceProvider
{
public function register()
{
...
$this->app->alias(\Vinkla\Hashids\Facades\Hashids::class, 'Hashids');
}
Version - inside my ServiceProvider with AliasLoader
class MyPackageServiceProvider extends ServiceProvider
{
public function register()
{
...
$loader = \Illuminate\Foundation\AliasLoader::getInstance();
$loader->alias('Hashids', \Vinkla\Hashids\Facades\Hashids::class);
}
When I'm testing the code, I get the error:
Error: Call to undefined method Vinkla\Hashids\Facades\Hashids::encode()
inside
/** #test */
public something_to_test()
{
dd(\Hashids::encode(1));
}
Ok I found one solution, but I am still confused why it is like that.
In my "MyPackageServiceProvider" I need to add:
$this->app->register(HashidsServiceProvider::class);
Why do I need to register a Provider? I thought the composer is handling the work.
At the end only this works:
$this->app->register(HashidsServiceProvider::class);
$loader = \Illuminate\Foundation\AliasLoader::getInstance();
$loader->alias('Hashids', \Vinkla\Hashids\Facades\Hashids::class);
The other versions doesn't work as well :(

Does defaults() method exist in Password?

i made a service provider to validate the password in Laravel 8, but i get this error:
Call to undefined method Illuminate\Validation\Rules\Password::defaults()
PasswordRuleServiceProvider was added in app.php
// Custom Service Providers
App\Providers\PasswordRuleServiceProvider::class,
This is the provider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
class PasswordRuleServiceProvider extends ServiceProvider
{
public function register()
{
//
}
public function boot()
{
Password::defaults(function () {
$rule = Password::min(8)->letters()->mixedCase()->numbers()->symbols()->uncompromised(3);
return $rule;
});
}
}
I followed the Laravel doc to do this but it doesn't work, does the defaults method exist in Password? Thanks.
As you can see in the commit history of that Password class, this method did not exist until Laravel v8.42.0 (released on May 18th 2021). Make sure that you are using at least that version if you want to call such a method.
Next time, you should check the source code in your project for such methods, and not any other version on the internet. The source code you've linked might be newer than that

Why won't this Laravel 5.4 service provider register?

I am trying to do a hello world service provider with the new Laravel 5.4.
I have created the following service provider file:
//File: app/TestProvider/TestServiceProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind('Test', function ($app) {
return new Test();
});
}
}
I have created a simple class under the same namespace:
//File: app/TestProvider/Test.php
namespace App\TestProvider;
class Test
{
/**
* Register bindings in the container.
*
* #return void
*/
public function helloWorld()
{
echo "hello world";
}
}
The problem is, this is not registering. The register method is executing as when I put a breaker before the 'bind' method, it executes:
public function register()
{
dd("BREAKER");
$this->app->bind('Test', function ($app) {
return new Test();
});
}
So this outputs "BREAKER" as expected. However if I put the breaker in the closure, nothing happens which suggests for some reason, that 'bind' method isn't being executed??
Any ideas?
EDIT:
Just some further info: I know that the Test class is registered and in the correct namespace as I can do:
dd(new Test());
in the registration method, and it outputs the resource id as expected.
Explanation
The closure provided only runs when the binding is being resolved. That's why it's a closure, it can be saved in the service container and resolved at any time while the program runs.
Solution
To see the resolved binding, create a controller and resolve the class in that controller:
// File: app/Http/Controllers/TestController.php
namespace App\Http\Controllers;
// This isn't the best way, but it works. See the best way below
class TestController extends Controller {
public function index()
{
return \App::make('Test')->helloWorld();
}
}
Of course, don't forget to register the route:
// File: routes/web.php
Route::get('/', 'TestController#index');
The binding will resolve when you hit the homepage.
However, as I said, it's not the best way, so here I prepared a better way. Change the way you register the binding:
// File: app/Providers/TestProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
use App\TestProvider\Test;
// Better way
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
// Note: we bind the exact complete class name!
$this->app->bind(Test::class, function ($app) {
return new Test();
});
}
}
After this change the controller so that it looks like this:
namespace App\Http\Controllers;
use App\TestProvider\Test;
class TestController extends Controller {
/**
* #var Test $test
*/
private $test;
// Let Laravel resolve the dependency on constructing the class
public function __construct(Test $test)
{
$this->test = $test;
}
public function index()
{
return $this->test->helloWorld();
}
}
You will see that the exact same thing happens, but it looks more elegant and avoids conflicts.
Details
Laravel gives only a high level overview of the service container, which doesn't help to learn how it works on the inside. The best way to see that is to go down the call stack.
When you do that, you find that Laravel registers every class in the project in the service container. That means that whether you create a service provider or not, the class will be in the container. How exactly?
When you run php artisan optimize, Laravel creates files that have array with all the classes of the project. When you run the app, after registering everything from the service providers, Laravel registers the rest of the classes from that file.
That means that in your case, if you don't specifically register the Test class, it will still be resolvable. Basically, you only need to register classes that need some specific instructions to be resolved.
So how does Laravel resolve the dependencies?
When you run \App::make(Test::class) or inject dependency via type hinting in the constructor (the "better way" from my solution), Laravel looks for that dependency among the bindings.
When it finds the dependency, it resolves either the closure associated to it or the constructor of the class directly.
When it resolves the constructor directly, it looks for type hints among the constructor parameters and recursively resolves all of them until there's nothing else to resolve.
After that it returns the resolved class.
Of course, bear in mind that for Laravel to analyze the contructor of a class, it needs to be resolved via the service container in the first place. You can't just call $test = new Test(); and expect Laravel to do all the magic :)
Conclusion
This is a rather quick overview of Laravel's service container. The best way for you to learn it is, of course, studying the sources for yourself. It's truly elegant and it uses PHP's functionality to the fullest.
I really hope this shed some light on the service container for you and can help you in the future :)
The closure passed to the bind() method is not executed until you actually attempt to resolve the alias you are binding.
So, if you dd('breaker') inside the closure, this won't actually get executed until Test is resolved (whatever your preferred resolution method is):
Service provider:
// bind the closure to the 'Test' alias
public function register()
{
$this->app->bind('Test', function ($app) {
dd("BREAKER");
return new Test();
});
}
Code that resolve Test alias:
// different ways of resolving the alias out of the container.
// any of these will execute the bound closure.
$test = resolve('Test');
$test = app('Test');
$test = app()->make('Test');
$test = \App::make('Test');
try:
$this->app->bind(Test::class, function ($app) {
return new Test();
});

Injection in Repository

I found many questions about my problem and tried (I think) all the solutions, but I can not make it work. I'm overlooking something very easy, probably.
I'm using Laravel 5. I try to implement a repository-pattern.
I have an Eloquent model '\Notos\Kind'.
Then I have this interface:
namespace Notos\Contracts;
interface Kind
{
public function findByName($name);
}
My Repository looks like this:
namespace Notos\Repository;
use Illuminate\Database\Eloquent\Model;
use Notos\Contracts\Kind as KindContract;
class Kind implements KindContract
{
protected $kind;
public function __construct(Model $kind)
{
$this->kind = $kind;
}
public function findByName($name)
{
return $this->kind->firstOrCreate(array('name' => $name));
}
}
My ServiceProvider:
namespace Notos\Providers;
use Illuminate\Support\ServiceProvider;
use Notos\Kind;
use Notos\Repository\Kind as KindRepository;
class RepoServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app->bind('Notos\Contracts\Kind', function ($app) {
return new KindRepository(new Kind);
});
}
}
And my controller that uses the repository:
namespace Notos\Http\Controllers;
use Notos\Repository\Kind as KindRepository;
class KindController extends Controller
{
protected $kind;
public function __construct(KindRepository $kind)
{
$this->kind = $kind;
}
public function find($name)
{
return $this->kind->findByName($name);
}
}
This provider is registered in config/app.php
When I try to execute the KindController#find I get this error:
BindingResolutionException in Container.php line 785:
Target [Illuminate\Database\Eloquent\Model] is not instantiable.
I can't find what I'm doing wrong.
It works perfect if I change __construct(Model $kind) to __construct(Kind $kind).
Any ideas?
Thanks
The first thing, I would advice you to add to your class names the function so for example instead of Kind for repository use KindRepository, the same for contract. Otherwise you will have 3 kinds classes (of course in different namespaces) but it will be hard to analyse the code.
The problem here is that in your KindController you try to inject KindRepository directly but in constructor you use Model. It won't work because Model is only abstract class. What you should do to make it work?
In the code:
$this->app->bind('Notos\Contracts\Kind', function ($app) {
return new KindRepository(new Kind);
});
you told Laravel that when you will use Notos\Contracts\Kind it should create KindRepository for you with Kind in constructor - look you told here about Contract, not about repository itself.
So to make it work, you should use in your controller not your repository, but your contract.
Instead of line:
use Notos\Repository\Kind as KindRepository;
you should write:
use Notos\Contracts\Kind as KindRepository;
and it should work now.

Laravel 4 Aliases in custom classes

i want to use the alias classes on laravel 4 "facades" like App::method , Config::method.
Well the thing is that i create a custom class and i have to import the namespaces like
<?php
namespace Face\SocialHandlers;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
class FacebookHandler implements SocialHandlerInterface {
public function registrar($perfil) {
Config::get('facebook');
}
}
is there any way to use those classes like in controllers or routes files of the framework ?
like
<?php
namespace Face\SocialHandlers;
//use Illuminate\Support\Facades\App;
//use Illuminate\Support\Facades\Config;
class FacebookHandler implements SocialHandlerInterface {
public function registrar($perfil) {
Config::get('facebook');
}
}
Cya
ps: sry for my english
You can use use Config; instead of the more verbose use Illuminate\Support\Facades\Config; and the autoloader should handle it correctly.
Just as a tip, you shouldn't hardcode dependencies in your code. Instead of using the facades, you could create an "ConfigInterface" to get the common dependencies you need. Then create a "LaravelConfig class" (Or Laravel4Config.php) and implement those methods.
For a Quick Fix Answer, "catch the underliying facade instance":
namespace Face\SocialHandlers;
//use Illuminate\Support\Facades\App;
//use Illuminate\Support\Facades\Config;
class FacebookHandler implements SocialHandlerInterface {
protected $config;
protected $app;
public function __construct()
{
$this->config = \Illuminate\Support\Facades\Config::getFacadeRoot();
$this->app = \Illuminate\Support\Facades\App::getFacadeRoot();
}
public function registrar($perfil) {
$this->config->get('facebook');
}
}
For a Real Answer, maybe tedious, but good in the long run, instead of using the facades use an interface.
interface SocialConfigInterface
{
public function getConfigurationByKey($key)
}
Then
class Laravel4Config implements SocialConfigInterface
{
protected $config;
public function __construct()
{
$this->config = \Illuminate\Support\Facades\Config::getFacadeRoot(); //<-- hard coded, but as expected since it's a class to be used with Laravel 4
}
public function getConfigurationByKey($key)
{
return $this->config->get($key);
}
}
And Your Code
namespace Face\SocialHandlers;
//use Illuminate\Support\Facades\App;
//use Illuminate\Support\Facades\Config;
class FacebookHandler implements SocialHandlerInterface {
protected $config;
public function __construct(SocialConfigInterface $config)
{
$this->config = $config;
}
public function registrar($perfil) {
$this->config->get('facebook');
}
}
This way, if you want to change between frameworks you just need to create a SocialConfigInterface Implementation, or imagine the scenario where Laravel 5 wont use Facades, you want your code to be independent of "outsider changes" this is inversion of control IoC
First run,
php artisan dump-autoload
This will add your class namespace to vendor/composer/autoload_classmap.php. Now locate the entry for your class in this classmap array and get the proper namespace from there.
For example, you will get something like,
'Illuminate\\Support\\Facades\\App' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/App.php',
For this particular entry you have an alias in app/config/app.php
'App' => 'Illuminate\Support\Facades\App',
At the same way, locate your entry and use an alias in app/config/app.php.
You just have to create a folder, or place a class wherever already is listed for autoload. Me, for exemple, have this class PDFMaker, that uses a DomPDF Laravel implementation. I created a folder named libraries and put the path to it (under the app folder) in the autoload:classmap key on composer.json
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/libraries",
"app/models",
"app/helpers",
"app/database/migrations"
]
I did the same with commands for artisan commands! When you do that, you only have to declare a new object for any class under that folder, or call it in a static way, if the class has defined static methods. Something like Class::method.
Hope it helps you, have a nice day! :D
EDIT: After that, don't forget the dump-autoload for placing the new class in autoload scope.
EDIT 2: Remember that once you've put the class on autoload, it will be in same scope the others, so you won't have to import it to other, neither others to it!
You can also prefix the class names with a backslash, to use the global namespace: \Config::get('facebook') and \App::someMethod() will work without the need to add a use statement, regardless of the file's namespace.

Resources