PHP Architect - 2017 01 January
PHP Architect - 2017 01 January
PHP[TEK] 2017
The Premier PHP Conference
12th Annual Edition
May 24-26 ATLANTA
Keynote Speakers:
tek.phparch.com
JANUARY 2017
Volume 16 - Issue 1
LISTING 2
As wrong as it feels to ignore it, you cant change this behav- 01. <?php
ior. Youre just documenting the way things currently are. If 02.
you write a test which adds one and one together and your 03. function output($d) {
method returns twelve you cant say thats wrong and fix it. 04. echo json_encode($d); die;
Instead, you write your test to capture the existing behavior 05. }
and move on, writing more tests as documentation. 06.
07. function error($m, $code=400){
Does that sound a bit crazy to you? Sadly, this kind of thing 08. $codes = [
happens all the time. Once you make code publicly available, 09. 400 => 'Bad Request',
you make a commitment that its behavior wont change with- 10. 404 => 'Not found'
out prior notification. Once the code has been made public, 11. ];
the original requirements are forgotten. Right and wrong 12.
dont matter anymore, there is only what is. 13. header('HTTP/1.1 '.$code.' '.$codes[$code]);
14. output(['error' => $m]);
By writing these tests, youre capturing behavior, not
15. }
changing it. It may look broken, but someone else may be 16.
relying on that broken implementation. If you fix the imple- 17. $username = $_GET['username']
mentation to match the specification, it may break their code. 18. ?? error('username is required');
Once the code is released, it becomes the specification. If 19. $key = $_GET['key'] ?? error('key is required');
the bug youve uncovered is a critical production issue, then 20.
raise the issue with your team for evaluation. Fixing it might 21. $validUsers = [
be the right call, especially if it affects security. If its some- 22. "michael" => "kangar00s",
thing that hasnt caused any issues so far, the correct solution 23. "oscar" => "phparch16"
24. ];
might just be to leave it alone.
25.
Testing HTTP APIs 26. $validKey = $validUsers[$username]
27. ?? error('could not find user', 404);
Lets get back to this service youve inherited at your new 28.
job. Its a service that accepts a username and an API key and 29. if ($key !== $validKey){
returns whether its valid or not. Everyone interacts with it 30. error('invalid apikey');
via HTTPS, meaning you have the ideal pinch point to start 31. }
writing tests against. So long as the service behaves identical- 32.
ly to people calling the public HTTP API, it doesnt matter 33. output(['success' => 'true',
34. 'user' => ['name' => $username]]);
how the internals of the service works. You could even
rewrite it in a whole new language if you need!
Though the service will likely be a more complicated than
About Behat
this in real life, the behavior will probably be similar to the Behat is intended to be used as a Behavior-driven Design
code in Listing 2. tool that happens to be able to verify those designs by
running code. A typical Behat story, which uses the Gherkin3
Reading the code, we can see there are several different
syntax, looks like the following:
paths through the code. Working out all of the different
branches that a user can take and making a note of them is Given I have 1 "Red Bucket" in my basket
the most important part of writing characterization tests. In And I have 2 "Large Spades" in my basket
this case, there are five routes: When I add 1 "Red Bucket" in my basket
Then I should see 2 "Red Buckets" in my basket
1. Missing username And I should see 2 "Large Spades" in my basket
2. Missing API key
The test follows a Given, When, Then flow to set up exist-
3. Invalid username ing state, perform an action, and assert that what you expect
4. Invalid API key happened. Each of those human readable lines will be backed
5. Successful request by code which describes what actions to perform.
Its relatively trivial to test all of those conditions by hand, It turns out the same flow is perfect for testing APIs too. To
but as the service grows in complexity, the time it takes test your API, you can use the datasift/behat-extension4
will increase. Also, you cant run manual tests as a part of package. This extension pulls in Behat for you and provides
a continuous integration system. Lets look at writing these some extra step definitions to help test HTTP APIs. This is
tests using Behat2, a Behavior-driven Development (BDD) what my composer.json looks like:
framework for PHP.
3 Gherkin: https://cucumber.io/docs/reference
4 datasift/behat-extension:
2 Behat: http://behat.org https://packagist.org/packages/datasift/behat-extension
LISTING 3
{
01. Feature: Auth service
"name": "mheap/auth-service",
02.
"require-dev": {
03. Scenario: No Username
"datasift/behat-extension": "4.*"
04. When I make a "GET" request to "/"
}
05. Then the response status code should be "400"
}
06. And the "error" property equals "username is required"
After running composer install, its time to 07.
bootstrap Behat and write your first test. Youll 08. Scenario: No API Key
need to create a behat.yml file in your projects 09. When I make a "GET" request to "/?username=foo"
10. Then the response status code should be "400"
root folder with the following contents to register
11. And the "error" property equals "key is required"
the extension with Behat. The base_url provided 12.
is where Behat will look for your application (in 13. Scenario: Invalid Username
this case, Im running the app on port 8080 using 14. When I make a "GET" request to "/?username=bananas&key=foo"
PHPs built in web server): 15. Then the response status code should be "404"
16. And the "error" property equals "could not find user"
default:
17. Scenario: Invalid API Key
extensions:
18. When I make a "GET" request to "/?username=michael&key=foo"
DataSift\BehatExtension:
19. Then the response status code should be "400"
base_url: http://localhost:8080/
20. And the "error" property equals "invalid apikey"
21.
suites:
22. Scenario: Successful login
default:
23. When I make a "GET" request to "/?username=michael&key=kangar00s"
contexts:
24. Then the response status code should be "200"
-
25. And the "success" property equals "true"
'DataSift\BehatExtension\Context\RestContext'
Start the built-in service by running verified the response it received contained the correct HTTP
php -S localhost:8080 in the same folder as your index.php code and error message. Work your way through the remain-
that bootstraps the application. You can test that this is work- ing branches by adding a new scenario underneath the auth
ing by visiting http://localhost:8080you should see the feature for each branch. To help you out, you can find my
error {"error":"username is required"}. Lets write a test final set of tests in Listing 3.
for this error condition!
With this set of tests, we can be sure that if the auth service
behavior changes (both intentionally and unintentionally)
Your First Behat Test
we will be able to catch the change in an automated fashion
Create a folder named features in the same directory and make a decision about if the change should be approved
as behat.yml. This is where Behat will look for tests to run. or denied.
Inside features, create a file called auth.feature. This is
If you wanted to test multiple values for a single scenar-
where your tests will live. Edit auth.feature and add the
io, you could use Behats table driven test support to achieve
following content:
your goal. Youll need to change your Scenario to a Scenario
Feature: Auth service Outline and add an Examples table with the required data:
Scenario: No Username
When I make a "GET" request to "/" Scenario Outline: Successful login
Then the response status code should be "400" When I make a "GET" request to "/?username=<name>&key=<key>"
And the "error" property equals "username is \required" Then the response status code should be "200"
And the "success" property equals "true"
Here, youre testing the auth services when no username And the "user.name" property equals "<name>"
is provided. When you make a GET request, you expect Examples:
to receive an HTTP code of 400 with an error that the | name | key |
| michael | kangar00s |
username is required. You can run this test by executing
| oscar | phparch16 |
./vendor/bin/behat.
Behat will then run this scenario once per row in the
$ ./vendor/bin/behat Feature: Auth service Examples table. This is a great way to test different input
values without repeating the test boilerplate each time.
Scenario: No Username When I make a "GET" request to "/"
Then the response status code should be "400" And the Working With Databases
"error" property equals "username is required" As well as providing steps to test RESTful APIs, the Behat
extension contains many more useful contexts. One of my
1 scenario (1 passed) 3 steps (3 passed)
most used ones is the DatabaseContext. If you register the
Behat just made a real HTTP request to your service and DatabaseContext in behat.yml and provide the connection
LISTING 4
credentials, your database state can be reset before every 01. default:
scenario. This can be combined with the RestContext to test 02. extensions:
a database driven API. See Listing 4 for an example of how to 03. DataSift\BehatExtension:
configure the DatabaseContext. 04. base_url: http://localhost:8080/
Theres not much to the DatabaseContext. If you register 05. database:
06. driver: mysql
the context and provide the required details, itll do all of its
07. dbname: authmanager
work in the background without you needing to do anything 08. host: 127.0.0.1
else. It currently supports MySQL and SQLite, but as its all 09. port: ~
PDO under the hood. It should easily work with any other 10. username: travis
database you need. 11. schema: /path/to/schema.sql
12. data: /path/to/data.sql
Encapsulating Behavior 13.
While the above tests are functional, they dont represent 14. suites:
15. default:
our intentions. Although we are currently making a GET
16. contexts:
request to "/" at the moment, what were actually trying to 17. - 'DataSift\BehatExtension\Context\RestContext'
do is log in as a user. If we changed the endpoint from "/" to 18. - 'DataSift\BehatExtension\Context\DatabaseContext'
"/check" all of our tests would need updating in the future.
Lets write a custom step to encapsulate that behavior now. LISTING 5
Currently, we load in the RestContext and DatabaseContext 01. <?php
from the DataSift Behat extension. As were going to be writ- 02. use Behat\Behat\Context\Context;
ing a custom step, we need to add the code to our own Behat 03. use Behat\Behat\Tester\Exception\PendingException;
context. 04. use Behat\Behat\Hook\Scope\BeforeScenarioScope;
By convention, any steps for the current test 05.
06. class FeatureContext implements Context {
suite should live in a class named FeatureContext
07.
which lives inside features/bootstrap. Create
08. private $restContext;
features/bootstrap/FeatureContext.php now with the 09.
following contents: 10. /** @BeforeScenario */
<?php 11. public function gatherContexts(
12. BeforeScenarioScope $scope
use Behat\Behat\Context\Context; 13. ){
use Behat\Behat\Tester\Exception\PendingException; 14. $environment = $scope->getEnvironment();
15. $this->restContext = $environment->getContext(
class FeatureContext implements Context { 16. 'DataSift\BehatExtension\Context\RestContext'
} 17. );
18. }
Next, youll need to register the context with Behat by edit- 19.
ing behat.yml. Add FeatureContext to the top of your list of 20. /**
available contexts like so: 21. * @When I log in as ":user" with the password ":pass"
22. */
suites: 23. public function iLogInAsWithThePassword($user,
default: 24. $passw) {
contexts: 25. $url = "/?";
- FeatureContext 26. if ($user) {
- 'DataSift\BehatExtension\Context\RestContext' 27. $url .= '&username='.$user;
- 'DataSift\BehatExtension\Context\DatabaseContext' 28. }
FeatureContext should now be available to all of our tests. 29. if ($pass) {
30. $url .= '&key='.$pass;
Its now time to change our feature file to use the language
31. }
wed like. Open up features/auth.feature and replace the 32.
line: 33. return $this->restContext->iRequest("GET", $url);
When I make a "GET" request to "/" 34. }
35. }
with the line:
When I log in as "michael" with the password "demo"
request, we care that were trying to log in as a user.
Save your changes then run Behat again with
Here, were removing the implementation details and
./vendor/bin/behat. It should say the default suite has
exposing our intent instead. We dont care that its a GET
undefined steps. Enter the number which corresponds to
FeatureContext and watch as Behat valid and invalid arguments. is for someone to read the code and
generates an example step for us. Copy You can test the empty argument work out the various branches through
and paste the generated definition into support by removing the username and the code so you can ensure that youve
the FeatureContext class and run your password from the first test we edited captured the entire public interface.
test again. This time you should get a to generate our step so the username Characterization tests are a great tool
TODO message, saying you should and password parameters are empty which allow a developer to start adding
write the pending definition. strings. Once you have your first test tests to applications that currently have
Now, lets implement our new step! passing, update the rest of your tests none. By treating the internals of an
As we just want to delegate to another to use the new step language. You can application as a black box and verify-
class, theres a lot of boilerplate to set also update your table driven test so ing its external behavior, it frees you
up so that our context can access the that the When statement contains: up to refactor the internals for any
RestContext. Take a look at the entire reason you like. This could be to make
When I log in as "" with the password ""
FeatureContext in Listing 5 before we the application more unit testable, to
examine it in depth. Then, run your tests again. improve performance, or even to make
A lot is going on there, so lets Congratulations! You just wrote your future development easier. Character-
step through it piece by piece. The first acceptance tests for an existing ization tests give you confidence that
first thing we do is import all of the service and encapsulated the behavior your changes dont have any unintend-
objects well need at the top. Next, in a custom step, so its easy to update ed side effects.
we define a gatherContexts function your tests should the underlying code Thats all we have time for today, but
that needs to run before every scenar- change in the future. keep an eye out for a follow-up article
io. This is how we can get access to in future editions of php[architect].
the RestContext and call methods
Conclusion Well cover how to write some of the
on it. Finally, we implement our new Its not the most glamorous job in the more complex characterization tests,
When I login step by constructing world, but building up a set of charac- including how to deal with code which
the URL we need to call and delegat- terization tests for your HTTP API can makes HTTP calls to external services
ing to the existing RestContext. You be a remarkably simple job. All it takes as part of doing its job.
might notice in the annotation for the
iLogInAsWithThePassword function, I Michael is a polyglot software engineer, committed to reducing
added quotes around the arguments. complexity in systems. Working with a variety of languages and
This allows us to provide empty argu- tools, he shares his technical expertise to audiences all around
ments and test that the application the world at user groups and conferences. Day to day, Michael is
behaves as intended as well as passing a fixer. He works on whatever needs an extra pair of hands both
at his day job and in open source projects. @mheap
global configuration object which stored the values we need- session. This is without adding any docblock comments
ed: Mage::registry('current_product');. And in the really (which are still a good idea) or using return types in PHP 7.
old days, we even called the $_SESSION superglobal or a glob-
al function like db(). PHP DI
This leads to brittle and hard-to-test code. What if we My favorite tool for dependency injection is PHP DI1. It
want to change the Transport class to a different one? Find handles constructor injection eloquently and is customizable
and replace may work, but we might miss something. What for those cases that just dont seem to fit any mold.
if Magento decides primary_product is a better label than One thing to note is that most dependency injection util-
current_product? What if PHP changes the name of the ities use an idea similar to a singleton: its called a container.
$_SESSION super global to another name? In each of these In the above example, if we are using PHP DI, I will need
examples, we are also breaking the principle of dependency to instruct it to resolve that interface to a CustomerSession
inversion. object (see following example). PHP DI instantiates a class,
Dependency injection is a great solution to these challeng- adds it to the container, and will inject that specific object
es. At its core, dependency injection follows the principle of anywhere that we need that type of object. Think of it like
dependency inversion. It also gives us better insight regard- caching. On the surface, the object could be viewed as a
ing how many purposes our classes have, assisting us in the singleton because it is the same instance used. This can be a
quest to give every class a sole responsibility. surprise if you are not aware of it. Look at the factory method
design pattern for more details.
Whats Dependency Inversion? Using PHP DI as our springboard, lets look at how to
I have found that starting an application and following create a project with PHP DI. First, you will include it with
the dependency injection pattern makes testing easier. For Composer:
example, to test a class that interacts with a session, I can
composer.phar require php-di/php-di
inject a custom session wrapperone that does not interact
with PHPs session functions. Next, in Listing 2, we need to setup the container:
Were going to look at the more common route: construc- When you call $container->get(), PHP DI is autowiring
tor injection. The other way to inject dependencies is setter up that class. This involves using reflection2 to look at the
injection. The problem with setter injection is if you neglect type hinting for the CustomerManager class to see it needs
to call a setter, you may have a fatal error. a CustomerSessionInterface. If we didnt add a definition
Listing 1 shown an example of constructor injection: for CustomerSessionInterface, it would try to instantiate a
class of that type (which would fail). Since we have added
This is the most basic example of constructor injec-
that mapping with addDefinitions(), it instantiates our
tion. We can instantiate the object by writing this code:
CustomerSession class.
new CustomerManager(new CustomerSession());. Instead of
our customer manager class having to determine what type The interesting thing is that PHP DI will traverse the
of session handler to use, we tell it what the appropriate one entire constructor tree to create the needed objects. So, if
is. This could be a BusinessSession or a GovernmentSession CustomerSession has dependencies, PHP DI would create
(see the strategy design pattern later). those, and if those dependencies have dependencies, it will
create or load those too: all the way up. This is what happens
Just by looking at this code, I know what this class needs:
the first time. For subsequent instantiations, the objects are
a class implementing CustomerSessionInterface. Addition-
loaded from the container.
ally, between the name of the class, CustomerManager and its
needs, a customer session object, I can see this class would As a side note, calling a specific object from the contain-
likely return an instance of a customer object, possibly based er is called the service locator pattern. It is considered an
on the ID found in the customer anti-pattern because of the lack of the
LISTING 1 LISTING 2
01. <?php 01. $builder = new DI\ContainerBuilder();
02. class CustomerManager 02. $builder->addDefinitions([
03. { 03. CustomerSessionInterface::class
04. protected $session; 04. => DI\object(CustomerSession::class)
05. 05. ]);
06. public function __construct( 06. $container = $builder->build();
07. CustomerSessionInterface $session 07.
08. ){ 08. $customerManager = $container->get(CustomerManager::class);
09. $this->session = $session;
10. } 1 PHP DI: http://php-di.org
11. }
2 using reflection: http://stackoverflow.com/q/4262350
dependency inversion principle; your code is still depending something different. Utilizing YAML or XML to provide
on a God class. For example: class names (for extensibility), you could use a factory to
instantiate a type of class. Factories can also be used to set up
public function __construct() {
$this->session = Container::get('CustomerSession');
complex objects (such as utilizing a new library with huge
} constructors).
As we go into the third design pattern, it is important to
Instead, we should inject the CustomerSession into the
remember the factory method is for creating objects. After
constructor using constructor arguments as was demonstrat-
the factory assembled your smartphone, they shouldnt see
ed in the prior code examples.
it again (unless it spontaneously catches on fire). This is the
I have found dependency injection to simplify the code same for software factories: after your object is created, the
that I write as it helps me to maintain classes which follow the factorys purpose is complete.
single responsibility principle. Using the PHP DI framework,
you have very little to do to implement this design pattern, Number Three: Strategy
but understanding how this works is very important! A year ago, I was tired of borrowing a weed-trimmer
Number Two: Factory Method from a relative to edge my lawn. The solution was to go to
a dealer of such products and see what they had to offer.
Each of the design patterns we discuss are semi-related or Their solution for homeowners was a multi-tool. To get a
assist each other. This one is no exception. weed-trimmer, you had to buy two pieces: an engine and a
As you read this magazine, you likely have a mobile phone weed-trimmer attachment. The benefit of this, they said, was
sitting next to you. Maybe you are sitting on a couch with a that you could also purchase a sidewalk edger attachment or
gentle light illuminating the pages. Or, maybe you received a pole chainsaw attachment. For all I know, they had a toilet
the digital edition in your inbox and couldnt start the day plunger attachment, too.
until you devoured a few pages. Either way, you are interact- I also have a few electric drills floating around my house.
ing with a number of objects you didnt create. You possibly Each of these has a chuck (similar to interfaces) which
bought your phone online. Maybe you went to the local
furniture store to find that perfect couch. The odds are good
you didnt build your own computer. Someone built each of
LISTING 3
those for you at a factory. 01. <?php
02. // Dependency injection configuration (for PHP DI):
Think of how much cheaper (likely) smartphones would
03.
be if we received a kit of thousands of parts to assemble our 04. return [
own smartphone. The number of people with a smartphone 05. \CustomerFactory::class => DI\object()
would be a small fraction of what it is today because only 06. ->constructorParameter('customerClassName',
those with a lot of time (and motivation) could own one. 07. \Customer::class)
On a more serious note, like many other metaphors, we 08. ];
can build factories into our code. As Im going to show you, 09.
10. // Factory
they can also provide great benefit.
11. class CustomerFactory
Consider the previous design pattern: dependency 12. {
injection. What if our class needs to create a new object 13. private $customerClassName;
(for example a new Customer)? The easy thing to write is 14.
new Customer(). This tightly couples our classes togeth- 15. public function __construct(
er and is an anti-pattern. If we just inject a Customer into 16. string $customerClassName
the constructor, it would be an instantiated object of the 17. ){
18. if (!class_exists($customerClassName)) {
Customer type: then we couldnt create multiple Customers.
19. throw new \Exception(
Instead, we can delegate that to a class designed to create 20. "Customer class has not been set."
other classes as seen in Listing 3. 21. );
Notice in this factory we are injecting the name of the 22. }
customer class (we set that up in the dependency injec- 23. $this->customerClassName = $customerClassName;
tion configuration). We could use this to inject a DSN for 24. }
PDO or a debug flag. In this case, our factory is acting as a 25.
26.
proxy, holding the dependencies until our child needs to be
27. public function create($initialData = [])
created. Additionally, we can pass unique data to the child 28. : CustomerInterface {
through the create methods arguments. 29. return new $this->customerClassName(
This is just one example of a factory. Think how easy, 30. $initialData
with this example, it would be to substitute the class for 31. );
32. }
33. }
accepts a drill bit, a screwdriver bit, and a plethora of other PHP 7 is helpful, in Listing 5, as it allows us to type-hint the
attachments (concrete classes). outputs. Also, notice that we could insert any child Provider,
You might see similarities in these two examples: a base and it makes no difference to the functionality of our parent
unit with a mechanism to connect to an object which imple- PersistenceLayer.
ments an interface. It doesnt matter what the attachment The strategy pattern is useful in any case where you need to
does, as long as it implements the interface. Thats the strate- swap out algorithms or components. Take an example from
gy design pattern. ImageMagick (I have no idea what their internal code looks
I will use the terms parent and child in the follow- like, but this could certainly be a use case):
ing way: parent describes what happens and the child $imagick->resizeImage($width, $height, $filterType,
describes how it happens. Take the example of the drill: the $blur, $bestFit);
parent (drill) provides power for the child (drill bit) to
$filterType is a constant denoting which algorithm to
drill a hole.
apply when resizing the image. If we were building this in
The kingpin of this design pattern is with extensive use of modern PHP, we would probably have each algorithm imple-
interfaces. This may seem like a lot of extra overhead. Howev- ment a common interface. When we call the resizeImage
er, languages other than PHP, such as C++ and Objective-C function, it could take advantage of the strategy design
require you to do this for every class, so its not necessarily an pattern and call the child that is applicable for the filter spec-
issuedoing it for a number of files should not be a problem. ified.
This is a discipline that took a long time for me to embrace,
Now that you hopefully have an understanding of the
but I have seen great results in doing so.
strategy pattern, lets take a look at the most complex of the
When your child classes implement an interface, you can four, one that is similar in concept to the strategy but quite
write your parent class to talk to methods that are exposed by different in implementation.
that interface. It doesnt matter what the child is or does, as
long as it implements the interface. Number Four: Chain of Responsibility
Here is a simple interface for persisting data: Chains are very useful. They keep cargo on the back of
interface ProviderInterface { semi-trucks. They keep your car from sliding off the road in
public function update($model); winter. They can even cut down trees.
} Typically, when I think of a chain in programming, I
Lets write some classes to implement this (see Listing 4): think of a vertical chain: inheritance. The chain I will be
describing is very differentit is horizontal. Think of this
Please note that these examples are abbreviated for clari-
chain as a job-runner. Instead of drilling down into data, we
ty and wont run if you try to execute them. I am primarily
are passing it horizontally. Instead of
trying to convey this way of thinking.
LISTING 5
LISTING 4 01. <?php
01. /** 02. /**
02. * This is a child class: 03. * This is the parent class, providing the host for
03. */ 04. * the children:
04. class MySqlProvider implements ProviderInterface 05. */
05. { 06. class PersistenceLayer
06. public function update($data) 07. {
07. { 08. private $providerFactory;
08. $connection = $this->getConnection(); 09.
09. $connection->exec('UPDATE SET'); 10. public function __construct(
10. } 11. ProviderFactory $providerFactory) {
11. } 12. $this->setProviderFactory($providerFactory);
12. 13. }
13. /** 14.
14. * This is a child class: 15. public function getProvider(string $endpoint)
15. */ 16. : ProviderInterface {
16. class CrmProvider implements ProviderInterface 17. return $this->providerFactory->get($endpoint);
17. { 18. }
18. public function update($data) 19.
19. { 20. public function update(string $endpoint,
20. $client = new GuzzleHttp\Client(); 21. array $data) {
21. $client->request('POST', '{url}'); 22. $this->getProvider($endpoint)->update($data);
22. } 23. }
23. } 24. }
LISTING 6
dealing with executing each item in the chain, we call the 01. <?php
first item, and that method call returns us the value we want 02. interface ChainLinkInterface
(even though there may be multiple chain links that were 03. {
called). Another way to think of it is similar to the Unix pipe 04. protected function getDataFor($key) : void;
operator: the output from the command on the left of the 05. public function append(ChainLinkInterface $nextLink);
operator is the input for the command to the right. 06. public function get(\Transport $transport) : \Transport;
07. public function process(\Transport $transport) : bool;
Unfortunately, PHP doesnt have an exact replica for the 08. }
pipe operator. I use a transport object to carry data from 09.
one command to the next. This is ideal because you can only 10. abstract class ChainLink
return one value or object and passing variables by reference 11. {
is prone to issues. This transport object can be sent to each 12. protected $nextLink;
link of the chain and have values set or unset on it, to be 13. protected $result;
passed onto the next link. 14. protected $data;
15.
The simplest way to go through a list of commands is to 16. public function __construct(
instantiate each links object and use a foreach loop to call 17. ChainLinkInterface $nextLink
each item. Depending on how complex your system is, this 18. ){
may be fine. However, that can become unwieldy as your 19. $this->nextLink = $nextLink;
application gets bigger. 20. }
21.
The Setup: 22. public function get(\Transport $transport): \Transport {
The following implementation of the chain of responsibility 23. $value = $this->getDataFor($transport->getKey());
is very clean and easy to extend. It also takes some explaining. 24. if (!empty($value)) {
25. $this->result->setResult($value);
The idea is that the concrete part of each link only knows 26. }
about what action it takes. There is an abstract class each 27.
link extends, handling the chaining details. The unique thing 28. if (empty($value) && $this->nextLink) {
about this way of chaining is that it is completely horizontal. 29. $this->nextLink->get($transport->getKey());
You append link to link, and the abstract class handles the 30. }
chaining. In Listing 6, lets take a look at how this works: 31.
32. return $this->result->getResult();
Lets look at each method: 33. }
__construct(ChainLinkInterface $nextLink); depen- 34. }
dency injection! This stores the next link in the chain as a
class property. getDataFor($key); is the function that does LISTING 7
the work. Notice it is protected. We only want to have
01. <?php
the abstract class call this function on the concrete imple- 02. class FirstAction extends ChainLink
mentation. It is hidden because we only want our concrete 03. implements ChainLinkInterface
class to implement it, as this function likely depends on 04. {
additional information to successfully complete its job. 05. protected function getDataFor($key) {
get(\Transport $transport): this function gets the data 06. return $this->doSomeActionOnOurKeyVariable($key);
from getDataFor($key). If there was a value returned, the 07. }
chain is complete (depending on your implementation). 08. }
09.
Otherwise, it passes it onto the next item. At the end, this
10. class SecondAction extends ChainLink
function will return the Transport object.
11. implements ChainLinkInterface
You can have the chain of responsibility stop at the first 12. {
winner or have it process each link no matter what. An 13. protected function getDataFor($key) {
example of it stopping on the first successful result would 14. return $this->runAComplexImageProcessingAlgo($key);
be looking up a key in a list of databases: the first one that 15. }
finds the value returns it, and the chain stops executing. An 16. }
example of the second could be running consecutive filters
Putting It All Together:
on an image.
Whew, there was some code! As you look at this and study
Concrete Links: it, you will see it isnt complex after all. The ChainLink class is
Listing 7 shows an actual implementaion of the only a few lines, and every concrete chain link extends it. So,
ChainLinkInterface. how does it all go together?
tional Sites
Building Excep & Thesis
ss Building Exceptional Sites
with WordPre by Peter MacIntyre
primarily
xperience in IT,
eilly);
Good Parts (OR
eilly), Pro
HP-3rd Ed (OR
spoken at
ers. Peter has
by Peter MacIntyre
You can ind
oth 5.3 and 4.0.
re.
nd @pbmacinty
e-
e.
to WordPress and the Thesis theme.
Word
w
Press & Thesis
to
ing
ny
s
EO),
e Purchase Book
er
http://phpa.me/wpthesis-book
Peter MacIntyre
CoderCruise
CoderCruise
7 days at sea, 3 days of conference
Leaving from New Orleans
e a ke rs and visiting Montego Bay,
l l f o r Sp 6th Grand Cayman, and Cozumel
Ca J a n
e n u ntil
Op
July 16-23, 2017 Tickets $250
www.codercruise.com
16 \ January 2017 \ www.phparch.com Presented by
FEATURE
e
08.
reflection to parse annotations in comment blocks. The code 09. // ... rest of test ...
in Listing 1 might look pretty familiar to many PHP devel- 10. }
opers:
But what is reflection? Its not a new concept by any measure. LISTING 2
The concept of reflection is introspection about code being
01. ZEND_METHOD(reflection_class, hasMethod)
executed. Its a meta-concept, allowing you to examine code
02. {
structure of classes, properties, functions, methods and so 03. reflection_object *intern;
on. It allows you to examine values; even those which are 04. zend_class_entry *ce;
private or protected properties, as shown above. Reflection, 05. char *name, *lc_name;
in general, also grants the ability to modify the structure or 06. size_t name_len;
behavior of code at runtime in a limited way. Its a concept 07.
which exists in multiple programming languages, such as Go, 08. METHOD_NOTSTATIC(reflection_class_ptr);
Java, Python and so on; PHP exposes this functionality in the 09. if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name,
reflection extension1. 10. &name_len) == FAILURE) {
11. return;
The reflection extension is built-in to PHP, cant be 12. }
disabled, and is around 7,000 lines of C code. The reflec- 13.
tion extension works very well because it has direct access 14. GET_REFLECTION_OBJECT_PTR(ce);
to the real values that make up the variables we see in what 15. lc_name = zend_str_tolower_dup(name, name_len);
is known as userland PHPthe PHP world were used to 16. if ((ce == zend_ce_closure &&
working in. Looking at one of the more simple methods in 17. (name_len == sizeof(ZEND_INVOKE_FUNC_NAME)-1)
the reflection extension (Listing 2), the implementation of 18. && memcmp(lc_name, ZEND_INVOKE_FUNC_NAME,
19. sizeof(ZEND_INVOKE_FUNC_NAME)-1) == 0)
ReflectionClass#hasMethod reveals theres not much going
20. || zend_hash_str_exists(&ce->function_table,
on here. 21. lc_name, name_len)) {
If we skip over things like parsing parameters and a few 22. efree(lc_name);
necessary macros to perform some various housekeep- 23. RETURN_TRUE;
ing tasks we see that the primary operation of this method 24. } else {
boils down to the call to zend_hash_str_exists. This func- 25. efree(lc_name);
tion checks to see if the name provided as the parameter to 26. RETURN_FALSE;
27. }
hasMethod exists in the classes function table. To understand
28. }
1 reflection extension: https://php.net/book.reflection
why this works and is so straightforward, lets quickly look at how Better Reflection works.
how a class instance is represented by the Zend Engine in C: Creating a reflection in Better Reflection is, at least behind
zend_object - this is essentially the instance of the class the scenes, a four step process. Its still pretty straightforward,
and has other properties not listed here, such as handlers and however, and where you might currently do something like
garbage collection. this with PHPs internal reflection:
zend_class_entry* ce - the ce or class entry refer- use ReflectionClass;
ences a zend_class_entry which is the blueprint of the
class itself; i.e.information about the structure: $reflection = new ReflectionClass(
HashTable function_table \My\ExampleClass::class
HashTable properties_info );
HashTable constants_table In Better Reflection, its almost as simple for most use cases:
zval* static_members_table
use BetterReflection\Reflection\ReflectionClass;
zend_class_entry** interfaces
zend_class_entry** traits $reflection = ReflectionClass::createFromName(
etc. \My\ExampleClass::class
);
The important part we care about here is that the
function_table, properties_info, and constants_table are This static constructor, createFromName looks something
all implemented as HashTables. We can think of these, essen- like this under the hood:
tially, as arrays. For example, its very easy to look up whether public static function createFromName($className) {
a function exists. return ClassReflector::buildDefaultReflector()
The point is we can directly access what is going on under ->reflect($className);
the hood, and expose it in userland via the reflection API, }
meaning we can start messing around with things that Underneath, it uses this layer called a Reflector,
shouldnt be messed around with. and because were trying to reflect a class, it uses the
Its Like Reflection, But Better ClassReflector; yes there is a FunctionReflector too for
reflecting on functions (not class methods) directly. We call
Having the inquisitive nature to find out how things work, the buildDefaultReflector static constructor which creates
I thought why stop there? Im going to introduce to you a a ClassReflector with some source code locators which
library called Better Reflection2, which is a reflection library behave in a transparent way, and calls reflect on that, pass-
written in pure PHP, which does not require any extensions, ing the class name. The buildDefaultReflector call looks
and does not use the internal Reflection API directly. One like this:
of the original goals for the Better Reflection library was
to create a compatible layer offering a drop-in replacement public static function buildDefaultReflector() {
for the internal reflection API, meaning it can work with return new self(new AggregateSourceLocator([
new PhpInternalSourceLocator(),
the existing functionality that uses it, but can also provide
new EvaledCodeSourceLocator(),
additional features. There are a few caveats, though, so we new AutoloadSourceLocator(),
describe the API as mostly compatible with the PHP core ]));
reflection API. }
The first question we need to answer is, why? The goal These source locators are different ways of finding and
of Better Reflection, as the name suggests, is to be better; preparing source code to be analyzed by Better Reflection.
specifically, to provide more features and awesome super- There are several Source Locators provided out the box, and
powers which allow you to do things with code that were not they are (at the time of writing):
possible before. A key component of Better Reflection is that AggregateSourceLocator
you can reflect on classes that have not actually been load-
AutoloadSourceLocator
ed yet. As explained earlier, the PHP internal reflection
ClosureSourceLocator
extension works by already having access to the loaded class
blueprint. By creating reflections from classes that dont ComposerSourceLocator
exist yet, we can start to do some pretty interesting things, DirectoriesSourceLocator
such as modifying the class itself, or its methods, prior to EvaledCodeSourceLocator
loading. This also applies to third party codebases, such as FileIteratorSourceLocator
downloaded PHP packages we dont trust. This technique is PhpInternalSourceLocator
called monkey patching, and well look at how it works in a SingleFileSourceLocator
bit. Before we look into that, were going to explore exactly StringSourceLocator
The AggregateSourceLocator is simply a source
2 Better Reflection: https://github.com/Roave/BetterReflection
LISTING 3
locator which accepts an array of other source locators. It 01. use BetterReflection\Reflector\ClassReflector;
allows chaining and multiple methods of locating source 02. use BetterReflection\SourceLocator\Type\StringSourceLocator;
code, such as is used in the default reflector above: it 03.
uses an AggregateSourceLocator consisting of, in order, 04. $source = <<<EOF
PhpInternalSourceLocator, EvaledCodeSourceLocator, and 05. <?php
AutoloadSourceLocator. In Listing 3, lets look at how we can 06. class MyClassInString {}
use a single source locator by itself. 07. EOF;
08.
In this example, we are using a StringSourceLocator 09. $reflector = new ClassReflector(
directly. Because we would like to use a specific source loca- 10. new StringSourceLocator($source)
tor, we cant use the convenient static constructor above. 11. );
Instead, we need to use the ClassReflector directly, pass- 12.
ing in the source locator as the constructor parameter, with 13. $classInfo = $reflector->reflect(MyClassInString::class);
the string of code itself as the constructor parameter to
StringSourceLocator. former glory, the balance is restored, and we can now return
The default source locator for ClassReflector should the name of the file the autoloader tried to open.
cover 99% of use cases. First, the PhpInternalSourceLocator Of course, the assumption we make is that your chosen
uses a set of built-in stubs which define internal classes. autoloader will definitely load a file. If its doing something
Next, EvaledCodeSourceLocator uses the Zend\Code code weird and not loading a file, then this isnt going to work, and
generation library to generate source code executed by the youre going to have to use one of the other source locators
eval() function in PHP. The third and final default locator, to find your class. Finding out what the other SourceLocator
AutoloadSourceLocator is where some real magic happens. classes do can be left as an exercise for you to find out, but
In order to figure out how to load classes, most autoload- generally, theyre quite self-explanatory, and most have at
ers (including Composers autoloader) map a namespace to least basic usage documentation.
a PHP file on the filesystem, having a 1:1 ratio of class to file.
If we make the assumption that most autoloaders work this
Climbing the Abstract Syntax Tree
way, we can do something interesting to determine where we Okayso we have some source code locatedhow can we
expect the file containing a class will be locatedeven if it turn that into a reflection? The next step in the process is
hasnt yet been loaded. parsing this source code into something called an abstract
The technique boils down to these lines of code: syntax tree, or AST for short. This part of the process is pretty
much entirely taken care of by a library called PHP Parser3,
self::$autoloadLocatedFile = null; written by internals developer and CS/physics student Nikita
$previousErrorHandler = set_error_handler(function () {}); Popov. The purpose of this library is to take PHP source code
stream_wrapper_unregister('file');
as input, lex it, parse it, and return an AST representation of
stream_wrapper_register('file', self::class);
class_exists($className); it:
stream_wrapper_restore('file'); use PhpParser\ParserFactory;
set_error_handler($previousErrorHandler); $parser = (new ParserFactory)->create(
return self::$autoloadLocatedFile; ParserFactory::PREFER_PHP7
First, we essentially remove the error handler (set it to an );
empty function) to silence an inevitable, unavoidable error
print_r($parser->parse('<?php echo "Hello " . "world";'));
were about to trigger. We then unregister the existing stream
wrapper for handling filesthat is how PHP opens files, and If we looked at the output of this script, it would be very
register our own (self::class). The AutoloadSourceLocator verbose. To simplify the visualization, we would see this data
implements a couple of methods (stream_open and url_stat) structure represented:
used to hijack file system calls: Echo
public function stream_open($path, $mode, Concat
$options, &$opened_path) { Left
self::$autoloadLocatedFile = $path;
String, value Hello
return false;
} Right
When the call to class_exists happens, our own String, value world
stream_open function is called, and we store the name of the Each of these items is called an AST node and is a data
file in the attempt to load it. We then return false, which structure representing a piece of code. Unlike tokenization (a
is what causes the inevitable error mentioned above. Finally, method that could also be used), we dont have to deal with
the stream wrapper and error handlers are restored to their
3 PHP Parser: https://github.com/nikic/PHP-Parser
the actual syntax of the file, such as what the concatenation class MyClass {
operator looks like, or whether or not there is a semi-colon public function foo() {
at the end of the line. Instead, we get a pure representation of return 'foo';
}
the execution steps of this code, such that we could navigate
}
this graph of AST nodes, and execute them.
Lets look at a simple class structure to see how useful this Given you have a reflection representing this piece of code,
information can be, and how we can use this to create the you can use a helper method such as setBodyFromClosure to
reflections themselves: completely change the behavior of the foo method.
of the PhptTestCase class which could be used in a similar way to HHVMs the path a variable takes through the
be time-consuming and challenging, Hackificator toolto suggest and application; if the variable doesnt pass
while ensuring we dont introduce even add type declarations to PHP 5 through an elected escape method,
backwards compatibility breaks. For code to convert it to PHP 7 code. then emit a warning.
an interesting discussion on this, and Another use case could be to write a Another use case could be to be used
to see the ultimate closure of the issue tool which does API analysis and alert- in libraries like Doctrine. Currently,
in the PHPUnit bug bankruptcy, head ing when a public API has changed; for an entity in a project using Doctrine
to PHPUnit Issue 17836. example, by changing types or number ORM cannot be declared as final,
Finally, weve recognized the library of parameters and so on. Using Better on account of the lazy-loading prox-
is much slower than PHP ies Doctrine uses (which
internal reflection. The reason extend the entity). This could
for this is the use of Nikita What can Better Reflection be used be overcome by using the
Popovs PHP Parser library, monkey patching functional-
which is doing the job of the
for, exactly? Lots of things! Your imag- ity of Better Reflection. When
PHP lexer and parser, but in ination is the limit. Static analysis the proxy needs to be made,
PHP itself, isnt particularly comes to mind due to the way Better remove the final declaration
performant. However, after Reflection can analyze types in both from the class before loading
discussion, we concluded PHP 7+ code and older PHP 5 code it, allowing extension of the
its really not a priority issue. entity, or even embedding
The reason for this is all the the additional functionality
sensible use cases for Better directly into the entity itself
Reflection warrant ahead of time usage, Reflection, you can easily fetch a list of (thus avoiding the need to extend the
rather than runtime reflection. For every class in a directory and iterate entity at all).
example, monkey patching should be the reflections producing a specifica- As you can see now, the Better
done ahead of time, and the monkey tion for the public API. If you can do Reflection library aims to provide the
patched code should be cached on disk this against two different versions of basic needs for reflection but allows a
and loaded just like regular PHP code. a package, and note the differences huge amount of flexibility and control
As well see in some of the other use between the two public APIs, you can over your code, which isnt currently
cases for Better Reflection, tools can easily detect basic backwards compati- possible with PHPs internal reflection.
be written for static analysis, which bility breaks. Theres plenty of scope for new features
is not, and should not, necessarily be You could also use Better Reflection too, and as it starts being rolled out
done at runtime. Therefore, the perfor- to analyze use statements to deter- more and being used in various ways,
mance issue is a non-issue because mine explicit dependencies used in a we can see lots of potential for this
Better Reflection shouldnt even be project and perform a check against library. Better Reflection is an open
used when a user loads a web page, for composer.json if the dependencies source project, which means of course
example. are listed correctly. Or perhaps as a feedback, issues, and pull requests are
Some Potential Use Cases security analysis tool which checks very welcome!
for unescaped output, by following
What can Better Reflection be used
for, exactly? Lots of things! Your imagi- James is the founder of the UK based PHP Hampshire
nation is the limit. Static analysis comes user group and the PHP South Coast Conference. Hes
to mind due to the way Better Reflec- also a Zend Certified Engineer and consultant at Roave.
tion can analyze types in both PHP 7+ During his downtime, he continues to run the PHP
code and older PHP 5 code, thanks to Hampshire user group and the conference, and keeps up
the compatible nature of Popovs PHP with active contributions to various open source projects.
Parser library. Better Reflection could @asgrim
RabbitMQ has an excellent set of tutorials online: information, etc., could be metadata
http://www.rabbitmq.com/getstarted.html All of our developers are familiar with the head and
Mockery, likewise, has excellent documentation online: body sections of an HTML page. Lets construct a class,
http://docs.mockery.io/en/latest/ BatsMessage, which follows a similar idea, able to store head
Jeffrey Ways Laravel Testing Decoded5 has a chapter on (metadata) and body (payload) sections. The class can seri-
Mockery. Its the best Mockery tutorial Ive ever encoun- alize and unserialize its contents to create the JSON-encoded
tered, and you dont need to know anything about Laravel string for communication via RabbitMQ.
to follow it. The book as a whole is about TDD using
Laravel New Project
Julie Andrews showed the way half a century ago in the We begin coding by creating a new project. Create the
movie Sound of Music: Lets start at the very beginninga new project DemoMockery the same way we did in Part Three:
very good place to start. Rhythm of TDD
1. Clone the skeleton package:
When you read, you begin with A-B-C. When you sing, git clone [email protected]:thephpleague/skeleton.git.
you begin with do-re-mi. 2. Rename the folder: mv skeleton DemoMockery.
3. Then, tell PhpStorm to create a new project from
What does this mean when building messaging systems?
existing files.
1. To process a message, you need to receive the message.
4. Run the prefill.php script to customize your packag-
2. To receive the message, the message must have been es with author name, etc.: php prefill.php.
sent.
5. Remove the prefill.php script as instructed.
3. To send the message, you need to create the message.
6. To install Mockery and its dependencies run:
4. When creating, sending, and receiving the message, it composer require ---dev mockery/mockery
will calm the chaos if you have a predictable message 7. Get a clean run from PHPUnit. In my case, to run it
format. on my Mac, I invoke it via PhpStorm with a special
In other words, we need to begin by creating a Canonical php.ini file to load the xdebug extension.
Data Model:6 Your initial run should report OK (1 test, 1 assertion).
Delete src/SkeletonClass.php and tests/ExampleTest.php,
How can you minimize dependencies when integrating
but note the namespace and extended class.
applications that use different data formats? Design a
Canonical Data Model that is independent from any First Test
specific application. Require each application to produce
We begin by making sure we can construct a class of the
and consume messages in this common format.
correct type as in Listing 1.
Sure, all we are really doing is passing arrays around as
LISTING 1
a JSON-encoded string. What we proclaim we are doing
is constructing messages using a language-independent 01. <?php
Canonical Data Model which can communicate with any 02. namespace ewbarnard\DemoMockery;
03.
system able to connect to our RabbitMQ server.
04. class BatsMessageTest extends \PHPUnit_Framework_TestCase
Since our development team already uses JSON for data 05. {
transmission in various contexts, our Canonical Data Model 06. public function testConstructor() {
is merely formalizing our existing practice. 07. $target = new BatsMessage;
08. static::assertInstanceOf(BatsMessage::class, $target);
BATS Message Class 09. }
To me, it makes sense to separate metadata from the 10. }
payload data. For example, suppose we are crediting a
member for completing some action on the website: Run the tests. As expected, we see:
The accounting information would be contained in the PHP Fatal error: Class 'ewbarnard\DemoMockery\BatsMessage' not
message payload found
Timestamps, message identifier, routing/origin
Create the class: are nearly identical (at this point). With all tests passing, we
can refactor the test to remove duplication (see Listing 2).
<?php
Now you can better see why I always begin a test suite
namespace ewbarnard\DemoMockery; with the testConstructor() test. Its my safety net ensuring I
didnt break anything when refactoring the test setup.
class BatsMessage { We continue to develop and flesh out the BatsMessage
}
class. Its very rapid, and we wont show it here.
Were green, that is, all tests pass: OK (1 test, 1 assertion). Whats the point? BatsMessage is a small class. It doesnt
I nearly always start out a test suite by checking the construc- do much except store and serialize a couple of arrays. Does it
tor. This step ensures I have my tests hooked up correctly. merit writing an entire suite of unit tests?
Always observe the test failing before making it pass. This
Yes it does! We are building a distributed messaging
guarantees the test really has run.
system. The message itself is, obviously, central to everything.
Explore the API The message structure even has a pattern name, Canonical
Data Model. So, yes, this class does merit the unit-test treat-
The head() method should return an array, as should ment.
body(). Write the tests, and write the simplest thing possible
to pass the test. The unit tests also serve as executable documentation. If
anyone needs to integrate with our BATS system, they can
public function testHeadReturnsArray() { examine the BatsMessage test suite to easily understand the
$target = new BatsMessage; developers original intent. TDD mean all intended capabil-
$head = $target->head(); ities are demonstrated by the test suite. Finally, if anything
static::assertInternalType('array', $head);
happens which breaks the BatsMessage class, this test suite
}
will inform us the next time its run.
The simplest thing possible to pass the test:
Four-Phase Test
public function head() {
return []; xUnit Test Patterns: Refactoring Test Code7 by Gerard
} Meszaros (p.358) explains:
The body() test and function How do we structure our test logic to make what we are
LISTING 2 testing obvious? We structure each test with four distinct
parts executed in sequence:
01. <?php namespace ewbarnard\DemoMockery;
02. 1. Setup
03. class BatsMessageTest
2. Exercise
04. extends \PHPUnit_Framework_TestCase
05. {
3. Verify
06. /** @var BatsMessage */ 4. Teardown
07. protected $target;
08. Meszaros continues:
09. public function setUp() {
10. $this->target = new BatsMessage; We should avoid the temptation to test as much function-
11. } ality as possible in a single Test Method [p.348] because
12.
that can result in Obscure Tests [p.186]. In fact, it is pref-
13. public function testConstructor() {
erable to have many small Single-Condition Tests [p.45].
14. static::assertInstanceOf(
15. BatsMessage::class, $this->target Using comments to mark the phases of a Four-Phase Test
16. ); is a good source of self-discipline, in that it makes it very
17. } obvious when our tests are not Single-Condition Tests.
18.
19. public function testHeadReturnsArray() { It will be self-evident if we have multiple exercise SUT
20. $head = $this->target->head(); (system under test) phases separated by result verification
21. static::assertInternalType('array', $head); phases or if we have interspersed fixture setup and exer-
22. }
cise SUT phases. Sure, these tests may workbut they
23.
will provide less Defect Localization [p.22] than if we
24. public function testBodyReturnsArray() {
25. $body = $this->target->body(); have a bunch of independent Single-Condition Tests.
26. static::assertInternalType('array', $body);
27. } 7 xUnit Test Patterns: Refactoring Test Code:
28. } https://www.amazon.com/dp/0131495054
LISTING 3
The sequence hasnt been obvious in our examples thus far, 01. <?php
but, if you know the pattern you can see this pattern present 02. namespace ewbarnard\DemoMockery;
in all tests. 03.
04. class BatsCommon
Spies And Mockery 05. {
Real code has interdependencies. Sure, when we start at 06. protected $parms = [];
the very beginning, we have no dependencies. Thats easy. 07.
But, later code does have dependencies. To continue writ- 08. /** @var mixed */
09. protected $caller;
ing tests to exercise our code we use spies and mock objects.
10.
Well look at the code first and think backward to how we 11. public function __construct(array $parms = []) {
might have tested it. And then, dont worry, we will test it. 12. $this->parms = $parms;
13. if (array_key_exists('caller', $parms)) {
For this article we are focusing on a single interdepen- 14. $this->caller = $parms['caller'];
dency, namely the connection between the CakePHP 3 15. }
framework and our BATS code. The connection (that is, 16. }
the interdependency) is in BatsCommon. BatsCommon is 17.
an abstract base class containing most of the glue code 18. protected function verbose($message, $lines = 1) {
19. if ($this->caller) {
between BATS and RabbitMQ. Were not showing any of
20. $this->caller->verbose($message, $lines);
the glue here. 21. }
22. }
What is a spy? A spy allows you to observe or verify the 23. }
internal state of the SUT (system under test). In our
example, the spy will be a child class which spies on its When caller is not passed into the constructor,
parent class. The child (spy) class is test code, and the verbose does not call caller.
parent (real) class is production code.
First Test
What is a mock object? A mock object also lets you Our first test, shown in Listing 4, checks the constructor.
observe or verify the internal state of the SUT. The Well use setUp() and use testConstructor() to ensure
difference is the mock object lets you to set up your expec- setUp() was indeed executed correctly. This becomes more
tations before the test, and the mock object verifies that important as we switch to using a spy.
each expectation was met. The Spy, by contrast, opens up All tests pass. How do we test that parameters are correct-
a backdoor into your production code, allowing your tests ly passed into the constructor, given they are stored in a
to poke around as needed. protected property? We use a spy, see Listing 5. Place the spy
CakePHP 3 supports verbose output8 which is only in the test folder, not in the src folder. It is not part of your
sent to the console when --verbose was specified on the production code.
command line, with its Shell::verbose() method.
Most of the BATS library is plain PHP, that is, not specifi- LISTING 4
cally part of the CakePHP ecosystem. Its helpful to hook into 01. <?php
CakePHPs console output functions, so I built this into the 02. namespace ewbarnard\DemoMockery;
base class BatsCommon (see Listing 3). The caller passes $this 03.
into the BatsCommon constructor which provides us access to 04. class BatsCommonTest extends \PHPUnit_Framework_TestCase
verbose(). 05. {
06.
Consider the list of tests which should have gotten us here: 07. /** @var BatsCommon */
If nothing is passed into the constructor, the set of 08. protected $target;
parameters is an empty array. 09.
Anything passed into the constructor is available as an 10. public function setUp() {
11. $this->target = new BatsCommon();
array key of $params property.
12. }
If caller is passed in, it is available as $this->caller. 13.
verbose() must be called with at least one parameter. 14. public function testConstructor() {
15. static::assertInstanceOf(
The second verbose() parameter defaults to integer 1. 16. BatsCommon::class, $this->target
When caller is passed into the constructor, verbose 17. );
calls it with both parameters. 18. }
19. }
8 verbose output: http://phpa.me/cake-console-output
LISTING 5
Test Spy [Meszaros, p.538] 01. <?php
02. namespace ewbarnard\DemoMockery;
How do we implement Behavior Verification? How 03.
can we verify logic independently when it has indirect 04. class BatsCommonSpy extends BatsCommon
outputs to other software components? 05. {
06. public function parms() {
We use a Test Double to capture the indirect output calls 07. return $this->parms;
made to another component by the SUT for later verifi- 08. }
09. }
cation by the test A key indication for using a Test Spy
is having an Untested Requirement [p.268] caused by an
inability to observe the side effects of invoking methods LISTING 6
on the SUT. Test Spies are a natural and intuitive way 01. <?php
that gives the Test Method access to the values recorded 02. namespace ewbarnard\DemoMockery;
during the SUT execution. 03.
04. class BatsCommonTest extends \PHPUnit_Framework_TestCase
Now, lets test the parameter-passing mechanism. 05. {
06. /** @var BatsCommonSpy */
public function testParms() { 07. protected $target;
$expected = ['a' => 'b', 'c' => 3]; 08.
$this->target = new BatsCommonSpy($expected); 09. public function setUp() {
static::assertSame($expected, 10. $this->build();
$this->target->parms()); 11. }
} 12.
All tests pass. We have duplication; its time to refactor as 13. protected function build(array $parms = []) {
shown in Listing 6. Note, its just as important to refactor 14. $this->target = new BatsCommonSpy($parms);
the tests and keep them as clean as the production code. Its 15. }
16.
important that future developers be able to understand your
17. public function testConstructor() {
tests as quickly and easily as possible. 18. static::assertInstanceOf(
All tests continue to pass. 19. BatsCommon::class, $this->target
20. );
Mock Object 21. }
You wont be surprised to learn that mock object [Meszaros, 22.
23. public function testParms() {
p.544] is one of the xUnit Test Patterns. Dont be put off by
24. $expected = ['a' => 'b', 'c' => 3];
the books 947 pages. Youll level up your unit-testing expe- 25. $this->build($expected);
rience every time you read a page or even a paragraph out of 26. static::assertSame($expected, $this->target->parms());
the book. 27. }
Its important to keep our test cases organized. How do we 28. }
do that? Theres a test pattern for that:
In other words, when your test setup changes, start a new
Testcase Class per Fixture [Meszaros, p.631]
test class (and file). The Implicit Setup mentioned above is
simply PHPUnits built-in setUp() function.
How do we organize our Test Methods onto Testcase
Classes? Our next test requires we set up a mock object. This is our
clue that its time to create a new test class.
We organize Test Methods into Testcase Classes based on Our list of tests:
commonality of the test fixture. When caller is passed into the constructor, verbose
calls it with both parameters.
As the number of Test Methods grows, we need to decide
When caller is not passed into the constructor,
on which Testcase Class [p.373] to put each Test Method.
verbose does not call caller.
Our choice of a test organization strategy affects how
easily we can get a big picture view of our tests. It also Given verbose() is a protected method, we need to
affects our choice of a fixture setup strategy. enhance our spy:
public function verboseSpy($message, $lines = 1) {
Using a Testcase Class per Fixture lets us take advantage $this->verbose($message, $lines);
of the Implicit Setup [p.424] mechanism provided by the }
Test Automation Framework [page 298].
LISTING 7
Our tests depend on some assumptions. Be sure the follow- 01. <?php
ing tests remain on our list (or have already been written): 02. namespace ewbarnard\DemoMockery;
If caller is passed in, it is available as $this->caller. 03.
04. use Mockery as m;
If caller is not passed in, $this->caller is null.
05.
verbose() must be called with at least one parameter. 06. class BatsVerboseTest extends \PHPUnit_Framework_
The second verbose() parameter defaults to integer 1. TestCase
07. {
One great way to ensure those tests arent forgotten is to
08. public function tearDown() {
write empty tests and mark them incomplete. For example 09. m::close();
(string split for publication): 10. }
public function testCallerNull() { 11. }
static::markTestIncomplete('If caller not passed '
. 'in to constructor, $this->caller remains null'); LISTING 8
}
01. public function testVerbose() {
When we run the tests we have the reminder: 02. $caller = m::mock()->makePartial();
03. $caller->shouldReceive('verbose')
There was 1 incomplete test: 04. ->once()
05. ->withArgs(['test', 5])
1) ewbarnard\DemoMockery\BatsCommonTest::testCallerNull 06. ;
If caller not passed in to constructor, $this->caller remains 07. $parms = ['caller' => $caller];
null 08. $target = new BatsCommonSpy($parms);
09. $target->verboseSpy('test', 5);
OK, but incomplete, skipped, or risky tests! 10. }
Tests: 6, Assertions: 5, Incomplete: 1.
We can also mark tests as skipped. Use skipped when tests method with m::close(). Mockery runs its verifications
should not run given the current environment. For example, during m::close(). Without that call, youre not testing what
a frameworks MySQL tests should only run when MySQL you thought you were. I use Mockery as m as a convenience.
is available. Otherwise, they should report themselves as All tests pass. Here is what the above code does:
skipped. 1. M::mock() creates a mock object. We dont care about
The second of our tests is easy. If caller was not passed its class. We could have called m::mock('BatsCommon')
to the constructor, $this->caller should not be referenced to mock the BatsCommon class. PHP must be able to
as an object when calling verbose(). So, we simply call find the class for Mockery to mock it. makePartial()
verbose(). If nothing blows up, the test passes. tells Mockery to only mock those methods named in
public function testNoVerbose() { the upcoming expectations. Any other method calls
$this->target->verboseSpy('test'); pass through to the real class being mocked.
static::assertTrue(TRUE, 'test blows up otherwise'); 2. Sets expectations. This mocked object should
} get verbose() method called exactly once as
The test passes. Now we need to create a Mock Object verbose('test', 5). The test tearDown() will verify
[Meszaros, p.544]: all expectations were met.
3. Sets the parameter list we will be passing to our
How do we implement Behavior Verification for indirect BatsCommon object constructor.
outputs of the SUT? How can we verify logic inde- 4. Creates our BatsCommon object.
pendently when it depends on indirect inputs from other 5. Calls verbose(). Its a protected method, so we have
software components? the spy call the class for us.
The TearDown method calls m::close() which verifies that
We replace an object on which the SUT depends on with
all expectations were met. One thing you should note: if the
a test-specific object that verifies it is being used correctly
expectations are not met, the PHPUnit output can be confus-
by the SUT.
ing. Change the call from $target->verboseSpy('test', 5);
The Mockery package makes it ridiculously easy to replace to $target->verboseSpy('test', 6);. In other words, call
dependencies and verify the dependencies were exercised as verbose() with the second parameter being 6 rather than 5.
expected. PHPUnit spews the following:
Create a new empty test class as in Listing 7 which correct-
ly uses Mockery.
When you use Mockery, remember to include a tearDown()
When you have a lot of complex business logic, that logic yourself spending a larger proportion of your time in the
needs to go somewhere. When youre doing Test-driven red-green-refactor cycle of real development, which can be
Development and aim to keep your method complexity low, quite fun. Youll find yourself spending less time debugging
you tend to have a lot of protected methods laying out that in production, which is generally not fun.
business logic. When you are developing code, your testing target is that
If you have a lot of protected methods, you may have one method youre working with at the moment. Everything
another class trying to get out. But those protected methods else, even that related method ten lines down, is a dependen-
still need to go somewhere. Whether you have two protected cy. Put your target method in laser focus. If anything else
methods in class A and two more in class B, you still have makes it difficult to test, use mocks, spies, and anything else
four protected methods which might play best by being indi- in your arsenal to push those dependencies aside.
vidually tested when youre doing Test-first Development. Looking ahead, Part Five: Producer-Consumer Program-
That wont always be the case, but when it is, I say just mock ming in CakePHP/RabbitMQ brings our case study full circle.
the protected method and get on with it. Well see a bit more code, and look at the radically different
Your most likely alternative is PHPUnits data provider. way of thinking that gets us there.
You can pass a series of inputs through the public method
and verify return values (or use a spy to verify internal state). Ed Barnard began his career with
You can drive your development by adding more and more CRAY-1 serial number 20, working
cases to the data provider: add another use case to the data with the operating system in assembly
provider; watch it fail; write the minimum code to make it language. Hes found that at some point
pass; refactor. That refactor may well involve extracting the code is code. The language and details
new logic to a new protected method. dont matter. Its the craftsmanship that
matters, and that craftsmanship comes
Summary from learning and teaching. He does PHP
Theres no way around this: unit tests are tricky because and MySQL for InboxDollars.com. @ewbarnard
dependencies are tricky to test. Learn the craft and prac-
tice, practice, practice. It gets better. It gets smoother. Youll
find your judgment more and more reliable. Youll find
Ever wondered what your application might do if you could change what these functions
returned? Then welcome to Monkey Patching. In this months edition of Education Station,
were going to see how to apply monkey patching in your testing efforts.
Through these libraries, Ive learned to create tests with As you can infer from that description, its not as powerful
mocks, spies, and fakes to help me ensure the code at mocking as Rubys implementation. But, for our purposes,
which Im creating does what I expect it should. After its perfectly adequate. So, lets install it. As always, lets use
I felt comfortable with my level of expertise there, I Composer to take care of the heavy lifting, by running the
moved on to learning about mutation testing, which I following Composer command in the root of your project:
wrote about back in the March 2016 issue5.
composer require --dev php-mock/php-mock
If you missed that months column, mutation testing
creates pseudorandom variations in your code and checks if After a minute or so, if that, youre ready to go. In your
your tests complain. If not, then theyre perhaps not as reli- favorite editor or IDE, create a new PHP file with the
able as you might think. contents in Listing 1 graciously borrowed from the official
documentation for Mocking built-in functions8. Now, lets
As a result of learning all these libraries, I feel a lot more
step through it together.
rounded as a developer; I feel a lot more comfortable with
the code I ship. While I love these libraries, there are times Up until now, what weve done is used a MockBuilder
that I have felt the need to go further. object to indicate were going to override the built-in
time() function, using the body of the closure passed to the
After all, if I can mock the third-party libraries which I
setFunction() method. Instead of returning the current
use, why not mock the granddaddy of them allPHP? Why
system timestamp, it will instead return the fixed timestamp
should that be any different? Arguably, its not. It should be
value 1417011228.
fair to treat PHP as just another third-party library which my
application makes use of. With the mock built, we can now run some assertions upon
it, which further demonstrate the functionality of the library.
Now, I cant say with complete confidence, but I believe
At the point that we run the first assertion, the monkey patch
up until version 5.3.0, it wasnt possible to mock PHPs built-
for the time() function is not in effect, as $mocks enable()
in functionality; which includes such functions as time(),
methods not been called.
rand(), array_key_exists(), push(), and so on.
However, with the advent of namespaces, and the name-
spaces fallback6, it now is. If you have a second, have a read
through that section of the PHP manual. If not, I want to LISTING 1
draw your attention to one key sentence: 01. <?php
02.
03. namespace foo;
For functions and constants, PHP will fall back to global
04.
functions or constants if a namespaced function or 05. require_once('vendor/autoload.php');
constant does not exist. 06.
07. use phpmock\MockBuilder;
What this means is were now in a position to play around 08.
with PHPs internals, while not actually changing them. 09. $builder = new MockBuilder();
Think about the possibilities this opens up. Lets not just 10. $builder->setNamespace(__NAMESPACE__)
think about them, lets get in and find out with some code. 11. ->setName("time")
12. ->setFunction(
PHP Mock 13. function () {
Specifically, were going to experiment with a library which 14. return 1417011228;
15. }
facilitates this very functionality; its called PHP-Mock7. Writ-
16. );
ten by Markus Malkusch, it allows for the mocking of PHPs 17.
built-in functions, including time(), exec(), and rand(). In 18. $mock = $builder->build();
the words of the project itself, PHP-Mock: 19.
20. // The mock is not enabled yet.
Is a testing library which mocks non deterministic built- 21. assert(time() != 1417011228);
in PHP functions like time() or rand(). PHP-Mock uses 22.
that feature by providing the namespaced function. 23. $mock->enable();
24. assert(time() == 1417011228);
25.
26. // The mock is disabled and PHP's built-in time() is called.
27. $mock->disable();
5 the March 2016 issue: 28. assert(time() != 1417011228);
https://www.phparch.com/magazine/2016-2/march/
6 the namespaces fallback:
https://www.php.net/language.namespaces.fallback 8 Mocking built-in functions:
7 PHP-Mock: https://github.com/php-mock/php-mock https://github.com/php-mock/php-mock-prophecy
Here, our patched copy of time() will still return the same these are the currently defined namespace, and the name of
value, just supplied slightly differently. the method that were going to monkey patch.
PHP Mockery Mock After that, we make a call to andReturn() supplying the
value we want to return, when that method is called. As in
Now, this is all well and good. But, perhaps youre
the first example, we can test it using assert().
concerned about having to learn yet another library, as youre
already familiar with others, such as Mockery9 and Prophe- One thing to note about the Mockery extension is that
cy10. there is no call to enable the patch. So once a method is
patched, then its done for all tests. After all of your tests
If thats the case, dont sweat it. PHP-Mock works with
are done you have to call Mockerys close() method. This
both via extensions. Lets see how to use it with Mockery, my
collectively disables all patched methods.
currently preferred mocking library.
As always, well need to install the Mockery extension11, PHP Prophecy Mock
again using Composer. To do so, run the following command, Lets have a look at how to use PHP-Mock with Prophe-
in the root of your project directory: cy. As with Mockery, we first need to install the extension
composer require --dev php-mock/php-mock-mockery for Prophecy12. Using Composer, we do this by running the
following command, from the root of our project:
When the librarys installed, in a new file, perhaps called
php-mockery-mock.php, add the code in Listing 2. composer require --dev php-mock/php-mock-prophecy
Theres not a lot to this, which is handy. Here, we make a With that done, lets create one final PHP file, called
call to the static mock() method, passing in two parameters; php-prophecy-mock.php. In it add the code shown in Listing
3.
9 Mockery: http://docs.mockery.io/en/latest/
10 Prophecy: https://github.com/phpspec/prophecy
11 the Mockery extension: 12 the extension for Prophecy:
https://github.com/php-mock/php-mock-mockery https://github.com/php-mock/php-mock-prophecy
Similar to how the library works standard. As with the Mockery exten- you need to
with Mockery, we dont have to do sion, the Prophecy extension doesnt
much to patch a method. First, create have an enable() method. In Conclusion (tl;dr)
a new PHPProphet object, which will So, once a patch is created, its in Thats been a whirlwind introduction
store our patched functionality. effect for that namespace. However, to monkey patching in PHP, specifical-
On it, call the prophesize() method, also like Mockery, they can be glob- ly within the confines of testing. While
passing in the name of the namespace ally disabled. This is handled by the Im not against using the functionality,
within which we will create patched checkPredictions() method. its not something I would wholeheart-
functionality. The example above uses edly encourage, for the reservations
PHPs __NAMESPACE__ directive to spec- Something to Keep In Mind stated above.
ify the current namespace. Theres just one thing to bear in However, in the right circumstances,
Doing so will return an object imple- mind when using these libraries. The when applied with due thought and
menting ProphecyInterface, similar functions namespace cannot be discipline, I dont see any harm in it.
to how Prophecy typically works. On qualified. Any functionality which Whats more, like interactive rebasing
that object, call the method you want you want to patch has to be within in Git, I see it as a good tool to make
to patch, as though it were a meth- the current namespace. So you cant use of. Just dont get carried away with
od on the object, and then call the patch \DateTime. But, you could patch it.
willReturn() method to specify the DateTime. Dont let that trip you up. If
value the patched method should
return. Matthew Setter is an independent software developer who special-
izes in creating test-driven applications, and a technical writer
Next, call reveal, to reveal the
http://www.matthewsetter.com/services/. Hes also editor of Master Zend Frame-
prophecy. If youre familiar with using
work, which is dedicated to helping you become a Zend Framework master? Find
Prophecy, all of this should be pretty
out more http://www.masterzendframework.com/welcome-from-phparch.
your application.
y
Chris Tankersle
Purchase Book
http://phpa.me/docker4devs
Chris Tankersley
Understanding Objects
David Stockton
As developers, we understand how our programs work. Perhaps thats an
assumption. Some of us understand how certain aspects of our code works.
Others, but fewer, understand all aspects of how the code we write works. As developers who
are improving and making the transition from junior to mid-level and to senior and beyond,
our understanding increases as we learn the ins and outs of the language, the application and
the techniques that work to build successful applications. Over the next few months, well be
building up a solid foundation of Object-oriented concepts and patterns, how they work, and
how Object-oriented code can be used to build more maintainable software.
Some of you are likely quite familiar object-oriented language, there was but I had no idea at the time. Every site
with Object-oriented programming really no discussion or learning about and tutorial I found (thanks, Alta Vista,
(OOP). This article may not be for you, objects in the Java class. We all just Yahoo! and WebCrawler, and a little bit
but I hope there are some aspects Ill learned that our classes filled with later Google) relied on register globals;
cover that will be new or open up new functions all worked if the function database examples included queries
ways of thinking. This month, though, definitions started with public static built with string concatenation, usually
I want to address developers who want and they stopped working if we directly from PHPs superglobals. PHP
to learn more about OOP. removed static or changed public to 3 didnt support objects. The scripts
something else, even though the code were easy to follow, mostly. You started
Some Background could compile. at the top, read down, and everything
I realized a few days ago that some- In 1997, I was tasked with building was mixed together. HTML, code, SQL
time this year Ill have been writing a website which allowed users to find queries. In some cases, there was a full
code for 30 years. Ive been doing it information about articles concerning file of just functions which would be
professionally (read: getting paid for women in tech and computing. Id built require_onced into another file, but
it) for nearly 20. I started with BASIC some basic sites with HTML and some that was typically the extent of the
on an Apple II clone. In college, I garbage JavaScript that would animate organization and structure.
learned enough Fortran to test out of elements around the page, thanks to Shortly after I started learning PHP
the class and then went to C, C++, and Adobe GoLive, but I hadnt yet built on the Homesite web server, PHP 4
Java. We covered the concept of class anything that would dynamically was released. PHP 4 had some prim-
in C++, sort of, but despite being an create different pages based on user itive object support, but nothing like
input. I did some research, what weve had since around PHP
considered Bash for the job, 5.2. I remember trying to learn OOP
quickly found it was going with my procedural background was
to be a nightmare and went a struggle. The early examples I found
back trying to find answers were ill-conceived, and there wasnt
for something that would a lot of explanation. I came to the
work. During my quest to conclusion OOP was not something
find a solution, I stumbled that helped with anything.
on ASP, but it required an With PHP 5, the language improved
expensive development and gained better support for OOP
tool and web server. over the years. While PHP still
I even considered writing supports coding procedurally, much
it in C and using CGI, but of the current code youll see written
fortunately, I stumbled on in the language is Object-oriented, or
PHP and some tutorials taking advantage of the OOP features
on webmonkey.com. They PHP brings. And, here we are now. Ive
were undoubtedly awful been developing my PHP code using
and full of security issues, OOP concepts for more than a decade
now. I want to share some of what Ive limitation of objects needing to behave
learned in hopes that I can help you like, or even have a real-world equiva-
come to a solid understanding much lent at all.
more quickly than I did, by avoiding Lets take a quick look at what I
some pitfalls which took me a long mean. Suppose theres a Car class that
time to get around. does all sorts of Car things. Cars need
engines, so we give the car an engine.
There are plenty of excuses Ive
heard to avoid OOP. To truly take $engine = new Engine();
advantage, it requires thinking in a $myCar = new Car($engine);
$yourCar = new Car($engine);
different way than procedural code.
Some people argue their appli- In the example above, we have two
cation isnt complicated enough separate and distinct objects that are
to warrant using OOP. That was cars, and they have an engine. They can
one of my excuses before I saw a do all the things cars with engines can
good example. Properly modeling do. However, both cars have the exact
objects with data and behavior same engine. In the real-world, this is
takes practice. On the other hand, ridiculous. Every car in the real-world
properly designed classes and needs its very own engine. But in OOP,
and $myCar goes nowhere. There are
objects lead to code that is easier its perfectly legitimate and often desir-
numerous ways to design the system
to test, as well as code you can able to provide the same object to other
differently, so this is not an issue, but
reuse in ways you may not have objects at the same time during the
my point is that we still have to build
conceived of originally. Ive found same execution of a program. However,
and design systems in such a way that
that the simpler the classes, the there are design decisions which need
they do what we want and dont do
easier it is to use in different ways. to be considered when doing this sort
what we dont want.
Some people feel OOP takes longer of thing.
to write. In some cases, this may Consider the two cars with one You may be wondering why theres
be true since it does take time to engine example. Suppose the car only one engine for both cars. In
design objects. However, as far provides us some methods like turnOn the Zend Engine, and in other
as actually writing the code, its and pressGas. We build our class so languages, objects are assigned by
faster in many cases. If the objects that if the car is on, pressing the gas reference so $engine always refers
have one job, then being able will move it forward, and if the car is to the same instance of an object,
to focus on only that small part off, pressing the gas has no effect. So even if we pass it into more than
means the code can be simple and taking our two cars we run the follow- one object that needs an engine.
straight-forward. ing code: This wasnt the case in PHP 4.
up, we have superglobals1. These are variables that are typi- Visibility Modifiers
cally defined and assigned by PHP, in most cases before your
When starting out, it seems that knowing as much as possi-
script starts. They include variables like $_SERVER, $_REQUEST,
ble about how every aspect of the code works is important.
$_GET, $_POST, and others. $_SESSION is initialized when you
For a programmer, knowing it all is good. However, from the
start a session. They are accessible by everything in your
perspective of the code, its best to limit what any given piece
program without the global keyword. Its usually in your best
of code knows about as much as possible. To use another real
interest to avoid using these in your code directly. Its better
world analogy for this point, imagine
to abstract usage of these into as
how difficult it would be to drive
few places in code as you can. I
prefer to let whatever framework This hiding of information a car if we had to know every
aspect of how a car works. If we
Im using deal with those vari- and functionality is called were required to understand that
ables usually.
encapsulation in OOP pressing on the gas moves a lever
Next, we have globals2. These (the pedal), which pulls a cable,
are also variables that are avail- terms. It means we should
which is attached to another lever
able everywhere in your program. only expose the minimal which opens a and the, so
Unlike superglobals, unless the
code were talking about is not
amount of information we more gas is pumped through the
and then, you know um, stuff
defined in a function or class, you can for the system to work. happens and the car goes fast-
have to tell PHP that you want er. If we were required to know
to access the global. Again, this or keep in mind all the workings of the
should be avoided at all costs. Globals can be dangerous in cars systems in order to drive, wed probably have a lot fewer
that they lead to unpredictable execution and bugs which drivers and very few people would know how to drive more
are difficult to track down. Because they can be changed by than a single vehicle. Instead, all of the complex workings of
anything within your code, changing the order that certain the car are hidden behind a few common interfaces that are
bits of code run can cause it to have a completely different shared between different vehicles. We have gas pedals, brake
result. pedals, and a steering wheel. Whether your car has a rack
Once we introduce classes, there can be variables at the and pinion, power steering, articulated steering, or some-
class level. These are class static variables. They belong to the thing else, the steering wheel abstracts that all away so all
class, not an object or instance. we need to know is when we turn the wheel clockwise, the
class Samoflange vehicle goes to the right, and counter-clockwise goes to the
{ left. It means in general if you know how to drive one car, you
public static $doohickey; can drive almost any other car.
}
If a static class variable is defined as public, it is effectively
a global variable, but rather than accessing it via $doohickey
it is accessed via Samoflange::$doohickey. Whether these
variables are defined as public, protected, or private, they are
all effectively global. The protected and private static class
variables are just globals that cannot be read from every-
where.
Next, we have instance variables or variables that belong to
a single instance of a class. These are the fields or attributes
that can be used to hold on to state and dependencies of an
object.
Finally, we have the local variable scope. These variables
are defined, used, and eventually destroyed when the method
or function completes. By the way, if youve heard the term
method before and not understood what that is, its just a
function defined within a class. The value of local variables
can live beyond a function or method if they are returned.
1 superglobals:
https://www.php.net/language.variables.superglobals
2 globals: https://www.php.net/language.variables.scope
This hiding of information and functionality is called one object to another, rather than making a bunch of objects
encapsulation in OOP terms. It means we should only that inherit and change functionality.
expose the minimal amount of information we can for the If inheritance is not your thing, or you feel that a class
system to work. PHP provides visibility modifiers we can use should never be extended, you can declare it final. This
to allow the language to enforce what parts of the program ensures at the language level that nothing else can extend
can see variables or access a method. Some other languag- it. Marco Pivetta wrote a good post arguing that class-
es (like Python) dont have this and rely on convention, and es which implement an interface, and no other public
variable naming to infer that certain variables or functions methods should always be marked final. You can read it at
should not be used by developers. You may see old PHP code http://phpa.me/ocramius-final-class
where variables and functions are prefixed with an under-
score (_), which was how PHP 4 code hinted at visibility and Do One Thing and Do It Well
trusted convention to enforce it. Ive said for years that if you have to use the word and to
PHP provides three levels of visibility in objects: public, describe what the purpose of your class is, then its doing too
protected, and private. much. The same can be said at the method level and argu-
Public variables and methods are ably at the namespace or package
available to anything in our code level as well. In OOP, we get
with access to the instance of better, more powerful code by
that object, as well as any code I want to talk about inheri- building simple objects and
combining them. If youre
in the object itself. tance, mostly to say in most building a class and you
Protected hides variables and
methods to the outside world,
cases, inheritance is not the find the functionality youre
and makes sure the code is only right way to solve a problem. creating could be delegated
or moved out, chances are
available within the class and
its a good idea to do that. It
its inheritance hierarchy. Dont
may end up that your orig-
worry if you dont know what I mean by
inal design was fine with the functionality happening in a
inheritance; well get to it later.
single object, but later as requirements change and the soft-
Finally, private variables and methods are available only ware evolves, more classes and objects may make themselves
to instances of that class. This is not a typo. In addition to apparent.
private variables and methods being usable within a single
Creating classes that are limited in what they do as well
instance, if another class of the same type has access to a
as what is exposed via the public visibility modifier means
different instance, it can access private variables and call
maintenance of the code will be easier, testing it will be
private methods on that other instance.
simpler, and refactoring, updating, adding and changing
Inheritance of Classes functionality will be more straightforward and cheaper.
I want to talk about inheritance, mostly to say in most Conclusion
cases, inheritance is not the right way to solve a problem.
Over the coming months, well focus more on different
However, since it seems every introduction to OOP includes
patterns I see and use in code, talk about why those patterns
a discussion on inheritance, I shouldnt pretend it doesnt
exist, what they are good for, and how they should be used.
exist. Inheritance allows a class to be built based on anoth-
This month, I talked some aspects of my philosophy towards
er class. If you can describe the relationship between two
Object-oriented programming and design which should
classes with is a then inheritance might be a good solution.
help in understanding why I build some things in the way I
However, if the relationship is not is a, then it is definitely
do. I hope youll join me next month. If youve got a partic-
the wrong solution.
ular pattern or question about Object-oriented design or
In PHP we use inheritance with the extends keyword. For programming that youd like to read about, please be sure to
example: reach out and let me know.
class Foo extends Bar {
} David Stockton is a husband, father and Software Engineer
and builds software in Colorado, leading a few teams of
In this case, were saying a Foo is a Bar. Foo should be
software developers. Hes a conference speaker and an active
providing some different but related functionality, potential-
proponent of TDD, APIs and elegant PHP. Hes on twitter as
ly overriding or augmenting existing functionality. Over the
@dstockto, YouTube at http://youtube.com/dstockto, and can
next few articles, I hope to show and convince you (if you
be reached by email at [email protected].
dont already believe it) that we should prefer composition
over inheritance. This means that we can build powerful
solutions by combining objects and delegating work from
Functional Programming in PHP will show you how to leverage these new
language features by understanding functional programming principles. With over
twice as much content as its predecessor, this second edition expands upon its
predecessor with updated code examples and coverage of advances in PHP 7 and
Hack. Plenty of examples are provided in each chapter to illustrate each concept as
its introduced and to show how to implement it with PHP. Youll learn how to use
map/reduce, currying, composition, and more. Youll see what external libraries
are available and new language features are proposed to extend PHPs functional
programming capabilities.
Last year, we saw continued adoption of PHP 7 with good- I want to send it out to everyone in the PHP community: core
ies like static type hints. The core team capped off the year developers, extension writers, documentation maintainers,
with the release of PHP 7.1. Six years on, we have more events, user group leaders, conference organizers, and PHP program-
user groups, and contributors in our corner of the web. This mers around the world. This is Uncle Cal saying Thank You,
year, instead of selecting only certain people for it to go out to, to you.
Past Events
December 2016
SymfonyCon Berlin 2016 PHP Conference Brazil 2016
December 13, Berlin, Germany December 711, Osasco, Brazil
http://berlincon2016.symfony.com http://www.phpconference.com.br
ConFoo Vancouver 2016 MageCONF16
December 57, Vancouver, Canada December 10, Kiev, Ukraine
https://confoo.ca/en/yvr2016 http://mageconf.com
Upcoming Events
January May
PHPBenelux Conference 2017 phpDay 2017
January 2728, Antwerp, Belgium May 1213, Verona, Italy
https://conference.phpbenelux.eu/2017/ http://2017.phpday.it
php[tek] 2017
February May 2426, Atlanta, Georgia
SunshinePHP 2017 https://tek.phparch.com
February 24, Miami, Florida
PHP Tour 2017 Nantes
http://sunshinephp.com
May 1819, Nantes, France
PHP UK Conference 2017 http://event.afup.org
February 1617, London, U.K.
PHPSerbia Conference 2017
http://phpconference.co.uk
May 2728, Belgrade, Serbia
http://conf2017.phpsrbija.rs
March
ConFoo Montreal 2017 International PHP Conference 2017
March 810, Montreal, Canada May 29June 2, Berlin, Germany
https://confoo.ca/en/yul2017/ https://phpconference.com
The State of PHP Security there have been a lot of tools and arti- Some Security New Years
cles released to help you along the
In December of 2015, we were path to more secure code. Right here, Resolutions
presented with the most major update in php[architect], there have been Its a new year, and theres still plen-
to the PHP language in yearsPHP some great security-related articles ty of work to be done when it comes
7. This version came with a whole published over the past year including: to securing PHP applications. While
host of new features including several there were a lot of advancements in
Drupal Security: How Open
that relate more to the security side of the previous years, there are still many
Source Strengths Manage Software
things (like the filtering allowed with things that have to be done manually
Vulnerabilities by Cathy Theys
unserialize). Since its release and the because of the nature of the language.
release of follow-up versions, over 80 Learn from the Enemy: Securing
Your Web Service Parts 1 & 2 by Its a habit this time of year to have
CVE (Common Vulnerabilities and some New Years resolutions; things
Exposures) security issues have been Edward Barnard
you want to improve over the course of
identified and resolved, usually within Implementing Cryptography by
the next year. Id like to propose a few
a few weeks of them being discovered Edward Barnard
development security related items
and reported. The PHP development In addition to these articles theyve you could add to the list to help you
group and all of its members have done also released a Web Security anthol- be more proactive when it comes to
an excellent job doing the hard work to ogy by combining these and several security.
make PHP as secure as it can be. others into a single book, Web Security
The beginning of December 2016 saw 20162. Dont Just Think Happy,
the release of PHP 7.1, the first minor Another group promoting security Think Bad Too
release in the post-PHP 7 world. This in PHP is The Paragon Initiative group3.
Its all too easy in our day-to-day
release added plenty of new features Theyve used both their blog and devel-
development to think along the happy
in its own right including the start of opment work to teach PHP developers
path. We write our code and madly
a long road for a lot of developers security from the start and teach best
click until things work as expected and
the deprecation of mcrypt1 support in practices for security current applica-
(hopefully) bug-free. We pat ourselves
PHP core. The mcrypt library itself is tions. Their blog covers topics like:
on the back and move along to the
way out of maintenance, having been public key encryption in PHP next task, sometimes building on the
abandoned in 2007. Its about time
secure software updates previous feature and others moving on
PHP deprecated it in favor of other
securing account recovery to a completely different topic. Were
functionality like OpenSSL. There are
cryptographically strong random all guilty of it, and the happy path is
a lot of libraries out there based on
number generation what meets the deadlines and makes
mcrypt, so this could be a major shift
product managers happy.
for many tools. Fortunately, OpenSSL secure serialization of data
support comes standard in just about However, what happens when some-
Theres plenty more out there, so be
every PHP installation these days so thing breaks? What happens when a
sure to check out the Paragon Initiative
making the transition should be rela- bug report is filed that an endpoint a
blog4 for lots of great articles.
tively easy with only a few gotchas to developer forgot to secure is spewing
look out for. private customer information out for
all the world to see? Its possible that,
Sharing the Knowledge 2 Web Security 2016:
http://phpa.me/web-security-2016 if the developer had thought like an
Along with all of these core changes, attacker about the work they were
3 The Paragon Initiative group:
https://paragonie.com doing they would have realized rely-
ing on security through obscurity is
1 deprecation of mcrypt: 4 Paragon Initiative blog:
https://wiki.php.net/rfc/mcrypt-viking-funeral https://paragonie.com/blog never an effective method. Had they
protected this chunk of code the pants coding standards and have sniffs applications. Its all too easy to come
on fire feeling they had when the bug to detect issues with syntax. Maybe up with grandiose plans that rely on
report came in could have been avoid- theyve even set up a mandatory review multiple moving pieces to protect your
ed. process before any code can make it to code. Maybe its coming up with some
When youre working through your production. All of these are great, but kind of special encryption scheme for
code, sit back for a second and think if you dont have any security-related your data (hint: dont do this) or maybe
about how it could be abused. Consid- step in your workflow youre missing a its implementing a more complex than
er the most common kinds of attacks major component. required authentication mechanism
out there today (XSS, CSRF, SQL injec- One of the biggest complaints I hear when just a username and passphrase
tion, etc.) and see if the code you just from developers is that worrying about will do.
wrote could be vulnerable. The larger security takes too much time or its Keeping it simple applies to data
the application, the more tricky this just one more thing to worry about access in your application too. Ive seen
becomes but there are many tools out when theyre writing their code. Devel- plenty of applications which require a
there to test for these things and librar- opers who talk like that have something series of complex steps to filter out or
ies to help prevent them. in commontheir efforts to integrate modify data based on who is accessing
secure development into their work- it. Unfortunately, these steps are imple-
Validate All the Things flow have security as an add on closer mented different ways in different
The major issues in web applications to the end of the development process. places, resulting in potential exposure
boil down to one thingimproper Theyre given the command and of sensitive information. Rules and
validation practices. Much like the now make sure its secure, but only functionality like this should be kept in
happy path development practices, after theyve completed the happy a standard place and should be some-
its all too easy to fall into the trap of path through their code. This results thing which can easily be applied to
thinking the input into your applica- in developers feeling like they have to any data set across the application with
tion will only come from the forms go back and wade through all the work very little effort.
you provided or some other trusted theyve just done to look for potential Remember, complexity is one of the
source. You set up your inputs and pull security issues. main enemies of security. The more
in the values for use on submit, but The real key to integrating securi- complex a system is the harder it is
how can you be sure what youre using ty and secure development into the to secure. Keeping things simple and
isnt tainted? workflow is to shift left and move approaching them from both a fail
Validation of all data, most impor- the security evaluation back before the fast and principle of least privilege
tantly user input, should be a development work is even done; back mindset helps you create more secure
requirement on your resolution list to the planning stages. New features and robust code thats easier to work
for this year. Most frameworks make and changes should be evaluated prior with in the long run.
it simple to validate the incoming data to code being written or changed to
on your requests with handy libraries see what possible threats it could be A Secure and Safe 2017
or, if there isnt one, it can be included vulnerable to. These findings should So, heres to wishing you a secure and
easily with Composerlike Respect/ then be passed over to the developers safe start to 2017! I hope youll take
Validation5. Trust me; theres nothing writing the actual code to keep in mind some of these resolutions to heart and
like the satisfaction the data youre as theyre hacking their way to success. apply them during your day-to-day
working with is exactly what youre By introducing it at this level, youre development. Its amazing how just a
expecting and that there are no sneaky setting the development up for a more few simple steps can help protect your
attackers worming their way in. secure end result and making your code (and your company) from embar-
development group much happier in rassing or damaging breaches. I hope
Implement a Development the long run. over the course of 2016 Ive provided
Security Workflow you with some helpful hints, good
Keep It Simple resources, and methods you can use in
Most larger development groups
As a final resolution to add to your your code. Its a new year, and theres
(and some smaller ones) have
list, I want you to remember to think plenty of work to be donelets get out
embraced the idea of continuous
simple when it comes to securing your there and drive security forward!
integration and, possibly, continuous
deployment. Theyve set up their auto-
For the last 10+ years, Chris has been involved in the PHP community. These
mated testing and workflow tools to
days hes the Senior Editor of PHPDeveloper.org and lead author for Websec.io, a site
notify them of breakage in their test
dedicated to teaching developers about security and the Securing PHP ebook series.
or other resources. Theyve defined
Hes also an organizer of the DallasPHP User Group and the Lone Star PHP Confer-
5 Respect/Validation: ence and works as an Application Security Engineer for Salesforce. @enygma
http://phpa.me/respect-validation
December Happenings
PHP Releases
PHP 7.1.0:
http://php.net/archive/2016.php#id2016-12-01-3
PHP 7.0.14:
http://php.net/archive/2016.php#id2016-12-08-1
PHP 5.6.29:
http://php.net/archive/2016.php#id2016-12-08-2
News
Freek Van der Herten: Symfony and Laravel will Fabian Schmengler: Collection Pipelines in PHP
require PHP 7 soon In a new post to his site Fabian Schmengler has written up
As Freek Van der Herten mentions in this recent post an introduction to collection pipelines and how it could
to his site, it was announced by both Fabien Poten- be applied to a Magento-based environment. He starts
cier (Symfony) and Taylor Otwell (Laravel) that the by illustrating the idea in Bash and Ruby, showing the
upcoming versions of the frameworksSymfony 4 and three main types of collection operations: map, filter and
Laravel 5.5will require PHP 7 by default. Freek talks some reduce. He talks about the advantages these methods have
about the improvements that come with PHP 7 and which over traditional looping and what kind of value they can
he thinks will show up in the different frameworks codebase. provide in both Laravel and plain old PHP. He illustrates
http://phpdeveloper.org/news/24711 the PHP-only versions using the array_filter, array_map
and array_reduce functions and some thoughts on when its
Codeception Blog: Writing Better Tests: good to use them over normal looping (and when its not).
Expectation vs Implementation http://phpdeveloper.org/news/24748
The Codeception blog has recent post theyve written up
talking about writing better tests for your application and SitePoint PHP Blog: Social Logins with Oauth.
the difference between expectation and implementation as it ioLog in with Anything, Anywhere
relates back to meaningful tests. They give an example of a test The SitePoint PHP blog has a tutorial posted from Meni Alla-
thats bound to implementation details from the Magento man showing you how to use the OAuth.io SDK for social
codebase that relies on a specific function implementation (the logins, integrating multiple social network logins in one
method). This function is a part of the Symfony functionality, centralized place. The tutorial then breaks down the steps to
not Magento, and what might happen if things change in your follow for getting the service set up and getting the required
application. They note that the main difference is testing for package installed. Following this the author shows how to
the result versus testing for the behavior of the functionality. connect your account to the various services and provides
http://phpdeveloper.org/news/24742 the code youll need to connect to the OAuth.io service
http://phpdeveloper.org/news/24730
Paul Jones: PECL Request Extension: Beta 1
Released! Amine Matmati: Symfony: the Myth of the
As Paul Jones has announced in this post to his site the Bloated Framework
PECL Request extension has reached the beta stage with Amine Matmati has written up a post with a few quick
the release of beta v1. The post also lists out some of the points refuting the bloated framework myth as it relates
new functionality introduced in this beta mostly focused to the Symfony framework. He then goes on to talk about
around the fetching of the forwarded for information. how, despite many Symfony components being used indi-
http://phpdeveloper.org/news/24722 vidually by other projects, the overall framework still
has the reputation for bloat. He goes through some of
the main points usually mentioned by the opponents.
http://phpdeveloper.org/news/24719
WebTech
Refactoring your words, while
you refactor your code.
@KaraFerguson
karaferguson.net The Frederick Web Technology
Group
technology : running : meetup.com/FredWebTech
programming
rungeekradio.com
www.cafepress.com/phparch
ElePHPants
PHPWomen
Plush
ElePHPants
Visit our ElePHPant Store where
you can buy purple plush mascots
for you or for a friend.
We offer free shipping to anyone in the
USA, and the cheapest shipping costs
www.phparch.com/swag
www.phparch.com \ January 2017 \ 47
finally{}
On Being a Polyglot
Eli White
Why would I, in a magazine dedicated to PHP, be discussing the virtues of
being a polyglot? In computer science terms of course.
The reasons are very straightfor- was this gem of knowledge: Now we can see that
ward. There is a great benefit to be the PHP community is already poly-
found in exposing yourself to differ- PHP has always been a glue glot. Weve embraced Javascript (and
ent programming languages and the language. Everything we glue other technologies as well) as strongly
concepts that they hold. They make together has nothing to do with the as our own dear PHP. Its why at PHP
you a better programmer overall as language weve chosen. Things like conferences, you dont find talk after
you expand your knowledge. using Varnish for caching, using talk about PHP itself, but a diverse
Cassandra for sessions, building mix of topics ranging from databas-
polyglot / pl glt/ good relational database schemas, es to task queues. In fact, recently my
noun effectively using NoSQL data stores, current company announced it was
and building work queues with hosting CoderCruise3, a polyglot web
1. person who knows and is able to ZeroMQ. programming conference, and I had
use several languages. someone come up to me and thank
Davey Shafik me because they felt that the PHP
We have a huge benefit in the PHP community could surely pull off a
world that is rarely looked at. By our I cannot think of a more true polyglot event.
nature, we are already one of the most statement about the technology that
I have programmed heavily in Apple-
technology diverse communities. surrounds PHP. We have always been
soft Basic, Ada, C, Perl, Java, JavaScript,
Recently the PHP community post- a community, a technology, based
and PHP over the years, and dabbled
ed 24 stories for the first 24 days1 in around finding the best other technol-
in dozens more. Each programming
December, and in one of those posts2, ogies on the web and stitching them
language has taught me something
all together into a cohesive bundle of
new, which Ive brought back to PHP
tech that somehow works. From the
itself. So as a New Year Resolution I
very beginning PHP was built as an
encourage all of you to expand your-
add-on to a web server; an extra set of
self, to dabble in another language, see
keywords that you put into the HTML
what it has to offer you, and bring what
itself to make magic happen. For that
you learn back to our PHP community
matter, what PHP programmer out
so that we can continue to evolve and
there doesnt also have to dabble in
grow.
JavaScript. Its truly rare to find that
100% back-end PHP developer who
The mind of the polyglot is a very
doesnt end up writing JavaScript to go
particular thing, and scientists are
along with it.
only beginning to look closely at
PHP itself has grown organically, how acquiring a second language
adding features as it steals borrows influences learning, behavior and
ideas that originate in other program- the very structure of the brain itself.
ming languages. It began as a mix of C
and Perl syntax and has added concepts Jeffrey Kluger
from Java, JavaScript and others. PHP
itself could be described as polyglot,
being a programming language of 3 CoderCruise:
multiple ones fused together. https://www.codercruise.com
Eli White is a Conference Chair for php[architect] and Vice President of One
1 24 stories for the first 24 days: for All Events, LLC. He may be a polyglot in computer science terms, but has never
https://24daysindecember.net managed to master another spoken language (having had enough issues stumbling
2 one of those posts: through English.) @EliW
http://phpa.me/24days-php-dead