Container in big projects and reuse

21 views
Skip to first unread message

Aleksey Denysyuk

unread,
Feb 13, 2010, 11:08:15 AM2/13/10
to Symfony Components, ve...@difane.com
Hi All,

We are developing now a set of separate components, which however can
be deeply integrated with each other. For that purpose we want to use
the Dependency Injection principles. But we have several questions
about the Symofny’s container.

1. How do you (or can you) pass the container itself to the created
service?
For example: we have Container with 2 services (mailer, tracer) and
the controller. Controller uses tracer and mailer uses tracer.
Controller creates the container and makes “$sc->tracer” to use
tracer. It’s ok. Then, controller want to get mailer using “$sc-
mailer”.
Now, if mailer checks for example if server is accessible and it is
not, it would be nice to use tracer to write that to the log file. But
mailer doesn’t now anything about container yet.
As I understood it is impossible to use configuration or builder to
force container to smth. like “->setContainer($this)”. Is that
correct? The solution would be to make the setContainer call from the
controller, but it makes no sense to do that in case if we have 10,
20, X services and all of them need container.

2. What about services, shared for some context?
For example we have two projects. In project 2 “service1” is used,
which uses the normal logger functionality for logging its events and
“service2” that is used in 2 projects. In project1 it uses the same
simple logger. In “project2” it uses for example SMS logging.
Both services access logger by “$sc->logger”. But they need different
logger instances. Service1 – simple, service2 – sms. How can this be
handled? It would be nice to have something like “$sc-
>getServiceForContext(‘logger’, __CLASS__)”. The we can configure
container in a way, that each context gets its own configurated
instance.

3. As I understood, the idea is to have one container in the running
environment. But what if we have service1, service2 and service3 which
have their own specific container (container1) in project1, that
overwrites the sfSymfonyContainer and we have service2, service3,
service4 with its special container (container2) in project2. Now for
example we have a new project and we need all of that services. To
have one container (created by the controller for example). I have
then to copy-paste the code (or configuration) from container1 and
container2 to the new one. And with more projects the copy-pasting
will grow. How is it possible to overcome this problem?

Thanx in advance for your answers,
Aleksey

Lukas Kahwe Smith

unread,
Feb 13, 2010, 11:19:13 AM2/13/10
to symfony-c...@googlegroups.com, ve...@difane.com
Hi,

I must admit I have a hard time following your questions, mainly because its a bit abstract without code. But I know where you are coming from. When I started implementing the service container in my framework I ran into questions that sounded similar (and were also impossible to explain).

I think the key thing to realize is that you need to spend some time structuring your configuration in a way that makes it possible for you to easily handle your different environments. Using the "import" support for load parameters and services is very empowering. Also the fact that you can "overload" any of these definitions. So you can a create a base config, which may split up its content into multiple files via the import magic. Now this base file in turn can also be loaded into another config that selectively overloads these services and parameters.

Another thing you need to accept with a service container is that anything you inject obviously needs to be instantiated. So design your classes accordingly. Lazy load connections, don't do a lot of work in the constructor/factory. But once you have done this, you no longer need to worry all that much about making these dependencies. If tracer is needed in the controller and in the mailer, so be it. With the power of the configuration you can easily pass the same shared instance or different instances with different configurations if necessary.

regards,
Lukas Kahwe Smith
m...@pooteeweet.org

Aleksey Denysyuk

unread,
Feb 18, 2010, 4:48:46 AM2/18/10
to Symfony Components
Hi Lukas,

Thanx for your fast answer.

I think it was an answer to the questions 2 and 3 )

Regarding 1st question. I'll write ans example.

class mailer
{
public function __construct ( $server, $login, $pass )
{
$this->mServer = $server;
$this->mLogin = $login;
$this->mPass = $pass;
}

public function setContainer ( $container )
{
$this->mContainer = $container;
}

public function send ( $to, $text )
{
$this->mContainer->logger->log("Sending message to $to. Message:
$text');
// Sending routine
// ...
}
}

Now the configuration:

parameters:
mailer.login: foo
mailer.pass: bar
mailer.server: mail.com

services:
mailer:
class: Zend_Mail
arguments: [ "%mailer.server%", "%mailer.login%", "%mailer.pass
%" ]
calls:
- [setContainer, [@container]]

It would be nice if i could write "@container" to let container pass
itself to the service. Is that somehow possible?

Regards,
Aleksey

David Stendardi

unread,
Feb 19, 2010, 7:54:55 AM2/19/10
to symfony-c...@googlegroups.com
Hello Aleksey !

1.      How do you (or can you) pass the container itself to the created service?

This is an anti pattern. A service must not be aware of the service container.
In your example, you should inject the "tracer" service into the "mailer" service.
As Lukas said, you will probably have to rewrite a bit your classes.

the configuration would look like this (in yaml) :

services:               
  tracer:
    class: %tracer.class%   
  mailer:
    class: %mailer.class%
    arguments: [@tracer]

If your tracer is a logger, you could decouple this using the event dispatcher container in the ProjectConfiguration. In my project i use the ProjectConfiguration instance to inject Event Dispatcher

services:

  configuration:
    class: ProjectConfiguration
    constructor: getActive
  mailer:
    class: %mailer.class%
    arguments: [@configuration]

I can then access the dispatcher and use inside my service for logging purpose

$this->dispatcher = $configuration->getEventDispatcher();


2. What about services, shared for some context

I think you should instanciate one container per "context". For example, in our projects the service container is created inside the application configuration. So we access it using : 

$sc = ProjectConfiguration::getActive()->getServiceContainer():


3. .. And with more projects the copy-pasting will grow. How is it possible to overcome this problem ?

The service container can load file from different paths. 
Here is a bit of code extracted from the "ApplicationConfiguration::getServiceContainer" class :

$aPaths[] = sfConfig::get('sf_app_dir').'/config/services/'.$this->getEnvironment();

$aPaths[] = sfConfig::get('sf_app_dir').'/config/services';

$oLoader = new sfServiceContainerLoaderFileYaml($this->oServiceContainer, $aPaths);

I have setted up a shared repository for common classes and configuration that is located in the project/lib/vendor/<company>
folder.

By adding the folder that contains  the shared configuration, you can configure the service container with defaults services and parameters that fit your company requirements (ex: smtp host etc...)

Hope this answer help you. Contact me if you want some pieces of code that highlight these use cases. 

David Stendardi
Reply all
Reply to author
Forward
0 new messages