Testable (small) application with basic initialisation using dependency injection
Been getting more and more in applying proper methodologies to code in a testable manner and the past 2 weeks have drastically changed my approach when starting a small app from scratch.
Since I very often have to write small php console applications, my current target is to have a minimal application methodology that initialises a small application in a testable manner. Just want the usual classes for config, DB connection with a properly used singleton , error_handling configuration an开发者_如何学God logging.
I have approached this in quite a few different ways, but as of right now I have no clue if I am at all in the proper direction. Reading up on posts about dependency injection, I have come to try creating objects that have objects as parameters all the while respecting the law of Demeter for easy mocking. I have eliminated global constants that were not truly constants. I have just eliminated most static calls, and for the needed singletons such as app-wide DB connection, trying to apply Mr Hevery's suggestions and contain/wrap it in an object that will be passed along with other needed objects in the collaborating classes.
This is a very simplistic example straight from the top of my head from what I have pieced together so far. The approach I am looking into is to use a class AppFactoryHelper that acts as a very simple factory and creates the objects noted above.
I create a config object that is just a regular array with getters and setters, a DB singleton connexion, a logger object, and the error_handling class all through this factory, ie:
// very sketchy outline of programm flow for initialisation, stripped of error handling
require_once 'includes/settings.php';
require_once 'AppHelperFactory.php';
require_once 'container.php';
// object to load in container
$appObjects = array = ('log', 'db', 'error_handling');
$appHelperFactory = new AppHelperFactory;
//config object will be needed helper objects
$config = $appHelperFactory->createConfig($settings.php); //
// create dependency container, sets config object as private property inside,
// container holds only getters for $config.
$container = $appHelperFactory->createContainer($config);
try{
foreach($appObject as $className){
$methodName = 'create' . $className;
$container->{$value} = $appHelperFactory->{$methodName}($config);
}
$app = new ObjectThatWillFinallyGetSomethingDone($container);
$app->doStuff();
This approach has raised questions though. Am I using dependency injection even close to the proper way I should? is the DB singleton better off in a container that way? And the thing that bugs me all the time in testing, how would I test my "main" file?
I've read your question soon after you posted it but I don't have a really solid approach to answersing.
So i decided on just going trough your post from top to bottom and answering on everything that jumps me.
DB connection with a properly used singleton
Singletons have no use in PHP
If you only need one, only make one. It just makes testing harder and introduces global state if you use a singleton.
I have just eliminated most static calls, and for the needed singletons such as app-wide DB connection
Again: "singleton" as in 'i only create one is fine. "Singleton" as in "the Pattern" is not. Just don't, you don't need it.
contain/wrap it in an object that will be passed along with other needed objects in the collaborating classes.
So basically a Registry? You put stuff in and your application expects that it can pull stuff out of it. So you are passing around an intermediate object instead of the real objects. It can work but it's usually not the best approach.
This Google Tech Talk: The Clean Code Talks - Don't Look For Things! does a nice job at explaining why.
The approach I am looking into is to use a class AppFactoryHelper that acts as a very simple factory and creates the objects noted above.
You only create the basic objects for you application one in your bootstrap so I'm not all to sure why you want to wrap those into a factory but well. I'll get back to that when it comes to the code.
Now to the code
Let me say first that i tried reading those ~30 lines for 5 times until i got the hang of what you are trying to do even so the code is kinda short. Might just be me though ;)
The requires:
Nowadays usually an autoloader is used, if you don't like crippling your file structure by tying your classnames to it use something like phpab that always for flexibility.
The objects:
You abstract everything your application does with your AppHelperFactory.
$appHelperFactory = new AppHelperFactory;
$config = $appHelperFactory->createConfig($settings.php);
// dunno what $settings.php means here, i assume you mean "settings.php" or something
I'm just suggesting that
$config = new Config();
$config->readFromFile("settings.php");
would also get the job done and since i assume you don't need to create config objects all over the place you should not build a factory just for the sake of it.
The same goes for your other objects.
Your ObjectThatWillFinallyGetSomethingDone depends on a container that is expects to hold at least 4 objects and mocking those for testing will be quite a pain. (Or at least more of a pain than it needs to be).
Especially when you are creating a web application i don't see the point in having a "application" object (if you use it as your "main method" thats fine i guess) but if you want it really unit testable you'd need to pass in a lot more objects to it than you currently have. Things like a router, some "controllerFactory" (or however you handle the request dispatching to the code that does your business logic) and so on.
Am I using dependency injection even close to the proper way I should?
You are using a Registry. Thats something else but related.
Reads on that topic (pro and con):
Flaw: Brittle Global State & Singletons
(the "Adding or using registries" and "Adding or using service locators" sections)
Do you need a Dependency Injection Container?
Dealing with dependencies
(just some samples)
and somewhat related Object lifecycle control
All in all the code you showed focuses on "build lots of stuff and put it somewhere in case i need it" and doesn't show what you are actually trying to do (usually answering requests).
So for that.. yeah.. you managed to do that and apart from some minor code complaints
$container->{$value} = $appHelperFactory->{$methodName}($config); // really? :(
you kinda succeed in doing so. If thats actually helping or any good can't be said from just that piece of bootstrap I'd say.
Hope that helps.. you can also jump into the PHP chat here on SO for those kinds of discussions if it something you can't get into a question because it's too subjective ;)
Why don't you simply use Symfony2 or other framework that meets your criteria? It supports DI, console apps, logging, database connection (using Doctrine DBAL or ORM) and so forth. Also it's being written and tested by hundreds of people so it's much safer and flexible.
- Database connection shouldn't be a singleton. There could be more than one instance of connection. Also there is no need for global, application-wise access as this object is being used only by some services responsible for maintaining business-data.
- Use reflection instead of
$container->{$value}
. It's much easier to deal with more "static" code.
EDIT, 2011-05-16
First of all, when I wrote:
There could be more than one instance of connection.
I meant that you could have several connections to several databases. Creating multiple connections to single database is pointless.
Database connection should be treated as a service. Let's say you're using PDO so all you need to do is to define several connection parameters and create a service (pseudocode):
...
<parameters>
<parameter key="db.connection.dns">mysql:dbname=testdb;host=127.0.0.1</parameter>
<parameter key="db.connection.username">root</parameter>
<parameter key="db.connection.password">password</parameter>
<parameter key="db.connection.options" type="collection" />
</parameters>
...
<services>
<service id="db.connection" class="PDO">
<argument>%db.connection.dns%</argument>
<argument>%db.connection.username%</argument>
<argument>%db.connection.password%</argument>
<argument type="collection">%db.connection.options%</argument>
</service>
</services>
...
So now, whenever you request the container to return a db.connection
service ($container->getService('db.connection');
) it will create, if necessary, and return a reference to the connection object.
Let's say you've got two services, user.manager
and thread.manager
that requires database connection. Just define a reference to that connection in your container:
<service id="user.manager" class="UserManager">
<argument type="service">db.connection</argument>
</service>
<service id="thread.manager" class="...">
<argument type="service">db.connection</argument>
</service>
class UserManager {
public function __construct(PDO $conn) {
...
}
}
That's all. A single object will be passed across all services that requires it.
You could use Symfony's DIC in ZF project. It can be used as standalone component:
- http://symfony.com/doc/current/book/service_container.html
- http://components.symfony-project.org/dependency-injection/documentation
- https://github.com/symfony/symfony/tree/master/src/Symfony/Component/DependencyInjection
精彩评论