Symfony: Cannot access a service inside bundle constructor - php

How can I access a service inside of a Bundle constructor? I'm trying to create a system where a theme bundle can register itself automatically with the theme service, see small example below (the simpler solution the better):
<?php
namespace Organization\Theme\BasicBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ThemeBasicBundle extends Bundle
{
public function __construct() {
$themes = $this->get('organization.themes');
$themes->register(new Organization\Theme\BasicBundle\Entity\Theme(__DIR__));
}
}
However, $this->get does not work, this might be because there is no guarantee that all bundles has been registered yet, are there any post bundle registration "hooks" that I can use instead? Are there any special method names that I can add to the bundle class that gets executed after all bundles has been instantiated?
The service class looks like this:
<?php
namespace Organization\Theme\BasicBundle;
use Organization\Theme\BasicBundle\Entity\Theme;
class ThemeService
{
private $themes = array();
public function register(Theme $theme) {
$name = $theme->getName();
if (in_array($name, array_keys($this->themes))) {
throw new Exception('Unable to register theme, another theme with the same name ('.$name.') is already registered.');
}
$this->themes[$name] = $theme;
}
public function findAll() {
return $this->themes;
}
public function findByName(string $name) {
$result = null;
foreach($this->themes as $theme) {
if ($theme->getName() === $name) {
$result = $theme;
}
}
return $result;
}
}

It's normal that you can't access to the service container, because services are not compiled yet.
To inject tagged services into that bundle, you need to create a new compiler pass.
To create a compiler pass it needs to implements the CompilerPassInterface.
Put the class in the DependencyInjection/Compiler folder of the bundle.
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class CustomCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->has('organization.themes')) {
$container->getDefinition('organization.themes')->addMethodCall('register', array(new Organization\Theme\BasicBundle\Entity\Theme(__DIR__)));
}
}
}
Then override the build method of your bundle definition class.
class ThemeBasicBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new CustomCompilerPass());
}
}
Some links:
http://symfony.com/doc/current/components/dependency_injection/compilation.html
http://symfony.com/doc/current/cookbook/service_container/compiler_passes.html
http://symfony.com/doc/current/components/dependency_injection/tags.html

Try that it could work :) :
<?php
namespace Organization\Theme\BasicBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ThemeBasicBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$themes = $container->get('organization.themes');
$themes->register(new Organization\Theme\BasicBundle\Entity\Template(__DIR__));
}
}

Related

How to correctly extend and use other interfaces?

I'm trying to make use of a base interface for all my other interfaces as follows:
Base interface
<?php
namespace App\Repositories\Data;
interface IDataRepository
{
public function getAll();
public function getById($id);
public function create($model);
public function update($model);
public function delete($id);
}
Implemented base interface
<?php namespace App\Repositories\Data;
use Illuminate\Database\Eloquent\Model;
class DataRepository implements IDataRepository
{
// model property on class instances
protected $model;
// Constructor to bind model to repo
public function __construct(Model $model)
{
$this->model = $model;
}
// Get all instances of model
public function getAll()
{
return $this->model->all();
}
// create a new record in the database
public function create($model)
{
return $this->model->create($model);
}
// update record in the database
public function update($model)
{
$record = $this->find($model.id);
return $record->update($model);
}
// remove record from the database
public function delete($id)
{
return $this->model->destroy($id);
}
// show the record with the given id
public function getById($id)
{
return $this->model-findOrFail($id);
}
}
The interface where i'm trying to make use of the base interface
<?php
namespace App\Repositories;
use App\Repositories\Data\IDataRepository;
interface ITestRepository extends IDataRepository
{
}
implementation
<?php namespace App\Repositories;
use App\Library\Classes\Test;
use Illuminate\Database\Eloquent\Model;
class TestRepository implements ITestRepository
{
}
In my controller i'm trying to just call test repository so i can use all the base repository functions:
class TestController extends Controller
{
protected $testRepository;
public function __construct(Test $test)
{
$this->testRepository = new TestRepository($test);
}
public function index()
{
$data['testData'] = $this->testRepository->getAll();
return view('test', $data);
}
}
But i get the following error:
Class App\Repositories\TestRepository contains 5 abstract methods and
must therefore be declared abstract or implement the remaining methods
My application works fine if i only make use of my base interface and pass through a model. What would be the correct way to share functions from my base interface across all my other interfaces, so as to prevent code duplication? I appreciate any help.
I think that a Trait which will contains all methods of your interface declaration is the best choice. Something like (not sure about logic):
namespace App\Repositories;
trait TDataRepository
{
// model property on class instances
protected $model;
// Constructor to bind model to repo
public function __construct(Model $model)
{
$this->model = $model;
}
// Get all instances of model
public function getAll()
{
return $this->model->all();
}
// create a new record in the database
public function create($model)
{
return $this->model->create($model);
}
// update record in the database
public function update($model)
{
$record = $this->find($model.id);
return $record->update($model);
}
// remove record from the database
public function delete($id)
{
return $this->model->destroy($id);
}
// show the record with the given id
public function getById($id)
{
return $this->model-findOrFail($id);
}
}
And then just use it for classes with base interface:
namespace App\Repositories;
use App\Library\Classes\Test;
use Illuminate\Database\Eloquent\Model;
class TestRepository implements ITestRepository
{
use TDataRepository;
}
Also there are some other options:
abstract class with methods for base interface but it not so flexible like trait,
composition but you should change base idea and create a new entity for composition.
<?php
namespace App\Repositories;
use App\Interfaces\ITestRepository;
class TestRepository implements ITestRepository
{
public function getAll()
{
// TODO: Implement getAll() method.
}
public function getById($id)
{
// TODO: Implement getById() method.
}
public function create($model)
{
// TODO: Implement create() method.
}
public function update($model)
{
// TODO: Implement update() method.
}
public function delete($id)
{
// TODO: Implement delete() method.
}
}
Class must be declared abstract or implement methods 'getAll', 'getById', 'update', 'create', 'delete'
So All the method is by default abstract method in interface and you have to define all method in this class.
The class TestRepository should not implement any interface, but extend DataRepository:
<?php namespace App\Repositories;
use App\Repositories\Data\DataRepository;
class TestRepository extends DataRepository
{
}
DataRepository contains already the implementation of the interface IDataRepository. When you create a class implementing ITestRepository you will have to define the implementation of all the methods in the interface (which are the same as the base interface, in your case).

Laravel 5 Resolving dependencies in ServiceProvider

I have a class which acts like a storage (add/get item). I try to bind it as a singleton in one service provider, and resolve it in another's boot method.
The code is changed for simplicity.
app/Providers/BindingProvider.php
<?php namespace App\Providers;
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\ServiceProvider as ServiceProvider;
class MyBindingFacade extends Facade {
public static function getFacadeAccessor() {
return 'my.binding';
}
}
class MyBinding {
protected $items = [];
public function add($name, $item) {
$this->items[$name] = $item;
}
public function get($name) {
return $this->items[$name];
}
public function getAll() {
return $this->items;
}
}
class BindingProvider extends ServiceProvider {
public function register() {
$this->app->singleton('my.binding', function($app) {
return $app->make('App\Providers\MyBinding');
});
}
public function provides() {
return [
'my.binding',
];
}
}
app/Providers/ResolvingProvider.php
<?php namespace App\Providers;
use Illuminate\Support\ServiceProvider as ServiceProvider;
use App\Providers\MyBinding;
class ResolvingProvider extends ServiceProvider {
public function boot(MyBinding $binding) {
$binding->add('foo', 'bar');
// $manual = $this->app->make('my.binding');
// $manual->add('foo', 'bar');
}
public function register() {}
}
app/Http/Controllers/WelcomeController.php
<?php
namespace App\Http\Controllers;
use App\Providers\MyBindingFacade;
class WelcomeController extends Controller {
public function index()
{
dd(MyBindingFacade::getAll()); // debug items
}
}
When I try to debug MyBinding state in my WelcomeController I'm getting empty item array. However, if I uncomment $manual part from my ResolvingProvider it returns an array containing 'foo' => 'bar'. Does it mean IoC resolution is broken in ServiceProvider::boot() method or am I misusing Laravel functionality?
Laravel version: 5.0.28
UPDATE: Added code sample from WelcomeController.
With this:
$this->app->singleton('my.binding', function($app) {
return $app->make('App\Providers\MyBinding');
});
You're saying: my.binding is a singleton and resolves to an instance of App\Providers\MyBinding.
That doesn't mean that App\Providers\MyBinding is registered as singleton too. What you should do instead is this:
$this->app->singleton('App\Providers\MyBinding');
$this->app->bind('my.binding', function($app) {
return $app->make('App\Providers\MyBinding');
});
Because the Facade binding uses $app->make() you should get the same instance you registered with $this->app->singleton() right above.
In the first example you are not using the Facade, you should be using:
use App\Providers\MyBindingFacade as MyBinding;
Which will in fact call make it using 'my.binding'.

PHP Laravel: Trait not found

I have some trouble with namespace and use.
I get this error: "Trait 'Billing\BillingInterface' not found"
These are the files in my Laravel application:
Billing.php
namespace Billing\BillingInterface;
interface BillingInterface
{
public function charge($data);
public function subscribe($data);
public function cancel($data);
public function resume($data);
}
PaymentController.php
use Billing\BillingInterface;
class PaymentsController extends BaseController
{
use BillingInterface;
public function __construct(BillingPlatform $BillingProvider)
{
$this->BillingProvider = $BillingProvider;
}
}
How to i use use and namespace properly?
BillingInterface is an interface not a trait. Thus it can't find the non existent trait
Also you have an interface called BillingInterface in a namespace called Billing\BillingInterface, the fully qualified name of the interface is: \Billing\BillingInterface\BillingInterface
Perhaps you mean
use Billing\BillingInterface\BillingInterface;
// I am not sure what namespace BillingPlatform is in,
// just assuming it's in Billing.
use Billing\BillingPlatform;
class PaymentsController extends BaseController implements BillingInterface
{
public function __construct(BillingPlatform $BillingProvider)
{
$this->BillingProvider = $BillingProvider;
}
// Implement BillingInterface methods
}
Or to use it as a trait.
namespace Billing;
trait BillingTrait
{
public function charge($data) { /* ... */ }
public function subscribe($data) { /* ... */ }
public function cancel($data) { /* ... */ }
public function resume($data) { /* ... */ }
}
Again the modified PaymentsController, but with fully qualifies names.
class PaymentsController extends BaseController
{
// use the fully qualified name
use \Billing\BillingTrait;
// I am not sure what namespace BillingPlatform is in,
// just assuming it's in billing.
public function __construct(
\Billing\BillingPlatform $BillingProvider
) {
$this->BillingProvider = $BillingProvider;
}
}

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);

How to load an Entity from another Bundle on Symfony2

Let's say I have two Bundles :
Compagny\InterfaceBundle
Compagny\UserBundle
How can I load an Entity of UserBundle in the controller of InterfaceBundle ?
The Controller of my Compagny/InterfaceBundle :
<?php
// src/Compagny/InterfaceBundle/Controller/DefaultController.php
namespace Compagny\InterfaceBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Compagny\UserBundle\Entity; // I believed this line will do the trick, but it doesn't
class DefaultController extends Controller
{
public function indexAction()
{
$user = new User();
}
}
The Entity of my Compagny/UserBundle :
<?php
namespace Compagny\UserBundle\Entity
class User {
public $name;
public function setName($name) {
// ...
}
public function getName() {
// ...
}
}
(Let's says for this example that the User class doesn't use Doctrine2, because it doesn't need to connect to the database).
<?php
// src/Compagny/InterfaceBundle/Controller/DefaultController.php
namespace Compagny\InterfaceBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Compagny\UserBundle\Entity\User; // It's not a trick, it's PHP 5.3 namespacing!
class DefaultController extends Controller
{
public function indexAction()
{
$user = new User();
}
}
You can of course, you are just using a class from another namespace. The fact that it is an entity is not important at all! You can of course query the entity manager for that entity as well.

Categories