Laravel Singleton array not working, but works when using closure - php

In my AppServiceProvider.php I tried binding a singleton interface to a concrete class using the array syntax:
class AppServiceProvider extends ServiceProvider
{
public $singletons = [
MyClassInterface::class => MyClass::class,
];
}
The interface is then typehinted in a class.
This gives an error like:
TypeError: TheClassThatUsesTheService::__construct(): Argument #2 ($myClass) must be of type MyClassInterface, string given
The errors disspears if I make a manual singleton binding using a closure:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(MyClassInterface::class, function () {
return new MyClass;
});
}
}
Should this not behave the same?
MyClass.php:
<?php
class MyClass extends Base implements MyClassInterface
{
public function get(): mixed
{
//
}
public function insert(): void
{
//
}
}
MyClassInterface.php:
<?php
interface MyClassInterface
{
public function get(): mixed;
public function insert(): void;
}
Base.php:
class Base
{
protected function service()
{
//
}
}

Related

Symfony how autowire a class specifique?

How can we have AutoWire active on calls in the 2nd child of the controller.
Here are different examples to explain my problem.
First exemple :
#Route("/user")
class UserControler extends AbstractController {
#Route("/", name="user_index", methods={"GET"})
public function index(UserRepository $userRepository) : Response {
if(!$userRepository instanceof UserRepository){
throw new Exception('is not instance of UserRepository');}
}
return new JsonResponse(['succes' => "test"]);
}
it's ok, i don't have problem here, the autowire has instantiate UserRepository
In this example, the Test1 class will be instantiated in the user controller, it takes a UserRepository in the contructor the auto wire will provide it
class Test1 {
public function __construct (UserRepository $userRepository) {
if(!$userRepository instanceof UserRepository) {
throw new Exception('is not instance of UserRepository');
}
}
}
-
class UserControler extends AbstractController {
#Route("/", name="user_index", methods={"GET"})
public function index(Test1 $test1 ) : Response {
return new JsonResponse(['succes' => "test"]);
}
None error trow, i have a instance of UserRepository, i can used it.
Now i want UserControler instanciate class Test1 and call method try().
The method try() instantiate the classe Test2 who need UserRepository in construtor.
class Test1 {
public function try() {
new Test2();
}
}
class Test2 {
public function __construct (UserRepository $userRepository) {
if(!$userRepository instanceof UserRepository) {
throw new Exception('is not instance of UserRepository');
}
}
}
-
class UserControler extends AbstractController {
#Route("/", name="user_index", methods={"GET"})
public function index(Test1 $test1 ) : Response {
return new JsonResponse(['succes' => "test"]);
}
Autowire not working here, is possible ?
In the Test1::try() method, you create an instance of the Test2 class manually, so automatic dependency injection does not work. In this case, you need to manually pass arguments when creating the class.
For example like this
class Test1 {
private UserRepository $userRepository;
public function __construct (UserRepository $userRepository) {
$this->userRepository = $userRepository;
}
public function try() {
new Test2($this->userRepository);
}
}
Or you can make automatic dependency injection work:
class Test1 {
public function __construct (Test2 $test2) {
// do something with Test2
}
}

Dependency Injection

I have this code
Controller
<?php
namespace App\Exchange\Helpers;
use App\Contracts\Exchange\Notification;
class Locker
{
protected $notification;
public function __construct(Notification $notification)
{
$this->notification = $notification;
}
public function index()
{
return $this->notification->sendMessage('test');
}
Interface
<?php
namespace App\Contracts\Exchange;
interface Notification
{
public function sendMessage($message);
}
File Kernel.php
namespace App\Providers;
use App\Contracts\Exchange\Notification;
use App\Exchange\Helpers\Notification\Telegram;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(Notification::class, function (){
return new Telegram(env('TELEGRAM_EXCHANGE_TOKEN'), env('TELEGRAM_EXCHANGE_CHAT_ID'));
});
}
If I try to use new Locker(); I get a TypeError error: Too few arguments to function App\Exchange\Helpers\Locker::__construct(), 0 passed in Psy Shell code on line 1 and exactly 1 expected
Your controller should extend Illuminate\Routing\Controller in order for dependency injection to work. Or just refactor your __construct method using app helper:
<?php
namespace App\Exchange\Helpers;
use App\Contracts\Exchange\Notification;
class Locker
{
protected $notification;
public function __construct()
{
$this->notification = app(Notification::class);
}
public function index()
{
return $this->notification->sendMessage('test');
}
}

May I use properties from a parent class in a trait?

Is it OK to use properties/methods from parent classes in trait methods?
This code works, but is it good practice?
class Child extends Base{
use ExampleTrait;
public function __construct(){
parent::__construct();
}
public function someMethod(){
traitMethod();
}
}
trait ExampleTrait{
protected function traitMethod(){
// Uses $this->model from Base class
$this->model->doSomething();
}
}
I don't think it's good practice.
Instead you could have a method to fetch your model object, and have that method as an abstract signature in you trait:
trait ExampleTrait {
abstract protected function _getModel();
protected function traitMethod() {
$this->_getModel()->doSomething();
}
}
class Base {
protected $_model;
protected function _getModel() {
return $this->_model;
}
}
class Child extends Base {
use ExampleTrait;
public function someMethod() {
$this->traitMethod();
}
}
Or pass your model as a parameter to your trait method:
trait ExampleTrait {
protected function traitMethod($model) {
$model->doSomething();
}
}
class Base {
protected $_model;
}
class Child extends Base {
use ExampleTrait;
public function someMethod() {
$this->traitMethod($this->_model);
}
}
Both of these approaches let you utilize your IDE's type hinting.

Laravel service container is it possible to bind to class AND all it's ancestors - like in Symfony

I have base class like this:
class BaseAPI
{
public function __construct(Client $client, CacheInterface $cache, $apiBaseUri)
{
$this->client = $client;
$this->apiBaseUri = $apiBaseUri;
$this->cache = $cache;
}
}
And then in provider:
class APIServiceProvider extends ServiceProvider
{
public function register()
{
$this->app
->when(BaseAPI::class)
->needs('$apiBaseUri')
->give(env('DEFAULT_API_URI',''));
}
}
That is working.
But when I'm making ancestor:
class GetLocations extends BaseAPI
{
protected $apiMethodName = 'locations';
protected $type = 'get';
}
I'm getting error. Of course I can manually write code for each of ancestors, but there is a lot's of them, so question is: is there any inheritance mechanism for binding?
Something like this:
https://symfony.com/doc/current/service_container/parent_services.html
Did you try to use interface for this?
class BaseAPI implements APIInterface
{
}
class APIServiceProvider extends ServiceProvider
{
public function register()
{
$this->app
->when(APIInterface::class)
->needs('$apiBaseUri')
->give(env('DEFAULT_API_URI',''));
}
}

PHP/Laravel - Can't initiate extend of abstract class

I'm quite new to using abstract classes and interfaces in PHP.
I'm trying to initiate a extend of an abstract class, but it won't work. It might be a Laravel specific issue i'm having.
This is the case:
I have an interface
I have an abstract class that implements the interface
I have 'regular' class that extends the abstract class
I try to implement the class
This is the interface:
<?php namespace Collection\Services\Validation;
interface SomeInterface {
public function with(array $input);
public function passes();
public function errors();
}
This is the abstract class:
<?php namespace Collection\Services\Validation;
use Illuminate\Validation\Factory;
abstract class SomeClass implements SomeInterface {
protected $validator;
protected $data = array();
protected $errors = array();
protected $rules = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
public function with(array $data)
{
$this->data = $data;
return $this;
}
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules);
if( $validator->fails() )
{
$this->errors = $validator->messages();
return false;
}
return true;
}
public function errors()
{
return $this->errors;
}
}
This is the "regular" class:
<?php namespace Collection\Services\Validation;
class SomeClassExtender extends SomeClass {
public function sayBye()
{
return 'bye';
}
}
This is the implementation:
<?php
use Collection\Services\Validation\PageFormValidator;
use Collection\Services\Validation\SomeClassExtender;
class PagesController extends BaseController {
protected $someClass;
public function __construct(SomeClassExtender $class)
{
$this->someClass = $class;
}
And then i get this error:
Illuminate \ Container \ BindingResolutionException
Target [Symfony\Component\Translation\TranslatorInterface] is not instantiable.
If i remove the initiation of the Factory class, the error is gone. The Factory class is also just a regular class.
What am i doing wrong here?
I see that you're following Chris Fidao's book. Got the same error as you are.
This is my solution, put this inside global.php
App::bind('Symfony\Component\Translation\TranslatorInterface', function($app) {
return $app['translator'];
});
EDIT:
I think the problem with Factory is that you need to bind the translator interface to $app['translator']. Here's what I found...
If you look at the Factory class, it requires the translator interface -- A quick look into its public __construct in the API:
public function __construct(TranslatorInterface $translator, Container $container = null)
{
$this->container = $container;
$this->translator = $translator;
}
Then if you look at the public function register() in ValidationServiceProvider, you'll find that Laravel binds the TranslatorInterface to $app['translator']:
$validator = new Factory($app['translator'], $app);
Then seems like a service provider to bind $app['translator'] is needed, or we can just bind it in global.php.
I think this is the best working solution, found the same exact problem . Solved it by,
injecting the already bound "validator" object in the Validator facade.
<?php namespace Illuminate\Support\Facades;
/**
* #see \Illuminate\Validation\Factory
*/
class Validator extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'validator'; }
}
Instantiate the Factory class with App::make('validator')
Do it this way,when instantiating your SomeClassExtender class.
$someClassExtender = new SomeClassExtender( App::make('validator') );
This article by #PhilipBrown Advanced Validation as a Service for Laravel 4 - http://culttt.com/2014/01/13/advanced-validation-service-laravel-4/

Categories