I have a controller:
public function getAllItemsAction()
{
$content = $this->getDoctrine()->getRepository(Item::class)->findAll();//<-(1)--THIS TO REPOSITORY
if ($content === NULL) {
return new View("Items not found", Response::HTTP_NOT_FOUND);
}
return new View($content,Response::HTTP_OK);
}
How can I move this line (1) to the repository and then use this method from the repository in the controller?
The line you highlighted is actually not related from Doctrine: you are getting a service from the Dependency Injection container, and then calling a method on it.
What may bothers you is that you are using an alias (getDoctrine()) and the registry from Doctrine, which is there for conveniency.
But actually you could also declare you repository as a service and do this: $this->get('item_repository)->findAll()`.
Related
I am starting to work with services in Symfony and therefore created the example service from the symfony documentation:
namespace AppBundle\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
private $logger;
public function __construct(LoggerInterface $logger){
}
public function getMessage()
{
$this->logger->info('Success!');
}
}
I call that service in my controller (I also have the use Statement:
: use AppBundle\Service\MessageGenerator;
$messageGenerator = $this->get(MessageGenerator::class);
$message = $messageGenerator->getMessage();
$this->addFlash('success', $message);
My service is defined in the services.yml file:
app.message_generator:
class: AppBundle\Service\MessageGenerator
public: true
so in my eyes I did everything exactly as described in the documentation and when calling:
php app/console debug:container app.message_generator
in my commandline I get my service:
Option Value
------------------ ------------------------------------
Service ID app.message_generator
Class AppBundle\Service\MessageGenerator
Tags -
Scope container
Public yes
Synthetic no
Lazy no
Synchronized no
Abstract no
Autowired no
Autowiring Types -
Now when I execute the controller function where I call my service I still get the error:
You have requested a non-existent service "appbundle\service\messagegenerator".
Any ideas?
Symfony is a bit confusing at naming: you retrieve the service by requesting it by its defined name: app.message_generator.
$messageGenerator = $this->get('app.message_generator');
Symfony has recently suggested switching from a give-name (app.message_generator) that you are defining the service as, to the class name (AppBundle\Service\MessageGenerator). They are both just 'a name' to call the service.
You are trying to use both, when only the given name is defined.
In the long term, it's suggested to use the ::class based name, and quite possibly allow the framework to find the classes itself, and configure them itself too. This means that, by default, all services are private, and are handled by the framework & it's service container.
In the meantime, while you are learning, you can either:
$messageGenerator = $this->get('app.message_generator');
or define explicitly define the service, and make it public, so it can be fetched with ->get(...) from the container.
# services.yml
AppBundle\Service\MessageGenerator:
class: AppBundle\Service\MessageGenerator
public: true
# php controller
$messageGenerator = $this->get(MessageGenerator::class);
or just injected automatically into the controller, when that is requested
public function __construct(LoggerInterface $logger, MessageGenerator $msgGen)
{
$this->messageGenerator = $msgGen;
}
public function getMessage()
{
$result = $this->messageGenerator->do_things(....);
$this->logger->info('Success!');
}
I am trying to setup a Symfony implementation of this PHP library for Chargify https://github.com/johannez/chargify
I'm getting a bit lost working out the best / proper way to set it all up.
I think I need to setup Guzzle as a service, then create a Chargify factory and have that added as a service.
My problem is that in the factory class, when I try and use the Guzzle service I get a fatal error
Fatal error: Using $this when not in object context in /symfony/src/Acme/ChargifyBundle/Factory/ChargifyFactory.php on line 8
This is my Factory class
<?php
namespace Acme\ChargifyBundle\Factory;
class ChargifyFactory implements ChargifyFactoryInterface
{
public static function build($type)
{
$client = $this->get('chargify.guzzle.client');
$className = 'Chargify\\Controller\\' . ucfirst($type);
if (class_exists($className)) {
return new $className($client);
}
else {
throw new Exception("Invalid controller type given.");
}
}
}
If it's useful to see some config, this is my services.yml for the bundle
services:
chargify.guzzle.client.curl_auth:
class: %guzzle.plugin.curl_auth.class%
arguments:
api_key: %chargify_api_key%
chargify.guzzle.client:
class: %guzzle.client.class%
tags:
- { name: guzzle.client }
calls:
- [setBaseUrl, [%chargify_domain%]]
- [addSubscriber, [#chargify.guzzle.client.curl_auth]]
argument: %chargify_domain%
chargify.factory:
class: Acme\ChargifyBundle\Factory\ChargifyFactory
arguments:
- ["type"]
chargify.customer:
class: Acme\ChargifyBundle\Controller\CustomerController
factory_class: Acme\ChargifyBundle\Factory\ChargifyFactory
factory_method: build
arguments:
type: "customer"
How can I use the guzzle client in the Factory with out using
$client = $this->get('chargify.guzzle.client');
EDIT:
I have changed the code as per #alex's answer, but I'm still getting an error. I think this is because the function is static. I've looked though the documents, but I can't see where I can setup a factory without a static function, and when I get rid of static I get a different error.
Runtime Notice: Non-static method Acme\ChargifyBundle\Factory\ChargifyFactory::build() should not be called statically, assuming $this from incompatible context
That is being thrown from some generated code
protected function getChargify_CustomerService()
{
return $this->services['chargify.customer'] = \Acme\ChargifyBundle\Factory\ChargifyFactory::build('customer');
}
I have started using Symfony's console components to build various cli tools.
I am currently slapping together such a console app, that has require various configurations, some of which are shared among commands, other configs are unique to the command.
At first I was using a helper class, with a static function call to load a regular configuration array.
Yesterday I refactored this and now load configuration in the config component, along with the treeBuilder mechanism for validation. This is all done in the main console script, not in the "command" classes.
$app = new Application('Console deployment Application', '0.0.1');
/**
* Load configuration
*/
$configDirectories = array(__DIR__.'/config');
$locator = new FileLocator($configDirectories);
$loader = new YamlConfigLoader($locator);
$configValues = $loader->load(file_get_contents($locator->locate("config.yml")));
// process configuration
$processor = new Processor();
$configuration = new Configuration();
try {
$processedConfiguration = $processor->processConfiguration(
$configuration,
$configValues
);
// configuration validated
var_dump($processedConfiguration);
} catch (Exception $e) {
// validation error
echo $e->getMessage() . PHP_EOL;
}
/**
* Load commands
*/
foreach(glob(__DIR__ . '/src/Command/*Command.php') as $FileName) {
$className = "Command\\" . rtrim(basename($FileName), ".php");
$app->addCommands(array(
new $className,
));
}
$app->run();
Currently, the only means to setup the configuration is to setup the code that loads the configuration in a separate class and call this class in in the configure() method of every method.
Maybe there is a more "symfonyish" way of doing this that I missed, I also would like to avoid having the entire framework in codebase, this is meant to be a lightweight console app.
Is there a way to pass the processed configuration to the commands being invoked, using DI or some other method I am not aware of?
Manual Injection
If you wany to keep things light and only have one (the same) configuration object for all commands, you don't even needa DI container. Simply create the commands like this:
...
$app->addCommands(array(
new $className($configuration),
));
Although you have to be aware of the trade-offs, e.g. you will have to have more effort extending this in the future or adjust to changing requirements.
Simple DI Container
You can of course use a DI container, there is a really lightweight container called Twittee, which has less than 140 characters (and thus fits in a tweet). You could simply copy and paste that and add no dependency. In your case this may end up looking similar to:
$c = new Container();
$c->configA = function ($c) {
return new ConfigA();
};
$c->commandA = function($c) {
return new CommandA($c->configA());
}
// ...
You then would need to set that up for all your commands and configurations and then simply for each command:
$app->addCommand($c->commandA());
Interface Injection
You could roll your own simple injection mechanism using interfaces and setter injection. For each dependency you want to inject you will need to define an interface:
interface ConfigAAwareInterface {
public function setConfigA(ConfigA $config);
}
interface ConfigBAwareInterface {
public function setConfigA(ConfigA $config);
}
Any class that needs the dependency can simply implement the interface. As you will mostly repeat the setters, make use of a trait:
trait ConfigAAwareTrait {
private $config;
public function setConfigA(ConfigA $config) { $this->config = $config; }
public function getConfigA() { return $this->config }
}
class MyCommand extends Command implements ConfigAAwareInterface {
use ConfigAAwareTrait;
public function execute($in, $out) {
// access config
$this->getConfigA();
}
}
Now all that is left is to actually instantiate the commands and inject the dependencies. You can use the following simple "injector class":
class Injector {
private $injectors = array();
public function addInjector(callable $injector) {
$this->injectors[] = $injector;
}
public function inject($object) {
// here we'll just call the injector callables
foreach ($this->injectors as $inject) {
$inject($object);
}
return $object;
}
}
$injector = new Injector();
$configA = new ConfigA();
$injector->addInjector(function($object) use ($configA) {
if ($object instanceof ConfigAAwareInterface) {
$object->setConfigA($configA);
}
});
// ... add more injectors
Now to actually construct a command, you can simply call:
$injector->inject(new CommandA());
And the injector will inject dependencies based on the implemented interfaces.
This may at first seem a little complicated, but it is in fact quite helpful at times.
However, if you have multiple objects of the same class that you need to inject (e.g. new Config("path/to/a.cfg") and new Config("path/to/b.cfg")) this might not be an ideal solution, as you can only distinguish by interfaces.
Dependency Injection Library
You can of course also use a whole library and add that as dependency. I have written a list of PHP dependency injection containers in a separate answer.
This question already has answers here:
Is there a way to specify Doctrine2 Entitymanager implementation class in Symfony2?
(4 answers)
Closed 9 years ago.
I've created some additional methods to help with managing entities in a Symfony2 project. I would like these method to be available on Doctrine's EntityManager which is obtained from the container with the "doctrine.orm.entity_manager" key.
I found this post, and tried implementing it, but it is not working. I get an error that says The attribute "name" must be set for path "doctrine.orm.entity_managers".
This is an older post, so it may be some kind of "trick" that has been deprecated since then; I'm using the latest stable versions of Symfony2 and Doctrine.
Is there a way to override the class that Doctrine uses to create it's default EntityManager, or am I going to have to just create like another service and use that instead?
Way remains the same.
Override parameter doctrine.orm.entity_manager.class from orm.xml in your services.yml with your custom EntityManager class that extends Doctrine\ORM\EntityManager.
Override create method(it changed a little bit)
public static function create($conn, Configuration $config, EventManager $eventManager = null)
{
if ( ! $config->getMetadataDriverImpl()) {
throw ORMException::missingMappingDriverImpl();
}
switch (true) {
case (is_array($conn)):
$conn = \Doctrine\DBAL\DriverManager::getConnection(
$conn, $config, ($eventManager ?: new EventManager())
);
break;
case ($conn instanceof Connection):
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
throw ORMException::mismatchedEventManager();
}
break;
default:
throw new \InvalidArgumentException("Invalid argument: " . $conn);
}
// return your instance of em
return new MyEntityManager($conn, $config, $conn->getEventManager());
}
Error that you see is the fact that you have the wrong settings in the config.yml for doctrine. See reference.
I prepare external bundle and I would like to add some routes during compilation passes.
Routes will be created on the main app/config/config.yml settings.
I was trying to get router from ContainerBuilder in my CustomCompilerPass via:
$definition = $container->getDefinition('router');
, but I got The service definition "router" does not exist.
Is it possible to add custom routes during compilation passes?
There's no way to add routes at compiler passes.
In order to dynamicly load routes (aware of container parameters) I'd use a custom route loader as given in my previous example
class MyLoader extends Loader
{
protected $params;
public function __construct($params)
{
$this->params = $params;
}
public function supports($resource, $type = null)
{
return $type === 'custom' && $this->params == 'YourLogic';
}
public function load($resource, $type = null)
{
// This method will only be called if it suits the parameters
$routes = new RouteCollection;
$resource = '#AcmeFooBundle/Resources/config/dynamic_routing.yml';
$type = 'yaml';
$routes->addCollection($this->import($resource, $type));
return $routes;
}
}
routing.yml
_custom_routes:
resource: .
type: custom
router is an alias, not a service. To get that from a ContainerBuilder, use ContainerBuilder::getAlias. To get the service ID, you need to cast that object to a string: (string) $container->getAlias('router'). Now, you can use that ID to get the service: $container->getDefinition($container->getAlias('router')). And then you get the Service which you can modify to add routes.
BTW, I'm not sure if this is really the thing you want. What about using the CmfRoutingBundle. Then, you use the Chain Router, so you can use both the Symfony2 router and the DynamicRouter. The DynamicRouter can be used with a custom route provider, in which you return the routes you want (you can get them from every resource you want).