tag:blogger.com,1999:blog-18864823261107566702024-03-28T00:21:18.723-07:00Tech BlogProgramming: The art of pushing buttons in the right order. (Posts maybe be few and far between)Anonymoushttp://www.blogger.com/profile/18205730894565091028noreply@blogger.comBlogger6125tag:blogger.com,1999:blog-1886482326110756670.post-2532238195103463032014-09-11T15:23:00.001-07:002014-09-11T17:25:58.882-07:00Bootstrapping and Secrets Management: solving the chef-vault chicken/egg problem with chef-metal<h2>
<span style="font-size: x-large;">
Intro</span></h2>
Automating server creation with <a href="http://www.getchef.com/" target="_blank">Chef</a> can be very satisfying when it works well. It saves time and avoids the need for tedious and repetitive tasks. Distribution of secrets to servers created this way has always been a hassle for me (or insecure, if laziness gets the best of me). Chef-vault does some fancy things with encrypted data bags and the public/private key system used by chef. When an item is updated in the vault, it encrypts the data for clients using their public keys from chef, allowing those clients to decrypt it with their private keys. The problem with this is, the client needs to exist before the vault is updated, but you also want the client to access things in the vault while it is running its initial run list. In this post I'll cover how I'm currently solving this problem with <a href="https://github.com/opscode/chef-metal" target="_blank">chef-metal</a> and <a href="https://github.com/Nordstrom/chef-vault" target="_blank">chef-vault</a>. For those unfamiliar, chef-metal allows you to create machines, clusters, full stacks, or whatever, using the same type of code you are already familiar with in configuring machines.<br />
<span style="font-size: x-large;"><br /></span>
<br />
<h2>
<span style="font-size: x-large;">
Too long; not going to read</span></h2>
A single chef-metal recipe does the following<br />
<ol>
<li>creates an instance with a role and empty run list</li>
<li>chef-vault is updated using said role</li>
<li>instance gets a new run list</li>
</ol>
<span style="font-size: x-large;"><br /></span>
<br />
<h2>
<span style="font-size: x-large;">
Disclaimer</span></h2>
My knowledge of ruby is pretty limited, and this whole method seems a little convoluted. I'm hoping someone will come along and show me a better way.<br />
<span style="font-size: x-large;"><br /></span>
<br />
<h2>
<span style="font-size: x-large;">
Tutorial</span></h2>
First, to get chef-metal to interact with your virtualization provider of choice, you need to set a driver. This can be done in knife.rb as follows:<br />
<br />
<pre class="prettyprint brush:ruby">...
driver 'fog:AWS:example'
...
</pre>
<br />
I'm using AWS, so I also have a file ~/.aws/config with profiles for different accounts. In this case, I've specified the 'example' profile<br />
<br />
<pre class="prettyprint brush:bash">...
[profile example]
aws_access_key_id=ABC
aws_secret_access_key=123
region=us-west-2
availability_zone=us-west-2b
</pre>
<br />
chef-vault also needs to know whether it should act in 'client' or 'solo' mode. Put something like this in your knife.rb as well (replacing values as needed)<br />
<br />
<pre class="prettyprint brush:ruby">...
knife[:vault_mode] = "client"
knife[:vault_admins] = [ "reese" ]
...
</pre>
<br />
Now we need a recipe that bootstraps a machine, updates the vault, and then adds the application to the machine's run list once it can access items in the vault.
<br />
<br />
<pre class="prettyprint brush:ruby">#secrets/recipes/metal.rb
%w{chef-metal chef-metal-fog fog}.each do |pkg|
chef_gem "#{pkg}" do
action :install
end
end
require 'chef_metal_fog'
#require "cheffish"
#specify where the chef server is
with_chef_server 'https://chef.example.com',
:client_name => 'reese',
:signing_key_filename => '~/.chef/reese.pem'
#create a key pair for the default admin user (ec2-user in this case)
fog_key_pair "admin"
#put options in a variable for easier overriding for different environments
options = {
:sudo => true,
:ssh_username => 'ec2-user',
:sudo => true,
:bootstrap_options => {
:key_name => 'admin',
:availability_zone => 'us-west-2b',
:security_group_ids => %w(sg-12345678),
:subnet_id => 'subnet-abcdef12',
:flavor_id => 't2.micro',
:image_id => 'ami-d13845e1', #amazon linux ami, us-west t2.micro
:virtualization_type => 'hvm' #required for t2.micro
}
}
with_machine_options options
machine "lego-app-dev" do
chef_environment "development"
role 'keyholder-bootstrap'
role 'lego-app-bootstrap'
action [:converge]
end
def deep_clone(o)
Marshal.load(Marshal.dump(o))
end
production_options = deep_clone(options) #someone good at ruby, tell me there's a better way
production_options[:bootstrap_options][:flavor_id] = "m3.medium"
production_options[:bootstrap_options][:image_id] = "ami-d13845e1"
machine "lego-app1" do
machine_options production_options
chef_environment "production"
role 'lego-app-bootstrap'
action [:converge]
end
include_recipe "secrets::update-vault"
machine "lego-app-dev" do
role 'keyholder-bootstrap' #is this needed? or does it remain from before?
role 'keyholder'
role 'lego-app-bootstrap'
role 'lego-app'
converge true
end
machine "lego-app1" do
role 'lego-app-bootstrap'
role 'lego-app'
converge true
end</pre>
<br />
Check out <a href="https://github.com/opscode/chef-metal/blob/master/docs/configuration.md" target="_blank">the documentation</a> to see more driver/machine configuration options<br />
<br />
I want to deliver sensitive data only to the clients which need it, so I have an empty 'bootstrap' role to go along with each role that accesses a set of secrets. These non-empty roles (e.g. 'keyholder') trivially run a single recipe with a name similar to the role. In this case, the role 'keyholder' lets emmet and wildstyle log in, while the role 'lego-app' runs the actual application.<br />
<br />
The update-vault recipe takes care of making sure each client can access the items it should be able to. This code is probably cringeworthy (but it works, as far as I can tell).<br />
<br />
<pre class="prettyprint brush:ruby">#secrets/recipes/update-vault.rb
chef_gem "chef-vault" do
action :install
end
secrets_path = "~/.chef/secrets"
vault_name = 'credentials'
#create or update the vault
def update_vault(vault, item, filePath, type, role)
ruby_block "check_vault_#{vault}_#{item}" do
block do
#probably a terrible way to decide whether to create or update the vault
Chef::Resource::RubyBlock.send(:include, Chef::Mixin::ShellOut)
#see if item exists in vault
command = 'knife vault show '+vault+' '+item
command_out = shell_out(command)
#check if command resulted in an error
if command_out.stderr.to_s != ''
puts "\ncreating vault and/or vault item\n"
action = "create"
else
puts "\nvault exists\n"
action = "update"
end
vault_command = "knife vault #{action} #{vault} #{item} --mode client --#{type} #{filePath} -A 'reese' -S 'role:#{role}' "
vault_command_out = shell_out(vault_command)
puts vault_command_out.stdout
end
end
end
#for each secret file, add an entry to the vault
secrets = {
"emmet" => "emmet.pub",
"wildstyle" => "wildstyle.pub"
}
secrets.each do |secret_name,filename|
filePath = File.join(secrets_path, filename)
type = "file"
role = "keyholder-bootstrap"
update_vault(vault_name, secret_name, filePath, type, role)
end
vault_name = 'config'
#for each json secrets set, add an entry to the vault
jsonSecrets = {
"service_config" => "services.json"
}
jsonSecrets.each do |secret_name,filename|
filePath = File.join(secrets_path, filename)
type = "json"
role = "lego-app-bootstrap"
update_vault(vault_name, secret_name, filePath, type, role)
end
</pre>
And here's an example services.json:
<br />
<pre class="prettyprint brush:js">{
"password":"spaceship",
"username":"benny"
}
</pre>
<br />
So since we run update-vault with chef-zero, it runs the knife commands using the local knife client.<br />
<br />
The actual 'lego-app' and 'keyholders' recipes run as usual on the machines.<br />
<br />
<pre class="prettyprint brush:ruby">#secrets/recipes/keyholders.rb
#allow use of chef_vault_item resource
include_recipe "chef-vault"
users = %w(emmet wildstyle)
vault = "credentials"
users.each do |username|
user username do
action :create
end
dir = "/home/#{username}/.ssh"
directory dir do
owner username
group username
mode 0755
action :create
end
private_key = chef_vault_item(vault, username)
file File.join(dir,"authorized_keys") do
content private_key['file-content']
owner username
group username
mode "0600"
action :create
end
end
</pre>
<br />
<br />
<pre class="prettyprint brush:ruby">#secrets/recipes/lego-app.rb
include_recipe "chef-vault"
vault = "config"
secret_name = "service_config"
config = chef_vault_item(vault, secret_name)
#these attributes could be used in a template or something
node.set['service']['password'] = config['password']
node.set['service']['username'] = config['username']
#alternatively, do stuff directly with the secrets
username = config['username']
execute "configure_amazing_service" do
command "echo username=#{username} > /usr/local/etc/service.conf"
end
</pre>
<br />
To run the recipe, use chef-zero and run the following command<br />
<pre class="prettyprint brush:bash">chef-client -z -o secrets::metal
</pre>
The 'metal' recipe should create 2 servers with empty run lists, one micro for development and one medium for production. From there, it will update the vaults, allowing the two new machines to access items if they are in the right role. Finally, it will apply the roles with the run lists to create the users and install/configure the application.
<br />
Let me know if I missed anything important or can do things a better way.
<br />
<span style="font-size: x-large;"><br /></span>
<br />
<h2>
<span style="font-size: x-large;">
Inspiration/Credits</span></h2>
The method used here was inspired by the orchestration scenario presented on <a href="http://www.getchef.com/blog/2014/03/04/chef-metal-0-2-release/" target="_blank">this page</a> and by <a href="https://twitter.com/moserke/status/456479736781234177" target="_blank">this tweet conversation</a> which I didn't fully understand.<br />
<div>
<br /></div>Anonymoushttp://www.blogger.com/profile/18205730894565091028noreply@blogger.com4tag:blogger.com,1999:blog-1886482326110756670.post-9636477381284234972013-09-11T10:33:00.001-07:002014-09-11T12:04:19.475-07:00Using the ServiceManager as an Inversion of Control Container (Part 2) In <a href="http://techblog.shinymayhem.com/2013/09/using-servicemanager-as-inversion-of.html" target="_blank">Part 1</a>, we learned how to inject a single, shared dependency into the controller using the ServiceManager as an inversion of control container, but what if you need multiple instances of a class? For example, what if you needed multiple <i>Brick </i>objects to build that <i>Building</i>? Here is the <i>Brick </i>class:<br />
<pre class="prettyprint brush:php"><?php
#module/Building/src/Building/Model/Brick.php
namespace Building\Model;
class Brick
{
protected $_color;
protected $_randomColors = array("red", "brown", "black", "yellow",
"orange", "purple", "green");
public function __construct($color)
{
$this->_color = ($color===null)?"default":$color;
}
public function setColor($color)
{
$this->_color = $color;
}
public function getColor()
{
return $this->_color;
}
public function getRandomColor()
{
return $this->_randomColors[array_rand($this->_randomColors)];
}
}</pre>
<br />
<br />
Now the <i>Building</i> model needs to be able to get multiple unique instances of <i>Brick</i>. To accomplish this, we'll need a basic understanding closures and capturing state. If we follow the same pattern as before, and add <i>Brick </i>as a dependency in <i>Building</i>, we will only have access to a single instance of <i>Brick</i>. Additionally, we will be unable to provide any runtime dependencies, such as the <i>$color</i> parameter in <i>Brick</i>'s construction.<br />
<br />
<pre class="prettyprint brush:php"><?php
#module/Building/src/Building/Model/Building.php (wrong)
namespace Building\Model;
class Building
{
protected $_brick;
public function __construct($brick)
{
$this->_brick = $brick;
}
public function getBrick()
{
return $this->_brick;
}</pre>
<br />
In order to have multiple <i>Brick</i>s instantiated from within <i>Building</i>, we'll need to pass in the ability for <i>Building </i>to do so on its own. This can be done by passing in a <i>BrickFactory</i>. The factory is another closure, so we will have a closure within a closure. Notice that the runtime parameter <i>$color</i> is able to be passed in for the <i>Brick</i> construction<br />
<br />
<pre class="prettyprint brush:php"> #module/Building/Module.php excerpt
public function getServiceConfig()
{
return array(
'factories'=>array(
'BrickFactory'=>function($sm)
{
$factory = function ($color=null)
{
$brick = new Model\Brick($color);
return $brick;
};
return $factory;
},
//...</pre>
<br />
The <i>$factory</i> in this <i>BrickFactory </i>service doesn't need access to the <i>ServiceManager </i>in this example, but to imagine the <i>Brick </i>class having a <i>BrickMapper</i> dependency. If you are following along, don't copy this into your code.<br />
<br />
<pre class="prettyprint brush:php"> #module/Building/Module.php excerpt (aside)
public function getServiceConfig()
{
return array(
'factories'=>array(
'BrickFactory'=>function($sm)
{
$factory = function ($color=null) use ($sm)
{
$mapper = $sm->get('BrickMapper');
$brick = new Model\Brick($mapper, $color);
return $brick;
};
return $factory;
},
'BrickMapper'=>function($sm)
{
$factory = $sm->get('BrickFactory');
$mapper = new Model\BrickMapper($factory);
return $mapper;
},
'Brick'=>function($sm)
{
$factory = $sm->get('BrickFactory');
$model = $factory->__invoke();
return $model;
},
//...</pre>
Luckily, doing it this way does not create circular dependencies, even though the mapper uses the factory, and the factory uses the mapper. Notice also that if we needed a single <i>Brick </i>model in some other class, we can create a brick for the <i>ServiceManager </i>using the <i>BrickFactory</i>, albeit without runtime construction parameters.<br />
<br />
The <i>use</i> keyword is what captures the <i>ServiceManager </i>for use later, when the factory is invoked. Without it, the factory would be unable to create the <i>BrickMapper</i>, because the scope of the closure is limited to itself.<br />
Anyway, back to creating <i>Brick</i>s. We can now modify the <i>Building </i>class to use the <i>BrickFactory </i>to create new <i>Brick</i>s. The factory closure needs be called, or 'invoked'. This can be done with either $factory() or $factory->__invoke(). I prefer the latter, as it is slightly less mysterious what is going on. The parameters inside the parenthesis are passed to the <i>$factory</i> closure.<br />
<br />
<pre class="prettyprint brush:php"> #module/Building/src/Building/Model/Building.php excerpt
public function __construct($brickFactory)
{
$this->_brickFactory = $brickFactory;
}
public function getNewBrick($color=null)
{
$factory = $this->_brickFactory;
$brick = $factory->__invoke($color);
return $brick;
}
public function addLayer($color=null)
{
$layer = array();
for ($i=1; $i<=6; $i++)
{
$brick = $this->getNewBrick();
if ($color === null)
{
//constructed with the default color and then initialized
$brick = $this->getNewBrick();
$brick->setColor($brick->getRandomColor());
}
else
{
//constructed fully initialized with runtime
// parameter (usually preferred)
$brick = $this->getNewBrick($color);
}
$layer[] = $brick;
}
$this->_bricks[]=$layer;
}
</pre>
<br />
What we've just done can be difficult to comprehend if closures are new to you. They are a tricky concept, but once understood, can be used to create such elegant code. Leave me a comment if this needs elaboration. For now, we are done with the conceptual stuff. Continue reading to get everything running.<br />
Next, we need to configure and inject this dependency. While we're here, lets do the same thing for the <i>ViewModel</i> that the controller needs.<br />
<br />
<pre class="prettyprint brush:php"> #module/Building/Module.php excerpt
public function getServiceConfig()
{
return array(
'factories'=>array(
//...
'Building'=>function($sm)
{
$factory = $sm->get('BrickFactory');
$building = new Model\Building($factory);
return $building;
},
'ViewFactory'=>function($sm)
{
$factory = function($variables=null, $options=null)
{
$viewModel = new \Zend\View\Model\ViewModel($variables,
$options);
return $viewModel;
};
return $factory;
},
//...
<span style="font-family: 'Times New Roman'; white-space: normal;"></span></pre>
<span style="font-family: 'Times New Roman';">And finally, add those dependencies to the constructors in their respective classes</span><br />
<pre class="prettyprint brush:php"> #module/Building/Module.php excerpt
public function getControllerConfig()
{
return array('factories' => array(
'Building\Controller\Building' => function ($sm)
{
$building = $sm->getServiceLocator()->get('Building');
$viewFactory = $sm->getServiceLocator()->get('ViewFactory');
$controller = new Controller\BuildingController(
$building, $viewFactory);
return $controller;
}
));
}</pre>
<pre class="prettyprint brush:php"> #module/Building/src/Building/Controller/BuildingController.php excerpt
public function getViewModel($variables = null, $options = null)
{
return $this->_viewFactory->__invoke($variables, $options);
}
public function indexAction()
{
$building = $this->getBuilding();
$building->addLayer('blue');
$building->addLayer();
$building2 = $this->getBuilding(); //gets same instance
$building2->addLayer('green');
$building2->addLayer();
$viewModel = $this->getViewModel(array('building'=>$building));
return $viewModel;
}
</pre>
<pre class="prettyprint brush:php"><?php
#module/Building/src/Building/Model/Building.php
namespace Building\Model;
class Building
{
protected $_brickFactory;
protected $_bricks;
public function __construct($brickFactory)
{
$this->_brickFactory = $brickFactory;
}
public function getNewBrick($color=null)
{
$factory = $this->_brickFactory;
$brick = $factory->__invoke($color);
return $brick;
}
public function addLayer($color=null)
{
$layer = array();
for ($i=1; $i<=6; $i++)
{
$brick = $this->getNewBrick();
if ($color === null)
{
$brick->setColor($brick->getRandomColor());
}
else
{
$brick->setColor($color);
}
$layer[] = $brick;
}
$this->_bricks[]=$layer;
}
public function getBricks()
{
return $this->_bricks;
}
}
</pre>
<pre class="prettyprint brush:php"><?php #module/Building/view/building/building/index.pthml ?>
<table >
<?php
foreach ($this->building->getBricks() as $key=>$layer)
{
echo '<tr>';
foreach ($layer as $brick)
{
echo '<td style="text-align:center; border:1px solid black; '.
'background-color:' . $brick->getColor() . '">';
echo $brick->getColor();
//echo spl_object_hash($brick);
echo '</td>';
}
echo '</tr>';
}
?></pre>
<br />
Now you can go to http://localhost/building to see that everything works as expected. You should see 4 layers: a green, a blue, and 2 with random colored bricks. Go to <a href="https://github.com/rwilson04/zf2-dependency-injection/tree/part2">https://github.com/rwilson04/zf2-dependency-injection/tree/part2</a> for complete code.<br />
For production, you might want to convert the closures in the <i>'factories' </i>key to classes, as suggested in <a href="http://www.michaelgallego.fr/blog/2013/01/21/some-tips-to-write-better-zend-framework-2-modules/" target="_blank">this article</a>.<br />
I'm still trying to figure out how to include initializers, and have them work on the objects created by the closures. If you have a solution, let me know.<br />
Another thing to consider would be type-hinting. For anything that is swappable, you would define an interface and add that to the method definitions. For other types, I'm not sure yet whether including type-hinting is the right way to go.<br />
Thanks for reading. I hope this helped. Leave me a comment, especially if there is something missing or something that needs clarification.<br />
<div>
<br /></div>
Anonymoushttp://www.blogger.com/profile/18205730894565091028noreply@blogger.com13tag:blogger.com,1999:blog-1886482326110756670.post-34034099926174079852013-09-11T10:30:00.001-07:002014-09-11T12:04:34.142-07:00Using the ServiceManager as an Inversion of Control Container (Part 1)Intro:<br />
<br />
In Zend Framework 1, it was difficult to follow best practices when it came to writing testable code. Sure, you could make testable models, but once you need those models in a controller, what do you do? Zend Framework 2 makes it much easier. In this post, I'll cover the basics of injecting a model into a controller.<br />
<br />
Motivation:<br />
<br />
The main goal here is to be able to wire up and configure your application from the highest level possible. Constructor injection + inversion of control makes it easy to determine which classes are dependent on other classes, and swapping classes with mocks or stubs is a breeze.<br />
<br />
Prerequisites:<br />
<ul>
<li>Basic understanding of what we are trying to accomplish, namely, dependency injection using the ServiceManager as an inversion of control container</li>
<li>Looked through/tested ZF2's <a href="https://github.com/zendframework/ZendSkeletonApplication" target="_blank">Skeleton Application</a> and <a href="http://framework.zend.com/manual/2.2/en/user-guide/overview.html" target="_blank">Getting Started guide</a>.</li>
<li>Cursory knowledge of <a href="http://php.net/manual/en/functions.anonymous.php" target="_blank">closures/callbacks</a></li>
</ul>
Begin by creating a new <i>Building</i>module, with its directory structure, similar to creating the <i>Album</i> module in the <a href="http://framework.zend.com/manual/2.2/en/user-guide/modules.html" target="_blank">Getting Started guide</a>.<br />
<br />
To create objects, first let's take a look at what we are trying to get away from.<br />
<br />
<pre class="prettyprint brush:php"> $building = new \Building\Model\Building();</pre>
<br />
This is bad. At least in a controller, or another model. No more <i>new</i> anything (except maybe <i>Exception</i>s...), no more hard-coded class names. Dependencies don't belong at this level of the code. Zend Framework 2's ServiceManager allows you to programmatically configure your dependencies. You should already be at least vaguely familiar with higher level dependency configuration from the <a href="http://framework.zend.com/manual/2.2/en/user-guide/modules.html">ZF2 Getting Started guide</a>, as this is how the <i>AlbumController</i> is configured, though it does a lot of stuff behind the scenes.<br />
<br />
<pre class="prettyprint brush:php"> $sm = $this->getServiceLocator();
$building = $sm->get('Building');</pre>
<br />
This approach is better, but you still don't want to put this in the controller. You should avoid pulling in classes using the <i>ServiceManager</i> anywhere except factories. Put another way, in general, <i>$sm->get()</i> does not belong in a controller or model, but does belong in <i>Module.php</i>'s <i>factories</i> array, which I'll demonstrate further down. A class full of objects created this way has a lot of "soft dependencies", which are difficult to document and keep track of. Instead, to 'inject' a dependency into a class, it should be a parameter in the construction. Let's start by creating the <i>BuildingController </i>with a <i>Building </i>model as a dependency, which we'll learn how to inject shortly. I usually store dependencies as instance variables during construction and access them when needed with 'getters'. In this example, we are still instantiating a new <i>ViewModel</i> from within the controller. We'll fix this in Part 2, where we learn how to use the <i>ServiceManager</i> to create objects with runtime dependencies (such as the <i>$variables</i> parameter in the <i>ViewModel </i>construction)<br />
<br />
<pre class="prettyprint brush:php">
<?php
#module/Building/src/Building/Controller/BuildingController.php
namespace Building\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class BuildingController extends AbstractActionController
{
protected $_building;
public function __construct($building)
{
$this->_building = $building;
}
protected function _getBuilding()
{
return $this->_building;
}
protected function _getViewModel($variables=null)
{
#we'll fix this later
return new \Zend\View\Model\ViewModel($variables);
}
public function indexAction()
{
$building = $this->_getBuilding();
$building->addLayer('blue');
$building->addLayer('red');
$viewModel = $this->_getViewModel(array('building'=>$building));
return $viewModel;
}
}</pre>
<br />
The Application's <i>ServiceManager</i> is responsible for creating services (the model and controller are both services). Internally, when tasked with creating a service with a particular name, <i>Building\Controller\Building</i> for example, it runs <i>canCreate("Building\Controller\Building")</i>, which checks all of your aggregated config arrays. One of the places it checks by default is the array returned by the method <i>getControllerConfig()</i> in <i>Module.php</i>, if it exists. This is where we can add the factory closure for the model that the controller needs access to.<br />
<br />
<pre class="prettyprint brush:php"> #module/Building/Module.php excerpt
public function getControllerConfig()
{
return array('factories' => array(
'Building\Controller\Building' => function ($sm)
{
$building = $sm->getServiceLocator()->get('Building');
$controller = new Controller\BuildingController(
$building, $viewFactory);
return $controller;
}
));
}
</pre>
Creating controllers is actually handled by the ControllerManager, a (sub)subclass of the main ServiceManager, and $usePeeringServiceManagers is set to false, which is why we need <i>$sm->getServiceLocator()->get()</i> here instead of just <i>$sm->get()</i>; it needs to retrieve the main ServiceManager to have access to the rest of the application's services.<br />
<br />
In this controller, the constructor triggers the instantiation of the <i>Building</i> model. The model isn't actually created until the controller's constructor runs, since it is not a model being passed in, but a factory. The factory is a closure, a function without a name which can be saved as a variable. The <i>ServiceManager</i> sets services as 'shared' by default, meaning that the first time it is referenced, it calls the closure creating the object, and every time after that it simply returns the already instantiated object.<br />
<br />
It is important to note that if the name of controller configuration (in this case <i>Building\Controller\Building</i>) is listed here, it cannot be defined somewhere else as well. For example, if it is in the <i>$config['controllers']['invokables']</i> section in your <i>module.config.php</i>, the <i>ServiceManager </i>will try to 'invoke' it, that is, construct it with no arguments, and will fail. If you are following along with your code, check if this is the case now, and fix it if necessary. I'll wait.<br />
<br />
Let's create the Building model as a simple class with no dependencies for now.<br />
<br />
<pre class="prettyprint brush:php">
<?php
#module/Building/src/Building/Model/Building.php
namespace Building\Model;
class Building
{
protected $_bricks=array();
public function addLayer($color)
{
$bricks = array($color, $color, $color);
$this->_bricks[] = $bricks;
}
public function getBricks()
{
return $this->_bricks;
}
}
</pre>
<br />
Because there are no dependencies or other required constructor arguments, we can define Building\Model\Building as an <i>invokable </i>in <i>Module.php</i>. The getServiceConfig() method is one of the aggregated configs that the <i>ServiceManager</i> checks to see which services it can create, and you should recognize it from the Getting Started guide.<br />
<br />
<pre class="prettyprint brush:php">
#module/Building/Module.php excerpt
public function getServiceConfig()
{
return array(
'invokables'=>array(
'Building'=>'Building\Model\Building',
),
);
}</pre>
<br />
<br />
ZF2 provides multiple ways of doing a lot of things. It is possible, for example, to separate the configuration into multiple files, which might be advisable once your wiring starts getting complicated. The factories can be their own classes which implement 'FactoryInterface' instead of closures defined in the config array.<br />
<br />
But, we are done for now. Control has now been inverted. We are injecting a dependency (<i>Building</i>) into a controller using the ServiceManager, and all of our wiring and configuration is in one place, <i>Module.php</i>.<br />
<br />
If you want to test it out now and get something running, read the rest of this post. Otherwise, check out <span style="background-color: white;"><a href="http://zendblog.shinymayhem.com/2013/09/using-servicemanager-as-inversion-of_11.html">Part 2</a></span> to learn how to inject a dependency where each instance needs to be distinct and have its own configuration, e.g. using a <i>$model->findAll()</i> or similar.<br />
<br />
You'll need to create a view<br />
<br />
<pre class="prettyprint brush:php">
<?php
#module/Buidling/view/building/building/index.phtml
?>
<table >
<?php
foreach ($this->building->getBricks() as $key=>$layer)
{
echo '<tr>';
foreach ($layer as $brick)
{
echo '<td style="text-align:center; border:1px solid black;'.
' background-color:' . $brick . ';">';
echo $brick;
echo '</td>';
}
echo '</tr>';
}
?>
</table></pre>
<br />
And then be sure to set up a route to <i>/building</i> so it is accessible.<br />
<pre class="prettyprint brush:php">
<?php
#module/Building/config/module.config.php</pre>
<pre class="prettyprint brush:php">return array(
'router' => array(
'routes' => array(
'building' => array(
'type' => 'segment',
'options' => array(
'route' => '/building[/:action]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
'controller' => 'Building\Controller\Building',
'action' => 'index',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'building' => __DIR__ . '/../view',
),
),
);</pre>
<pre class="prettyprint brush:php"></pre>
Don't forget the getConfig() and getAutoloaderConfig() methods in your Module.php.<br />
<br />
<pre class="prettyprint brush:php">
#module/Building/Module.php excerpt
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}</pre>
<br />
Finally, just go to http://localhost/building, and you should see a simple empty page with the layers we added.<br />
Check out <a href="https://github.com/rwilson04/zf2-dependency-injection/tree/part1">https://github.com/rwilson04/zf2-dependency-injection/tree/part1</a> for the code.<br />
<br />
Here's the link to <a href="http://techblog.shinymayhem.com/2013/09/using-servicemanager-as-inversion-of_11.html">Part 2</a> again.<br />
<br />
Comments are welcome.<br />
<div>
<br /></div>
Anonymoushttp://www.blogger.com/profile/18205730894565091028noreply@blogger.com65tag:blogger.com,1999:blog-1886482326110756670.post-59056625684644493132013-02-05T16:03:00.000-08:002013-02-05T16:03:49.522-08:00Custom Format for Date ValidatorThe Date validator, when created with no 'format' option, will default to 'yyyy-mm-dd', or as a PHP format string, 'Y-m-d'. If you want to use a custom date format, the Date validator doesn't seem to be quite adequate. It relies on PHP being able to create a valid date with<br />
<br />
<pre class="prettyprint brush:php">DateTime::createFromFormat($format, $value)</pre>
<br />
It will also blindly validate any pure integer inputs, assuming no error is thrown when attempting to create a date with that integer as a timestamp. So, if you want a date to be in the format (mm-dd-yyyy), you would use the format string 'm-d-Y', but there are a lot of invalid dates that will still pass. For example, if someone enters '12-12-99', it will validate. While it is easy for a human to look at that and know that they probably meant 1999, your date parser might not handle it well (what you do with the validated date is up to you). For proper input validation in a case like this, you really need a regex validator (the default format checks against a regex behind the scenes, but not for custom formats).<br />
<pre class="prettyprint brush:php"> 'validators'=>array(
array(
'name'=>'Date',
'break_chain_on_failure'=>true,
'options'=>array(
'format'=>'m-d-Y',
'messages'=>array(
'dateFalseFormat'=>'Invalid date format, must be mm-dd-yyy',
'dateInvalidDate'=>'Invalid date, must be mm-dd-yyy'
),
),
),
array(
'name'=>'Regex',
'options'=>array(
'messages'=>array('regexNotMatch'=>'Invalid date format, must be mm-dd-yyy'),
'pattern'=>'/^\d{1,2}-\d{1,2}-\d{4}$/',
),
),
),
</pre>
This particular regex will work with 1 or 2 digit days and months, because when I parse the date while handling the posted form data, I accept that format as well.<br />
Note that I set a few custom error messages. 'dateInvalidDate' should display when a date like 13-13-1999 is entered, while the other message should show up for anything that doesn't match the specified format. 'break_chain_on_failure' is needed to prevent duplicate errors from displaying when the format is invalid.<br />
<br />Anonymoushttp://www.blogger.com/profile/18205730894565091028noreply@blogger.com4tag:blogger.com,1999:blog-1886482326110756670.post-920777125203324532012-09-28T14:14:00.001-07:002014-09-11T12:04:53.699-07:00(outdated) Using the ServiceManager as an Inversion of Control Container (Part 2) <h3>
<span style="color: #cc0000;">Note: This post has been superseded. Check out the </span><a href="http://techblog.shinymayhem.com/2013/09/using-servicemanager-as-inversion-of.html">Part 1</a><span style="color: #cc0000;"> and </span><a href="http://techblog.shinymayhem.com/2013/09/using-servicemanager-as-inversion-of_11.html">Part 2</a><span style="color: #cc0000;">. The post you are reading now is outdated, and there is a much better way of doing this, in my opinion. </span></h3>
<h3>
<div style="font-size: medium; font-weight: normal;">
<b style="color: #666666;">Note about superseded posts:</b><span style="color: #666666;"> The new Part 1's changes mostly involve coding style and use of design patterns. In the outdated Part 2, I attempted to instantiate multiple instances of objects using the </span><i style="color: #666666;">AbstractPluginManager</i><span style="color: #666666;">. This worked for simple things, but got complicated when one of the non-shared instantiated instances had dependencies. The best I could work out involved creating factory factories, which created the required factories, which then created the model I wanted. Even without dependencies, the </span><i style="color: #666666;">AbstractPluginManager<span style="background-color: white; font-family: 'Trebuchet MS', Trebuchet, Verdana, sans-serif; font-size: 13px; line-height: 18px;"> </span></i><span style="color: #666666;">method still required creating those extra factory classes for the sole purpose of creating objects, which leads to a plethora of files in bigger applications. Another deficiency of the outdated version is that it still has hard-coded dependencies in the form of type-hinting.</span></div>
<div style="font-size: medium; font-weight: normal;">
<span style="color: #666666;"><br /></span>In the <a href="http://techblog.shinymayhem.com/2012/09/using-servicemanager-as-inversion-of.html" target="_blank">Part 1</a>, we learned how to inject a single, shared dependency into the controller using the ServiceManager as an inversion of control container, but what if you need multiple instances of a class? For example, what if you needed multiple Brick objects to build that Building?<br />
<br />
<blockquote class="tr_bq">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"><?php</span><br />
<span style="color: black; font-weight: bold;">namespace</span> Building\Model<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">class</span> Brick<br />
<span style="color: #009900;">{</span><br />
<span style="color: black; font-weight: bold;">protected</span> <span style="color: #000088;">$color</span><span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> __construct<span style="color: #009900;">(</span><span style="color: #000088;">$options</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #b1b100;">if</span> <span style="color: #009900;">(</span><span style="color: #339933;">!</span><a href="http://www.php.net/is_array" style="color: #000060;"><span style="color: #990000;">is_array</span></a><span style="color: #009900;">(</span><span style="color: #000088;">$options</span><span style="color: #009900;">)</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #b1b100;">throw</span> <span style="color: black; font-weight: bold;">new</span> \DomainException<span style="color: #009900;">(</span><span style="color: blue;">'$options must be an array'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> <br />
<span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">color</span> <span style="color: #339933;">=</span> <span style="color: #009900;">(</span><a href="http://www.php.net/empty" style="color: #000060;"><span style="color: #990000;">empty</span></a><span style="color: #009900;">(</span><span style="color: #000088;">$options</span><span style="color: #009900;">[</span><span style="color: blue;">'color'</span><span style="color: #009900;">]</span><span style="color: #009900;">)</span><span style="color: #009900;">)</span>?<span style="color: blue;">"default"</span><span style="color: #339933;">:</span><span style="color: #000088;">$options</span><span style="color: #009900;">[</span><span style="color: blue;">'color'</span><span style="color: #009900;">]</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> setColor<span style="color: #009900;">(</span><span style="color: #000088;">$color</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">color</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$color</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> getColor<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">color</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<span style="color: #009900;">}</span><br />
~ </div>
</blockquote>
<br />
That is where the AbstractPluginManager comes in. It extends ServiceManager, but its get() method allows an extra parameter, $options, which allows you to initialize each new instance with a unique configuration. Even though it is simplified and only needs one option, I set up the Brick class to match this pattern, allowing the $options array to be passed in to the constructor. I'll get to the implementation of the AbstractPluginManager after a little more setup.<br />
<br />
Lets add a BrickFactory class as a dependency for our Building. Adding a little functionality for actually doing something with the Bricks, Building now looks like this:<br />
<br />
<blockquote class="tr_bq">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"><?php</span><br />
<span style="color: black; font-weight: bold;">namespace</span> Building\Model<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">class</span> Building<br />
<span style="color: #009900;">{</span><br />
<span style="color: black; font-weight: bold;">protected</span> <span style="color: #000088;">$brickFactory</span><span style="color: #339933;">;</span><br />
<span style="color: black; font-weight: bold;">protected</span> <span style="color: #000088;">$bricks</span><span style="color: #339933;">;</span><br />
<span style="color: black; font-weight: bold;">protected</span> <span style="color: #000088;">$colors</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: blue;">"red"</span><span style="color: #339933;">,</span> <span style="color: blue;">"brown"</span><span style="color: #339933;">,</span> <span style="color: blue;">"black"</span><span style="color: #339933;">,</span> <span style="color: blue;">"yellow"</span><span style="color: #339933;">,</span> <span style="color: blue;">"orange"</span><span style="color: #339933;">,</span> <span style="color: blue;">"purple"</span><span style="color: #339933;">,</span> <span style="color: blue;">"green"</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> __construct<span style="color: #009900;">(</span>BrickFactory <span style="color: #000088;">$brickFactory</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">brickFactory</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$brickFactory</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> getNewBrick<span style="color: #009900;">(</span><span style="color: #000088;">$color</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #000088;">$factory</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">brickFactory</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$brick</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$factory</span><span style="color: #339933;">-></span><span style="color: #004000;">get</span><span style="color: #009900;">(</span><span style="color: blue;">'Brick'</span><span style="color: #339933;">,</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: blue;">'color'</span><span style="color: #339933;">=></span><span style="color: #000088;">$color</span><span style="color: #009900;">)</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$brick</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> addLayer<span style="color: #009900;">(</span><span style="color: #000088;">$color</span><span style="color: #339933;">=</span><span style="color: #009900; font-weight: bold;">null</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #000088;">$layer</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">for</span> <span style="color: #009900;">(</span><span style="color: #000088;">$i</span><span style="color: #339933;">=</span><span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span><span style="color: #339933;"><</span><span style="color: #cc66cc;">6</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span><span style="color: #339933;">++</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #000088;">$newBrickColor</span> <span style="color: #339933;">=</span> <span style="color: #009900;">(</span><span style="color: #000088;">$color</span> <span style="color: #339933;">===</span> <span style="color: #009900; font-weight: bold;">null</span><span style="color: #009900;">)</span>?<span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">colors</span><span style="color: #009900;">[</span><a href="http://www.php.net/array_rand" style="color: #000060;"><span style="color: #990000;">array_rand</span></a><span style="color: #009900;">(</span><span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">colors</span><span style="color: #009900;">)</span><span style="color: #009900;">]</span><span style="color: #339933;">:</span><span style="color: #000088;">$color</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$brick</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">getNewBrick</span><span style="color: #009900;">(</span><span style="color: #000088;">$newBrickColor</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$layer</span><span style="color: #009900;">[</span><span style="color: #009900;">]</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$brick</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> <br />
<span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">bricks</span><span style="color: #009900;">[</span><span style="color: #009900;">]</span><span style="color: #339933;">=</span><span style="color: #000088;">$layer</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> getBricks<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">bricks</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<br />
<span style="color: #009900;">}</span></div>
</blockquote>
<br />
With the new constructor, you can no longer instantiate a Building without a BrickFactory, so it can no longer be an invokable in the serviceConfig. We also need to specify how to create a BrickFactory. Update Module.php's getServiceConfig() method to look like this:<br />
<br />
<blockquote>
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> getServiceConfig<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #b1b100;">return</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><br />
<span style="color: blue;">'factories'</span><span style="color: #339933;">=></span>array<span style="color: #009900;">(</span><br />
<span style="color: blue;">'ViewFactory'</span><span style="color: #339933;">=></span>function<span style="color: #009900;">(</span><span style="color: #000088;">$sm</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #000088;">$viewFactory</span> <span style="color: #339933;">=</span> <span style="color: black; font-weight: bold;">new</span> Model\ViewFactory<span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$viewFactory</span><span style="color: #339933;">-></span><span style="color: #004000;">setInvokableClass</span><span style="color: #009900;">(</span><span style="color: blue;">'ViewModel'</span><span style="color: #339933;">,</span> <span style="color: blue;">'Zend\View\Model\ViewModel'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$viewFactory</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><span style="color: #339933;">,</span> <br />
<span style="color: blue;">'BrickFactory'</span><span style="color: #339933;">=></span>function<span style="color: #009900;">(</span><span style="color: #000088;">$sm</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #000088;">$brickFactory</span> <span style="color: #339933;">=</span> <span style="color: black; font-weight: bold;">new</span> Model\BrickFactory<span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$brickFactory</span><span style="color: #339933;">-></span><span style="color: #004000;">setInvokableClass</span><span style="color: #009900;">(</span><span style="color: blue;">'Brick'</span><span style="color: #339933;">,</span> <span style="color: blue;">'Building\Model\Brick'</span><span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">false</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><span style="color: #666666; font-style: italic;"><br /></span> <span style="color: #b1b100;">return</span> <span style="color: #000088;">$brickFactory</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><span style="color: #339933;">,</span> <br />
<span style="color: blue;">'Building'</span><span style="color: #339933;">=></span>function<span style="color: #009900;">(</span><span style="color: #000088;">$sm</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #000088;">$pluginManager</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$sm</span><span style="color: #339933;">-></span><span style="color: #004000;">get</span><span style="color: #009900;">(</span><span style="color: blue;">'BrickFactory'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$building</span> <span style="color: #339933;">=</span> <span style="color: black; font-weight: bold;">new</span> Model\Building<span style="color: #009900;">(</span><span style="color: #000088;">$pluginManager</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$building</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><span style="color: #339933;">,</span> <br />
<span style="color: #009900;">)</span><span style="color: #339933;">,</span> <br />
<span style="color: #009900;">)</span><span style="color: #339933;">;</span> <br />
<span style="color: #009900;">}</span></div>
</blockquote>
Notice that we also added a ViewFactory so that the ViewModel is not a hard dependency in the controller. Isn't it convenient that the ViewModel fits into this pattern, as an invokable with an optional array constructor parameter?<br />
The difference between these two factories is that ViewModel will be shared, meaning every time you call $factory->get('ViewModel') it will return the same instance, while Brick will return a brand new instance every time. The third parameter in the setInvokableClass function sets whether the class is shared, and defaults to true.<br />
You might see examples in the ZF2 source where invokeableClasses is set directly in the class implementing abstractPluginManager, like the following:<br />
<br />
<blockquote>
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: #000088;">$protected</span> invokableClasses <span style="color: #339933;">=</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: blue;">'db', 'firephp'</span><span style="color: #009900;">);</span></div>
</blockquote>
I'd recommend using the setter, as I did in Module.php, as it takes care of canonicalization of the name, and you can use the same name format that you use everywhere else (e.g. 'ViewModel' instead of 'viewmodel').<br />
<br />
So what is this BrickFactory? It is simply a class to create new Bricks. The key to having it work is the fact that AbstractPluginManager extends ServiceManager, which means it can have its own invokable classes and factories.This means that when we ask it for a Brick, it sees it as an invokable class and instantiates it (since we specified it as invokable in the closure), including the $options array in the constructor if it was specified. The implementation is as simple as this:<br />
<br />
<blockquote class="tr_bq">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"><?php</span><br />
<span style="color: black; font-weight: bold;">namespace</span> Building\Model<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">use</span> Zend\ServiceManager\AbstractPluginManager<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">class</span> BrickFactory <span style="color: black; font-weight: bold;">extends</span> AbstractPluginManager<br />
<span style="color: #009900;">{</span><br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> validatePlugin<span style="color: #009900;">(</span><span style="color: #000088;">$plugin</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #b1b100;">if</span> <span style="color: #009900;">(</span><span style="color: #000088;">$plugin</span> instanceof Brick<span style="color: #009900;">)</span> <span style="color: #666666; font-style: italic;"></span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #b1b100;">return</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> <br />
<span style="color: #b1b100;">throw</span> <span style="color: black; font-weight: bold;">new</span> \DomainException<span style="color: #009900;">(</span><span style="color: blue;">'Invalid Brick Implementation'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<br />
<span style="color: #009900;">}</span></div>
</blockquote>
The only function we are required to implement is validatePlugin, which checks to see if it is the correct type, and any other checking you need to do.<br />
The ViewFactory is nearly identical:<br />
<br />
<blockquote class="tr_bq">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"><?php</span><br />
<span style="color: black; font-weight: bold;">namespace</span> Building\Model<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">use</span> Zend\ServiceManager\AbstractPluginManager<span style="color: #339933;">;</span><br />
<span style="color: black; font-weight: bold;">use</span> Zend\View\Model\ViewModel<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">class</span> ViewFactory <span style="color: black; font-weight: bold;">extends</span> AbstractPluginManager<br />
<span style="color: #009900;">{</span><br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> validatePlugin<span style="color: #009900;">(</span><span style="color: #000088;">$plugin</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #b1b100;">if</span> <span style="color: #009900;">(</span><span style="color: #000088;">$plugin</span> instanceof ViewModel<span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #b1b100;">return</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> <br />
<span style="color: #b1b100;">throw</span> <span style="color: black; font-weight: bold;">new</span> \DomainException<span style="color: #009900;">(</span><span style="color: blue;">'Invalid ViewModel Implementation'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> <br />
<br />
<span style="color: #009900;">}</span></div>
</blockquote>
<br />
Finally, here is the new BuildingController and the new index.phtml<br />
<br />
<blockquote class="tr_bq">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"><?php</span><br />
<br />
<span style="color: black; font-weight: bold;">namespace</span> Building\Controller<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">use</span> Zend\Mvc\Controller\AbstractActionController<span style="color: #339933;">;</span><br />
<span style="color: black; font-weight: bold;">use</span> Building\Model\Building<span style="color: #339933;">;</span><br />
<span style="color: black; font-weight: bold;">use</span> Building\Model\ViewFactory<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">class</span> BuildingController <span style="color: black; font-weight: bold;">extends</span> AbstractActionController<br />
<span style="color: #009900;">{</span><br />
<span style="color: black; font-weight: bold;">protected</span> <span style="color: #000088;">$building</span><span style="color: #339933;">;</span><br />
<span style="color: black; font-weight: bold;">protected</span> <span style="color: #000088;">$viewFactory</span><span style="color: #339933;">;</span><br />
<br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> __construct<span style="color: #009900;">(</span>Building <span style="color: #000088;">$building</span><span style="color: #339933;">,</span> ViewFactory <span style="color: #000088;">$viewFactory</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">building</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$building</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">viewFactory</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$viewFactory</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> getBuilding<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">building</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> getViewFactory<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">viewFactory</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> indexAction<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #000088;">$building</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">getBuilding</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$building</span><span style="color: #339933;">-></span><span style="color: #004000;">addLayer</span><span style="color: #009900;">(</span><span style="color: blue;">'blue'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$building</span><span style="color: #339933;">-></span><span style="color: #004000;">addLayer</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$building2</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">getBuilding</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">//gets same instance</span><br />
<span style="color: #000088;">$building2</span><span style="color: #339933;">-></span><span style="color: #004000;">addLayer</span><span style="color: #009900;">(</span><span style="color: blue;">'green'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$building2</span><span style="color: #339933;">-></span><span style="color: #004000;">addLayer</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$viewModel</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">getViewFactory</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">-></span><span style="color: #004000;">get</span><span style="color: #009900;">(</span><span style="color: blue;">'ViewModel'</span><span style="color: #339933;">,</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: blue;">'building'</span><span style="color: #339933;">=></span><span style="color: #000088;">$building</span><span style="color: #009900;">)</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$viewModel</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<span style="color: #009900;">}</span> </div>
</blockquote>
<br />
<blockquote class="tr_bq">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"><?php</span><br />
<span style="color: #b1b100;">foreach</span> <span style="color: #009900;">(</span><span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">building</span><span style="color: #339933;">-></span><span style="color: #004000;">getBricks</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$key</span><span style="color: #339933;">=></span><span style="color: #000088;">$layer</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #b1b100;">echo</span> <span style="color: blue;">'<tr>'</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">foreach</span> <span style="color: #009900;">(</span><span style="color: #000088;">$layer</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$brick</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #b1b100;">echo</span> <span style="color: blue;">'<td style="text-align:center; border:1px solid black">'</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">echo</span> <span style="color: #000088;">$brick</span><span style="color: #339933;">-></span><span style="color: #004000;">getColor</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">echo</span> <span style="color: blue;">'</td>'</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<span style="color: #b1b100;">echo</span> <span style="color: blue;">'</tr>'</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<span style="color: black; font-weight: bold;">?></span></div>
</blockquote>
And update the getControllerConfig method to accomodate the new constructor (adding the viewFactory)<br />
<br />
<br />
<blockquote class="tr_bq">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"></span><span style="color: #6aa84f;">#Module.php</span><br />
<span style="color: black; font-weight: bold;"> public</span> <span style="color: black; font-weight: bold;">function</span> getControllerConfig<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #b1b100;">return</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: blue;">'factories'</span> <span style="color: #339933;">=></span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><br />
<span style="color: blue;">'Building\Controller\Building'</span> <span style="color: #339933;">=></span> <span style="color: black; font-weight: bold;">function</span> <span style="color: #009900;">(</span><span style="color: #000088;">$sm</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span><br />
<span style="color: #000088;">$building</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$sm</span><span style="color: #339933;">-></span><span style="color: #004000;">getServiceLocator</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">-></span><span style="color: #004000;">get</span><span style="color: #009900;">(</span><span style="color: blue;">'Building'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
$viewfactory = $sm->getServiceLocator()->get(<span style="color: blue;">'ViewFactory'</span>)<span style="color: #339933;">;</span><br />
<span style="color: #000088;">$controller</span> <span style="color: #339933;">=</span> <span style="color: black; font-weight: bold;">new</span> Controller\BuildingController<span style="color: #009900;">(</span><span style="color: #000088;">$building, $viewFactory</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$controller</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<span style="color: #009900;">)</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span><br />
<div>
<br /></div>
</div>
</blockquote>
<br />
And we're done. Now you can go to http://localhost/building to see the building that you built. It should have 2 layers with random colors, and 2 layers with fixed colors. Try setting $shared to true in the BrickFactory closure and see what happens.<br />
<br />
Hopefully this is enough to get you started writing your applications with injected dependencies using the ServiceManager as an inversion of control container, rather than a service locator. It will save you a lot of trouble in the long run.<br />
You can get the source code used for this project at <a href="https://github.com/rwilson04/zf2-dependency-injection">https://github.com/rwilson04/zf2-dependency-injection</a><br />
<br />
Please leave me some comments, especially if you see something I did wrong, could do better, needs clarification, etc. For example, passing a model into the view? What was I thinking? </div>
</h3>
Anonymoushttp://www.blogger.com/profile/18205730894565091028noreply@blogger.com5tag:blogger.com,1999:blog-1886482326110756670.post-21383091464697153922012-09-28T13:27:00.000-07:002014-09-11T12:05:06.872-07:00(outdated) Using the ServiceManager as an Inversion of Control Container (Part 1)<h3>
<span style="color: #cc0000;">Note: This post has been superseded. Check out the <a href="http://techblog.shinymayhem.com/2013/09/using-servicemanager-as-inversion-of.html">Part 1</a> and <a href="http://techblog.shinymayhem.com/2013/09/using-servicemanager-as-inversion-of_11.html">Part 2</a>. The post you are reading now is outdated, and there is a much better way of doing this, in my opinion. </span></h3>
<h3>
<div style="font-size: medium; font-weight: normal;">
<b style="color: #666666;">Note about superseded posts:</b><span style="color: #666666;"> The new Part 1's changes mostly involve coding style and use of design patterns. In the outdated Part 2, I attempted to instantiate multiple instances of objects using the </span><i style="color: #666666;">AbstractPluginManager</i><span style="color: #666666;">. This worked for simple things, but got complicated when one of the non-shared instantiated instances had dependencies. The best I could work out involved creating factory factories, which created the required factories, which then created the model I wanted. Even without dependencies, the </span><i style="color: #666666;">AbstractPluginManager<span style="background-color: white; font-family: 'Trebuchet MS', Trebuchet, Verdana, sans-serif; font-size: 13px; line-height: 18px;"> </span></i><span style="color: #666666;">method still required creating those extra factory classes for the sole purpose of creating objects, which leads to a plethora of files in bigger applications. Another deficiency of the outdated version is that it still has hard-coded dependencies in the form of type-hinting.</span></div>
<div style="font-size: medium; font-weight: normal;">
<span style="color: #666666;"><br /></span>In Zend Framework 1, it was difficult to follow best practices when it came to writing testable code. Sure, you could make testable models, but once you need those models in a controller, what do you do? Zend Framework 2 makes it much easier. In this post, I'll cover the basics of injecting a model into a controller.</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
Motivation:</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
The main goal here is to be able to wire up and configure your application from the highest level possible. Constructor injection + inversion of control makes it easy to determine which classes are dependent on other classes. The Getting Started guide uses the ServiceManager in the Controller to pull in the model, which creates "soft dependencies", so you can't completely tell which classes depend on other ones unless you look at the code on the lower levels. For actual testable/maintainable code, avoid this as much as possible.</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
Prerequisites:</div>
<ul style="font-size: medium; font-weight: normal;">
<li>Basic understanding of what we are trying to accomplish, namely, dependency injection using the ServiceManager as an inversion of control container</li>
<li>Looked through/tested ZF2's <a href="https://github.com/zendframework/ZendSkeletonApplication" target="_blank">Skeleton Application</a> and <a href="http://framework.zend.com/manual/2.0/en/user-guide/overview.html" target="_blank">Getting Started guide</a>.</li>
<li>Cursory knowledge of <a href="http://php.net/manual/en/functions.anonymous.php" target="_blank">closures/callbacks</a></li>
</ul>
<div style="font-size: medium; font-weight: normal;">
Begin by creating a new Building module, with a structure like the Album module in the Getting Started guide, and a route to /building so it is accessible. For the actual module code, let's start by adding the BuildingController with a Building model as a dependency, which we'll learn how to inject shortly.</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<blockquote class="tr_bq" style="font-size: medium; font-weight: normal;">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"><?php</span><br />
<span style="color: #6aa84f;">#Building/src/Building/Controller/BuildingController.php</span><br />
<span style="color: black; font-weight: bold;">namespace</span> Building\Controller<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">use</span> Building\Model\Building<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">class</span> BuildingController <b>extends</b> AbstractActionController<br />
<span style="color: #009900;">{</span><br />
<span style="color: black; font-weight: bold;">protected</span> <span style="color: #000088;">$building</span><span style="color: #339933;">; </span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> __construct<span style="color: #009900;">(</span>Building <span style="color: #000088;">$building</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">building</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$building</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> <br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> getBuilding<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">building</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> <br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> indexAction<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #000088;">$building</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">getBuilding</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$building</span><span style="color: #339933;">-></span><span style="color: #004000;">addLayer</span><span style="color: #009900;">(</span><span style="color: blue;">'red'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$viewModel</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">getViewModel</span><span style="color: #009900;">(</span><span style="color: #009900;"><a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: blue;">'building'</span><span style="color: #339933;">=></span><span style="color: #000088;">$building</span><span style="color: #009900;">)</span>)</span><span style="color: #339933;">; #we'll add this function <span style="color: #6aa84f;">later</span></span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$viewModel</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> <br />
<span style="color: #009900;">}</span></div>
</blockquote>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
Zend Framework 2's ServiceManager allows you to programmatically configure your dependencies. You should already be at least vaguely familiar with this from the ZF2 Getting Started guide. The Application's ServiceManager is responsible for creating services. Internally, when tasked with creating a service with a particular name, "Building\Controller\Building" for example, it runs canCreate("Building\Controller\Building"), which checks all of your aggregated configs. So one of the places it checks by default is the array returned by the method getControllerConfig() in Module.php. This is where we can add the factory closure for the model that the controller needs access to.</div>
<blockquote class="tr_bq" style="font-size: medium; font-weight: normal;">
<br />
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"></span><span style="color: #6aa84f;">#Module.php</span><br />
<span style="color: black; font-weight: bold;"> public</span> <span style="color: black; font-weight: bold;">function</span> getControllerConfig<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #b1b100;">return</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: blue;">'factories'</span> <span style="color: #339933;">=></span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><br />
<span style="color: blue;">'Building\Controller\Building'</span> <span style="color: #339933;">=></span> <span style="color: black; font-weight: bold;">function</span> <span style="color: #009900;">(</span><span style="color: #000088;">$sm</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #000088;">$building</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$sm</span><span style="color: #339933;">-></span><span style="color: #004000;">getServiceLocator</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">-></span><span style="color: #004000;">get</span><span style="color: #009900;">(</span><span style="color: blue;">'Building'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #000088;">$controller</span> <span style="color: #339933;">=</span> <span style="color: black; font-weight: bold;">new</span> Controller\BuildingController<span style="color: #009900;">(</span><span style="color: #000088;">$building</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">return</span> <span style="color: #000088;">$controller</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> <br />
<span style="color: #009900;">)</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> </div>
</blockquote>
<div style="font-size: medium; font-weight: normal;">
Creating controllers is actually handled by the ControllerManager, a (sub)subclass of the main ServiceManager, and $usePeeringServiceManagers is set to false, which is why here we need $sm->getServiceLocator()->get() instead of just $sm->get(); it needs to retrieve the main ServiceManager to have access to the rest of the application's services.</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
It is important to note that if the name of controller configuration (in this case Building\Controller\Building) is listed here, it cannot be defined somewhere else as well. For example, if it is in the $config['controllers']['invokables'] section in your module.config.php, the ServiceManager will try to 'invoke' it, that is, construct it with no arguments, and will fail. Check for this situation now.</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
Let's create the Building model as a simple class with no dependencies for now.</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<blockquote class="tr_bq" style="font-size: medium; font-weight: normal;">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"><?php<span style="color: #6aa84f;"></span></span><span style="color: black; font-weight: bold;"><span style="color: #6aa84f;"></span></span><br />
<span style="color: black; font-weight: bold;"><span style="color: #6aa84f;">#Building/src/Building/Model/Building.php</span></span><br />
<span style="color: black; font-weight: bold;">namespace</span> Building\Model<span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">class</span> Building<br />
<span style="color: #009900;">{</span><br />
<span style="color: black; font-weight: bold;">protected</span> <span style="color: #000088;">$colors</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: blue;">"red"</span><span style="color: #339933;">,</span> <span style="color: blue;">"brown"</span><span style="color: #339933;">,</span> <span style="color: blue;">"black"</span><span style="color: #339933;">,</span> <span style="color: blue;">"yellow"</span><span style="color: #339933;">,</span> <span style="color: blue;">"orange"</span><span style="color: #339933;">,</span> <span style="color: blue;">"purple"</span><span style="color: #339933;">,</span> <span style="color: blue;">"green"</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<br />
<span style="color: black; font-weight: bold;">public</span> <span style="color: black; font-weight: bold;">function</span> addLayer<span style="color: #009900;">(</span><span style="color: #000088;">$color</span><span style="color: #339933;">=</span><span style="color: #009900; font-weight: bold;">null</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #666666; font-style: italic;">#add either a random color brick, or the color specified</span> <span style="color: #000088;"> $newBrickColor</span> <span style="color: #339933;">=</span> <span style="color: #009900;">(</span><span style="color: #000088;">$color</span> <span style="color: #339933;">===</span> <span style="color: #009900; font-weight: bold;">null</span><span style="color: #009900;">)</span>?<span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">colors</span><span style="color: #009900;">[</span><a href="http://www.php.net/array_rand" style="color: #000060;"><span style="color: #990000;">array_rand</span></a><span style="color: #009900;">(</span><span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">colors</span><span style="color: #009900;">)</span><span style="color: #009900;">]</span><span style="color: #339933;">:</span><span style="color: #000088;">$color</span><span style="color: #339933;">;</span><br />
<span style="color: #b1b100;">echo</span> <span style="color: blue;">"Added <span style="color: #006699; font-weight: bold;">$newBrickColor</span> brick"</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;"> }<br />}</span></div>
</blockquote>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
Since there are no dependencies or other required constructor arguments, we can define Building\Model\Building as an invokable in Module.php. The getServiceConfig() method is one of the aggregated configs that the ServiceManager checks to see which services it can create, and you should recognize it from the Getting Started guide.</div>
<blockquote class="tr_bq" style="font-size: medium; font-weight: normal;">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: black; font-weight: bold;"> </span><span style="color: #6aa84f;">#Module.php</span><br />
<span style="color: black; font-weight: bold;"> public</span> <span style="color: black; font-weight: bold;">function</span> getServiceConfig<span style="color: #009900;">(</span><span style="color: #009900;">)</span><br />
<span style="color: #009900;">{</span> <br />
<span style="color: #b1b100;">return</span> <a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><br />
<span style="color: blue;">'invokables'</span><span style="color: #339933;">=></span>array<span style="color: #009900;">(</span><br />
<span style="color: blue;">'Building'</span><span style="color: #339933;">=></span><span style="color: blue;">'Building\Model\Building'</span><span style="color: #339933;">,</span><br />
<span style="color: #009900;">)</span><span style="color: #339933;">,</span><br />
<span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">}</span> </div>
</blockquote>
<div style="font-size: medium; font-weight: normal;">
That's it. Control has now been inverted. You are injecting a model into a controller using the ServiceManager, and all of your wiring and configuration is in one place. It is possible to separate the configuration into multiple files, which might be advisable once your wiring starts getting complicated. The factories can be their own classes which implement 'FactoryInterface' instead of closures defined in the config array.</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
If you want to try it out now, you'll need to replace the line in the controller</div>
<blockquote class="tr_bq" style="font-size: medium; font-weight: normal;">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: #000088;">$viewModel</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-></span><span style="color: #004000;">getViewModel</span><span style="color: #009900;">(</span><span style="color: #009900;"><a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: blue;">'building'</span><span style="color: #339933;">=></span><span style="color: #000088;">$building</span><span style="color: #009900;">)</span>)</span><span style="color: #339933;">;<span style="color: #6aa84f;"></span></span></div>
</blockquote>
<div style="font-size: medium; font-weight: normal;">
with</div>
<blockquote class="tr_bq" style="font-size: medium; font-weight: normal;">
<div class="php" style="background-color: #f0f0f0; border: 1px solid rgb(208, 208, 208); color: #000066; font-family: monospace;">
<span style="color: #000088;">$viewModel</span> <span style="color: #339933;">=</span> <span style="color: black; font-weight: bold;">new</span> ViewModel<span style="color: #009900;">(</span><a href="http://www.php.net/array" style="color: #000060;"><span style="color: #990000;">array</span></a><span style="color: #009900;">(<span style="color: blue;">'building'</span><span style="color: #339933;">=></span><span style="color: #000088;">$building</span><span style="color: #009900;">)</span>)</span><span style="color: #339933;">;</span></div>
</blockquote>
<div style="font-size: medium; font-weight: normal;">
Then just go to http://localhost/building, and if you set your routing up, it will just be an empty page with the line echo'd in the indexAction. Hint: don't forget the getConfig() and getAutoloaderConfig() methods in your Module.php</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
Check out <a href="http://techblog.shinymayhem.com/2012/09/using-servicemanager-as-inversion-of_28.html" target="">Part 2</a> to learn how to inject a dependency where each instance needs to be distinct and have its own configuration.</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
Please leave me some comments, especially if you see something I did wrong, could do better, needs clarification, etc.</div>
</h3>
Anonymoushttp://www.blogger.com/profile/18205730894565091028noreply@blogger.com21