Accessing setting from a model.. container is unavailable - php

Is there a way to access the container and thus the $app array of settings from a Slim Model file? It appears that this is ok for controllers but when I try to access $this->container always comes back null.

You can't. All that is not instantiated by the app itself won't be injected with dependencies/have access to the container.
However, there are ways to solve the problem you are facing.
Using a parameter in the model constructor
You could simply add a parameter in your constructor that takes the settings array:
function __construct(array $settings){...}
and then pass it when instantiating from a controller.
Pay attention to the fact that a container is not a service locator (see part 1.3 of the PSR), so you should not pass the container directly.
Using a factory that contains the array of settings
You could create a Factory that takes the array of settings as constructor, then store an instance in your container and add it as property of your class, this is an extension to the first answer that will save you time by not having to pass the settings as argument ever time.
// The factory
class MyModelFactory{
private array $settings;
function __construct(array $settings){
$this->settings = $settings;
}
public function makeInstance(): MyModel
{
return new MyModel($this->settings);
}
}
// In the controller
class Controller{
private MyModelFactory $myModelFactory;
...
// In your route method
$myModelInstance = $this->myModelFactory->makeInstance();
...
}
I have not included how to put the Factory inside the container and how to populate the controller class with it since this depends on the way you setup your application, but it's the easy part of the whole process :)

Related

Why I can't access to the paginator DTO in EasyAdmin?

I'm trying to access the EasyCorp\Bundle\EasyAdminBundle\Dto\PaginatorDtoin my Crud Controller :
public function __construct(
private EntityManagerInterface $manager,
private EntityRepository $entityRepository,
private PaginatorDto $paginatorDto,
) {
}
But I've got this error => Cannot autowire service "App\Controller\Activity\ActivityCrudController": argument "$paginatorDto" of method "__construct()" references class "EasyCorp\Bundle\EasyAdminBundle\Dto\PaginatorDto" but no such service exists. and I don't understand why and How to fix it :(
Any idea ?
I'm not an expert of that bundle so take my answer with a pinch of salt but looking at bundle's code I've noticed PaginatorDto not to be a service (as the name suggests).
As that DTO is not a service (and it's ok it is not), you can't autowire it nor make it a service "locally" (eg.: in your application).
So, in order to retrieve the DTO object, inject AdminContextProvider (that is a service as you can notice here) instead and use it to get the DTO
$adminContext->getCrud()->getPaginator();
Your crud controller should extend AbstractCrudController which give you access to the current admin context.
So if you want to use it in one of your crud controller method you should be able to access the paginator with:
$paginator = $this->getContext()->getCrud()->getPaginator();
If you want to do the same outside your crud controller, let's say in another service. You need to inject the AdminContextProvider to first get the AdminContext and do it the same way.
private ?AdminContext $siteRepository;
public function __construct(AdminContextProvider $adminContextProvider)
{
$this->adminContext = $adminContextProvider->getContext();
}

Laravel - How to maintain a object in different controllers?

I am new to the Laravel framework, and hopefully, someone can save my life!
So I am making a WEB application demo by using Laravel, and I use Vus as the front-end. I send the request from Vue to Laravel's controller, but I am wondering if I can maintain the object inside the controller when I finish one HTTP request, then I can still use the object's property data in another request (the request could be in the same controller or another controller)?
Especially the object I want to maintain has some properties like token and expired time, and a function inside to check if the token is expired or not. So I would like to keep these properties inside the object and just store them in the memory.
class OneController extends Controller
{
private object $object;
public function __consturct()
{
// Do something to $this->object
}
}
class AnotherController extends Controller
{
private object $object;
public function __consturct()
{
// Do something to $this->object
}
}
I don't know if I describe my problem clearly, so don't hesitate to tell me if you want more context!
Thank you, World!

Laravel: Using mock instance in Customised Route::model binding

Route model binding in Laravel, helps bind an instance to the service container, to be used in a controller action. And the beautiful thing is that when I want to write tests, I can bind a mock instance to the application, which replaces what is bound to the container. - App::instance($className, $mockInstance);
I have made a request to the route (Route::get("/{class_name}")). I want to return an instance of the class, depending on what the value of the route parameter class_name is.
If the value is two, I want an instance of the Two class, likewise for the One class. Notice that both classes inherit from AbstractClass, so the returned instance is of type AbstractClass, (PHP7).
Problem: When I make visit the route, I get the correct instance. But when I want to use the mock instance, it overrides the mock instance and creates/returns the main instance. I need to use the mock instance so I can set expectations on it.
I've added some code, which I hope illustrates what I mean.
abstract class AbstractClass {
// abstract parent class
}
class One extends AbstractClass {
// single child class
}
class Two extends AbstractClass {
// another single child class
}
// I put this inside the `bind` method of the RouteServiceProvider
Route::bind("class_name", function (string $class_name) : AbstractClass {
// I wish to return an instance to the route, based on the string that was passed - "one" or "two"
$class = ucfirst($class_name);
return new $class();
});
// test
public function testSomething () {
$mockInstance = Mockery::mock(AbstractClass::class);
App::instance(AbstractClass::class, $mockInstance);
$response = $this->get("/one");
}
// controller method
public function action (AbstractClass $instance) {
// I want to see/use the mock instance here, for testing
// and the main instance otherwise.
}
I am using PHP7.0, with Laravel5.4 for development
Reason:
Well I guess you're confusing Router bindings vs Container Bindings. These two are completely separate in nature.
Answer:
Just update the router bindings when you're writing tests.
$mock = Mockery::mock(AbstractClass::class);
$this->app->get('router')->bind('class', function () use ($mock) {
return $mock;
});
$response = $this->get("/test");
Router Bindings:
These bindings are only resolved when routing is involved, and it cannot be resolved when you're using app() instance.
Container Bindings:
Container bindings are separate and it doesn't know about the router bindings at all.

Why create a facade in laravel instead of calling a method directly?

I'm just starting with laravel and want to understand this...
Lets say we have a class in our application:
namespace App\Tests;
class MyTest{
public function sayHello($name){
echo "Hello, $name!";
}
public static function anotherTest(){
echo "another test...";
}
}
What is the advantage of creating a facade and a service provider over just using it as
use App\Tests\MyTest;
//... controller declarations here ....
public function someaction(){
$mt = new MyTest();
$mt->sayHello('John');
//or
MyTest::anotherTest();
}
//... etc...
A Facade in Laravel is only a convenient way to get an object from the Service Container and call a method on it.
So calling a Facade like this :
//access session using a Facade
$value = Session::get('key');
Is like doing:
//access session directly from the Service Container
$value = $app->make('session')->get('key');
As the Facade resolves the session key out of the Service Container and call the method get on it
Once understood what a Facade does, you should understand what is the Service container and what are the benefits of using it
The Service Container in Laravel cloud be a Dependency Injection Container and a Registry for the application
The advantages of using a Service Container over creating manually your objects are stated in one of my previous answers and in the doc page, but briefly:
Capacity to manage class dependencies on object instantation
Binding of interfaces to concrete classes, so that when a interface is requested in your program, a concrete class is instantiated automatically by the service container. Changing the concrete class on the binding, will change the concrete objects instantiated through all your app
Possibility to create single intances and get them back later (Singleton)

How to override View::make() in Laravel 4?

I would like to override the default View::make() method in Laravel, which can be used to return a view-response to the user.
(I think) I've already figured out that this method is stored inside Illuminate\View\Factory.php, and I've been reading about IoC container, while trying to make it work using some similar tutorials, but it just won't work.
I've already created a file App\Lib\MyProject\Extensions\View\Factory.php, containing the following code:
<?php namespace MyProject\Extensions\View;
use Illuminate\View\Factory as OldFactory;
class Factory extends OldFactory {
public static function make() {
// My own implementation which should override the default
}
}
where the MyProject folder is autoloaded with Composer. But I don't know how to use this 'modified' version of the Factory class whenever a static method of View (in particular View::make()) is being called. Some help would be great!
Thanks!
The call to View::make is, in Laravel speak, a call to the View facade. Facades provide global static access to a service in the service container. Step 1 is lookup the actual class View points to
#File: app/config/app.php
'aliases' => array(
'View' => 'Illuminate\Support\Facades\View',
)
This means View is aliased to the class Illuminate\Support\Facades\View. If you look at the source of Illuminate\Support\Facades\View
#File: vendor/laravel/framework/src/Illuminate/Support/Facades/View.php
class View extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'view'; }
}
You can see the facade service accessor is view. This means a call to
View::make...
is (more or less) equivalent to a call to
$app = app();
$app['view']->make(...
That is, the facade provides access to the view service in the service container. To swap out a different class, all you need to do is bind a different object in as the view service. Laravel provides an extend method for doing this.
App::extend('view', function(){
$app = app();
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$env = new \MyProject\Extensions\View($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
Notice this is more than just instantiating an object. We need to instantiate it the same way as the original view service was instantiated and bound (usually with either bind or bindShared). You can find that code here
#File: vendor\laravel\framework\src\Illuminate\View\ViewServiceProvider.php
public function registerFactory()
{
$this->app->bindShared('view', function($app)
{
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$env = new Factory($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
}
You can test if your binding worked with code like this
var_dump(get_class(app()['view']));
You should see your class name. Once your sure the binding has "taken", you're free to redefine whatever methods you want.

Categories