Laravel Service Provider bypassed - php

I have the following Class:
<?php
namespace App\CustomClasses;
class Disqus {
protected $secretKey;
protected $publicKey;
public function __construct()
{
$this->secretKey = 'abc';
$this->publicKey = '123';
}
public function payload()
{
...
}
}
I have also created a Service Provider (simplified bellow), to bind this class to the IOC container:
<?php namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\CustomClasses\Disqus;
class DisqusServiceProvider extends ServiceProvider {
public function register()
{
$this->app->singleton('Disqus', function() {
return new Disqus();
});
}
public function boot()
{
//
}
}
And in my controller:
<?php
use App\CustomClasses\Disqus;
class ArticlesController extends Controller {
public function view(Disqus $disqus)
{
...
//$disqus = App::make('Disqus');
return View::make('articles.view', compact(array('disqus')));
}
}
The problem is that whenever I use the $disqus variable, it is not 'generated' from the Service Provider, but the Disqus class itself.
However, when I have $disqus = App::make('Disqus');, the variable goes through the service provider.
So my question is, since the binding exists in the Service Provider, shouldn't the $disqus variable come from the DisqusServiceProvider rather than the Disqus class directly when I use it in my controller?
Am I missing something?
Thanks in advance for any help.

When the controller's action requires an object of class App\CustomClasses\Disqus to be passed, the service container searches its mappings for the class name of the dependency to see if it has the corresponding service. However, it uses the fully qualified class name and this is the reason it doesn't work correctly in your case.
In your service provider you have bound the service to Disqus, while the fully qualified class name is App\CustomClasses\Disqus. Use the fully qualified class name in the provider and it should work.

Related

Config class not found when unit testing laravel package

I'm working on a Laravel (5.4) package, and I'm trying to do unit testing. I have this class :
<?php
namespace Sample;
class Foo
{
public function getConfig()
{
$config = \Config::get('test');
return $config;
}
}
I have this test :
<?php
use PHPUnit\Framework\TestCase;
use Sample\Foo;
class FooTest extends TestCase
{
public function testGetConfig()
{
$foo = new Foo;
$config = $foo->getConfig();
}
}
When I execute phpunit I have this error :
Error: Class 'Config' not found
How can I unit test this class ?
Thank you.
Instead of extends PHPUnit\Framework\TestCase, you should extends Tests\TestCase:
<?php
namespace Tests\Unit;
// use PHPUnit\Framework\TestCase;
use Tests\TestCase;
use Sample\Foo;
class FooTest extends TestCase
{
public function testGetConfig()
{
$foo = new Foo;
$config = $foo->getConfig();
}
}
Moreover, Config or other Laravel facades may not work in #dataProvider method, please see Laravel framework classes not available in PHPUnit data provider for more info.
It is good practice to mock the dependencies in your code. In this case you are depending on an outside class (Config). Usually I test it like this:
// make sure the mock config facade receives the request and returns something
Config::shouldReceive('get')->with('test')->once()->andReturn('bla');
// check if the value is returned by your getConfig().
$this->assertEquals('bla', $config);
Obviously, you need to import the Config facade in your test.
BUT: I would inject the Config class in the constructor in my real code, instead of using the facade. But that's me... :-)
Something like this
class Foo
{
/** container for injection */
private $config;
public function __construct(Config config) {
$this->config = $config;
}
public function getConfig()
{
$config = $this->config->get('test');
return $config;
}
}
And then test it by injecting a mock Config into the constructor.
Try by including like this
use Illuminate\Support\Facades\Config;

Laravel Facade class not loading root class instead returns method not found

I am trying to get my Facade class to work, however it seems Laravel is calling the method on my Facade class instead of calling it on my root class. So I get method undifined error. When I create the the feed class directly from the binding ( App::make('feed')->addArticle();) it works. So I think there is soemthing wrogn with my Facade. Any ideas? Thank in advance.
Controller
class RssController extends BaseController
{
public function getArticles() {
Feed::addArticle();
}
}
ServiceProvider
use Illuminate\Support\ServiceProvider;
class FeedServiceProvider extends ServiceProvider {
public function register()
{
$this->app->bind('feed', function()
{
return new Feed;
});
}
}
Facade class
use Illuminate\Support\Facades\Facade;
class FeedFacade extends Facade {
protected static function getFacadeAccessor()
{
return 'feed';
}
}
Root Class
class Feed {
//vars
public function __construct()
{
}
public function make() {
return new Feed();
}
public function addArticle() {
return '#addArticle';
}
The problem seems to be you want both your Laravel Facade (Feed::), and the implementation class of your service provider (class Feed) to have the same name. Facades work because in app/config/app.php there's an alias section
'aliases' => array(
'App' => 'Illuminate\Support\Facades\App',
'Artisan' => 'Illuminate\Support\Facades\Artisan',
'Auth' => 'Illuminate\Support\Facades\Auth',
'Blade' => 'Illuminate\Support\Facades\Blade',
This aliasing means whenever you use, say, the App facade
`App::someMethod`
Laravel actually invokes the getFacadeAccessor on Illuminate\Support\Facades\App. There's no global class App in the system. If there were it would cause a similar problem with the facade.
Get an alias for Feed=>Illuminate\Support\Facades\Facade\FeedFacade into your system, and get your implementation class Feed out of the global namespace (moving the file to an appropriate place)
<?php
namespace My\Namespace;
class Feed
{
}
...
return new \My\Namespace\Feed;
and you should be all set.
Also, at the risk of confusing you, you don't need to drop your own classes in the Illuminate\Etc\... namespace, and you should probably put them in your own unless you're trying to get the core Laravel team to accept your classes as the official feed service.

Laravel Facades - Passing parameter to __construct()

If I am creating a Facade, and I want to pass parameters before it becomes instantiated, what do I do?
The facade's underlying service is resolved through the IoC container, so all you have to do is bind it correctly.
Create a Service Provider, and pass in whatever you want:
use Illuminate\Support\ServiceProvider;
class FooServiceProvider extends ServiceProvider {
public function register()
{
$this->app->bind('foo', function()
{
return new Foo('pass whatever you want');
});
}
}
Don't forget to load the service provider in your app's config array.
Then use that bound key in your facade:
class Bar extends Facade {
protected static function getFacadeAccessor() { return 'foo'; }
}

Issue with Facade and injected dependency in Laravel 4

I am having an issue getting a Facade to work properly with a dependency injected into the underlying class.
I have a class called 'Listing'. It has one dependency called 'AdvertRepository' which is an interface and a class called EloquentAdvert which implements the interface. The code for these three classes is here:
// PlaneSaleing\Providers\Listing.php
<?php namespace PlaneSaleing\Providers;
use PlaneSaleing\Repositories\Advert\AdvertRepository;
class Listing {
protected $advert;
public function __construct (AdvertRepository $advert_repository) {
$this->advert = $advert_repository;
}
public function test() {
$this->advert->test();
}
public function test2() {
echo "this has worked";
}
}
// PlaneSaleing\Repositories\Advert\AdvertRepository.php
<?php namespace PlaneSaleing\Repositories\Advert;
interface AdvertRepository {
public function test();
}
// PlaneSaleing\Repositories\Advert\EloquentAdvert.php;
<?php namespace PlaneSaleing\Repositories\Advert;
class EloquentAdvert implements AdvertRepository {
public function test() {
echo 'this has worked';
}
}
I have then created a service provider called ListingServiceProvider.php, which has the following code:
// PlaneSaleing/Providers/ListingServiceProvider.php
<?php namespace PlaneSaleing\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\App;
class ListingServiceProvider extends ServiceProvider {
public function register() {
App::bind('PlaneSaleing\Repositories\Advert\AdvertRepository', 'PlaneSaleing\Repositories\Advert\EloquentAdvert');
}
}
I also added this to the ServiceProviders array in app.php
Now, if I inject Listing as a dependency into a controller and call the test method (as shown below) Laravel correctly detects the dependency, instantiates EloquentAdvert via its binding and displays 'this has worked'.
// Controllers/TestController.php
use PlaneSaleing\Providers\Listing;
class TestController extends BaseController {
protected $listing;
public function __construct(Listing $listing) {
$this->listing = $listing;
}
public function test1() {
$this->listing->test();
}
}
Now, I then created a facade for Listing. I added a new facade as follows and added an alias in app.php:
// PlaneSaleing\Providers\ListingFacade.php
<?php namespace PlaneSaleing\Providers;
use Illuminate\Support\Facades\Facade;
class ListingFacade extends Facade {
protected static function getFacadeAccessor() {
return 'Listing';
}
}
I also added the following new lines to ListingServiceProvider.php:
<?php namespace PlaneSaleing\Providers;
use PlaneSaleing\Repositories\Advert\AdvertRepository;
use PlaneSaleing\Repositories\Advert\EloquentAdvert;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\App;
class ListingServiceProvider extends ServiceProvider {
public function register() {
App::bind('PlaneSaleing\Repositories\Advert\AdvertRepository', 'PlaneSaleing\Repositories\Advert\EloquentAdvert');
// New lines...
$this->app['Listing'] = $this->app->share(function() {
return new Listing(new AdvertRepository);
});
}
}
NOW...if I call Listing::test(), I get the following error: Cannot instantiate interface PlaneSaleing\Repositories\Advert\AdvertRepository.
If I call Listing::test2() , I get 'this has worked' so it seems the Facade is working correctly.
It seems that when accessing Listing via its Facade the binding between AdvertRepository and EloquentAdvert doesnt work. I have looked at my code in the ServiceProvider thinking it was the issue, but I cant figure it out.
Both the Facade and binding work when tested individually but not when both are used at the same time.
Any ideas???
OK, So I have figured it out...For those who run into a similar problem...
The offending statement was in ListingServiceProvider.php which read:
$this->app['Listing'] = $this->app->share(function() {
return new Listing(new AdvertRepository);
});
The error is the new AdvertRepository statement. The reason being is that, we are telling php to directly instantiate the interface 'AdvertRepository'. Instead, we need to tell Laravel to instantiate the appropriate implementation of the 'AdvertRepository' interface. To do that, we use App::make('AdvertRepository'). That way, Laravel uses the binding previously declared to instantiate the correct implementation.
If your constructor is not being inject with a class, you must tell Laravel what class will be used when it needs to instantiate a particular interface:
Put this in your filters or bindings file:
App::bind('PlaneSaleing\Repositories\Advert\AdvertRepository', function()
{
return new PlaneSaleing\Repositories\Advert\EloquentAdvert;
});

Symfony2 How to have access to a set of data in all controllers?

I have searched but I can't find any similar issues or maybe I am phrasing it wrong. What I want to achieve is access to an object in all the controllers in my Bundle. For example:
<?php
namespace Example\CoreBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FolderController extends Controller
{
function indexAction()
{
$title = $this->folder->getTitle();
$description = $this->folder->getDescription();
}
}
Usually outside of Symfony I would have extended the Controller class myself with BaseController extends Controller and set this up in the construct method but I know Symfony doesn't use the construct method so I am a bit stuck as to where to go.
I would usually do something like this:
class BaseController extends Controller
{
function __construct()
{
parent::__construct();
//load up the folder from my model with an ID
$this->folder = $folder;
}
}
I would then extend BaseController from FolderController and go from there but I have tried this with Symfony and it does not work. I have looked into Services but do not think this is what I need to make this work. If any more details are required please let me know, thanks.
If I understand your question correctly, services are indeed what you're looking for.
First, define a service in services.yml:
services:
vendor.folder_manager:
class: Vendor\FolderBundle\Entity\Manager\FolderManager
arguments:
em: "#doctrine.orm.entity_manager"
class: Vendor\FolderBundle\Entity\Folder
Then create that FolderManager class:
<?php
namespace Vendor\FolderBundle\Entity\Manager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
class FolderManager
{
protected $em;
protected $repo;
protected $class;
public function __construct(EntityManager $em, $class) {
$this->em = $em;
$this->class = $class;
$this->repo = $em->getRepository($class);
}
public function findById($id) {
return $this->repo->findById($id);
}
public function getRepository() {
return $this->repo;
}
}
Finally, in your controller:
$this->container->get('vendor.folder_manager')->findById($folderId);
Or:
$this->container->get('vendor.folder_manager')->getRepository()->findById($folderId);
Symfony2 will automatically inject the entity manager the class into the manager, so all you have to provide in the controller is the folder's id.
Edit:
To make it prettier, you could also make a shortcut function in your controller:
protected function getFolderManager()
{
return $this->container->get('vendor.folder_manager');
}
Then you can do:
$this->getFolderManager()->findById($folderId);

Categories