PHP, Zend Framework and Other Crazy Stuff
Posts tagged pear
Wishing For A PEAR Channel Aggregator? Yes, Please!
Apr 12th
There are a few things many PHP developers should be familiar with. We should be familiar with PEAR packages. We should be familiar with the PEAR installer. More and more of us actually are getting familiar with running PEAR channels. The problem that some of us have, like me, is that we’re working against an architecture which focuses on the central PEAR repository. It’s the elephant in the room, so to speak, and we spend year after year working around or completely ignoring it.
While I do criticise PEAR through this statement, that would be missing the point. PEAR is a story of two parts – the distribution mechanism using the PEAR installer and PEAR channels, and the centralised package repository. The problem is splitting the two, and I don’t believe the PEAR group (along with the rest of us) have really considered that which is a shame. As Till Klampaeckel recently stated, “PEAR packages are not as easy to use as some code you copy-pasted off the Zend devzone or phpclasses. While I agree, that we should try to make it just as easy, it’s just not one [of] PEAR’s goals right now.”.
That kind of sums up my issues with PEAR as a distribution mechanism – it’s not as easy as it could be and it really ought to be a primary goal. It’s not… PEAR obviously treats its own package repository as an elite citizen. You don’t need to discover its channel, or use a channel prefix, or go consulting Google to find something useful (it has a neat categorised package listing). No other PEAR channel set up independently has those advantages and the extra steps can’t compete with a simple download from a website. To install from another channel, you need to go search Google for a suitable library, check if it even has a PEAR channel, find the channel URI, guess the channel prefix – and then finally install something. Since channel discovery is probably not automatically performed (by default), dependency resolution can lead to more pain. This assumes hosting a PEAR channel is easy (which it is given the recent explosion of them using sane channel hosting tools like Pirum). Maybe that’s our trigger – PEAR channels are easy now. Suddenly, all the libraries I use are, or will be, hosted on a PEAR channel. Even Zend Framework 2.0 is heading to PEAR split by component. It’s fantastic!
Since we seem to like blaming the PEAR Group, and getting that ball kicked back to us, it’s time we did something useful. We’ve spent too much time ignoring PEAR as we grew apart from it with our frameworks, standalone libraries and custom plugin architectures. We’re making life harder for ourselves in doing so. Stuart Herbert has posted a short article to gather requirements for a Pear Channel Aggregator. I strongly suggest that interested PHP programmers drop by and add a comment with some suggestions/feedback. Let’s get this thing moving forward!
Gathering Requirements For A PEAR Channel Aggregator
Once that article went up, we started seeing that people are out there working on the problem. I don’t see any of them as being a final solution, and God forbid we just adopt a half-measure for some limited subset of requirements. It would be far preferable, if not essential, to see a complete solid solution meeting everyone’s varied requirements. There’s no excuse not to. Either we want a robust complete solution or we don’t.
As to the concept of a channel aggregator, I view it as a shift away from PEAR’s focus on a centralised repository to a focus on supporting and enabling an open decentralised system with one (or more) focal points. PEAR’s own channel should just be one among many. A channel aggregator should do away with channel prefixes, channel discovery, compulsory reliance on PEAR packaging/channel hosting (git is too popular to ignore), and support combining the package details from potentially hundreds of channels/git repos into one or more competing aggregators that offer easier lookup, rankings, user feedback, etc (baby steps obviously before we go nuts). Installing any package from any channel or supported git repo should only require hitting up a channel aggregator with its name – no other mucking around. It would become an ecosystem that is impossible to ignore and obvious to utilise for hosting and distribution, not simply of libraries but of components, plugins and anything that can be considered installable. I’m also being careful not to overly point at one aggregator – any decent decentralised system should be capable of supporting multiple nodes with indifference, working in tandem or not.
The other side of the problem is PEAR channel hosting and packaging. Hosting is easy. Setup Pirum and you’re ready to go. Packaging needs a better tool. A script that can accept a simple configuration file, build the package, and optionally include a cryptographic signature (because who are we kidding, we need the system to be secure). While building a robust system, overly relying on traditional PEAR distribution methods while the PEAR community has been slaving away on PEAR2 and Pyrus would be a tragedy. Do it right, or not at all. Ignoring all that work, if it proves useful, makes no sense. At the same time, allowing them to create limitations in the requirements would be a serious error.
Regardless, we really need the PEAR Group’s participation because, frankly, their input and support will make the difference between evolving PEAR distribution (with or without the existing PEAR efforts) with us to meet our needs, or sundering the community into two approaches that probably won’t be compatible.
I would hope that we’re capable of improving the entire infrastructure – not just one piece of it. Hopefully, with the assistance of our PEAR colleagues. And hopefully by avoiding segmentation of the overall community. If you’re working on pieces of the puzzle, I appeal to you to band together. Let’s not let fiefdoms derail what could be a massive boost to publishing code (not just libraries) online.
Gathering Requirements For A PEAR Channel Aggregator
In case you missed it the first time!
Mockery 0.6 Released – PHP Mock Object Framework
May 27th
Mockery is a Mock Object framework for PHP, compatible with most unit testing frameworks including PHPUnit. Its purpose is to implement a lightweight grammer for the creation and testing of Mock Objects, Test Stubs, and Test Spies as an alternative to the built-in support offered by PHPUnit, etc.
Mockery is hosted on Github (http://github.com/padraic/mockery) where you can find an extensive README covering its API and uses. The Mockery 0.6 release may be installed from the SurviveTheDeepEnd.com PEAR channel at http://pear.survivethedeepend.com.
Mockery 0.6 features:
- Full Mock Object and Test Stub support
- Lightweight fluent API
- Flexible mocking and stubbing
- Object Interaction Recording
- Natural language syntax and expectation constructs
- Supports generic (untyped) mock objects for rapid prototyping
- Simple partial mocking of real objects
- Both local and global Mock Object call ordering
- Built-in return value queue for repeated method calls
- Support for default expectations
- Support for expectation replacement and stacking
- Fluent API/Law of Demeter mocking
If that sounds complex, it’s not! Mockery can be picked up and used with little study.
Why Mockery?
Mockery’s objective is to simplify Mock Objects in PHP while maintaining significant flexibility and a default level of intuitive behaviour. In Mockery, Mock Objects behave exactly as you write them with liberal interpretations otherwise applied. Mockery was born out of my own need to innovate the use of Mock Objects in PHP and draw away from the original import of aging Mocking approaches from Java. While Java (and almost every other programming language) has been steadily progressing its mock object libraries, and complementing them with new solutions, PHP has a relatively static approach depending on similarly static library components. That result has seen solutions using clunky APIs, poorly described syntax and behaviour, a lack of focus on the practice of using Mock Objects, user confusion, and raised barriers to new programmers trying to learn about Mock Objects. Mockery is one potential solution to these problems. Also, as a dedicated Test-Driven Design user, I really want something that clicks immediately and doesn’t have any gotchas.
Installation
Mockery may be installed from its PEAR channel using:
pear channel-discover pear.survivethedeepend.com
pear install deepend/Mockery
Mockery is written in PHP 5.3 (I know, but all you 5.2 users will get there eventually
). It is released under a New BSD license.
Example
The README offers a good look at some examples, and explains the API in a lot of detail. If you are trying to figure something out, the README undoubtedly has a section for it. Here’s an API example (assuming Mockery namespace used as MK). We’re capturing an interaction where we login into a bookmarking service, check for the existance of a “php” tagged bookmark, add three more bookmarks and then recheck if a “php” tag exists (twice for fun). We’re mocking the service since we don’t actually want to mess with a real account! Following the description closely…
[geshi lang=php]$service = \MK::mock(‘MyService’);
$service->shouldReceive(‘login’)->with(‘user’, ‘pass’)->once()->andReturn(true);
$service->shouldReceive(‘hasBookmarksTagged’)->with(‘php’)->once()->andReturn(false);
$service->shouldReceive(‘addBookmark’)->with(‘/^http:/’, \MK::type(‘string’))->times(3)->andReturn(true);
$service->shouldReceive(‘hasBookmarksTagged’)->with(‘php’)->twice()->andReturn(true);[/geshi]
The example uses some of the basic parts of Mockery to describe some interaction with a mocked web service class (obviously
also stubbing the web service’s responses in terms of booleans). The setup is straightforward, easy to follow, and there’s zero
misinterpretations possible. Our description was likewise simple and uncomplicated. The third line just shows two argument matchers at work, a default regex (intrepreted from any string argument set so long as any eventual string comparison fails and it’s a valid regex) and a Type matcher set to match any valid string.
To put this into some perspective, here’s an equivalent attempt using PHPUnit in a similar order of thought (excerpt from a test).
[geshi lang=php]$service = $this->getMock(‘MyService’);
$service->expects($this->once())->method(‘login’)->with(‘user’, ‘pass’)->will($this->returnValue(true));
$service->expects($this->once())->method(‘hasBookmarksTagged’)->with(‘php’)->will($this->returnValue(false));
$service->expects($this->exactly(3))->method(‘addBookmark’)
->with($this->matchesRegularExpression(‘/^http:/’), $this->isType(‘string’))
->will($this->returnValue(true));
$service->expects($this->exactly(2))->method(‘hasBookmarksTagged’)->with(‘php’)->will($this->returnValue(true));[/geshi]
Besides the differences in API, there are others. If MyService is just intended as a fake unimplemented object (the class doesn’t exist), Mockery carries on and just uses a generic Mock instance without error. PHPUnit will throw an exception, however, stating that login() is not a valid method. If we assume the class is real, but missing some methods, the same thing happens and PHPUnit complains about missing methods. Eventually, you’ll get the idea to implement the dependent class… If we add all the relevant methods (say, we mock an interface with all methods declared), PHPUnit STILL fails. This time complaining that hasBookmarksTagged() was expected only once. This occurs because PHPUnit has no capacity for stacking later expectations, and so, it ignores the second (and any later) ones. We can fix that by merging both into a single expectation using:
[geshi lang=php]$service = $this->getMock(‘MyService’);
$service->expects($this->once())->method(‘login’)->with(‘user’, ‘pass’)->will($this->returnValue(true));
$service->expects($this->exactly(3))->method(‘hasBookmarksTagged’)->with(‘php’)
->will($this->onConsecutiveCalls(false, true, true));
$service->expects($this->exactly(3))->method(‘addBookmark’)
->with($this->matchesRegularExpression(‘/^http:/’), $this->isType(‘string’))
->will($this->returnValue(true));[/geshi]
Using OnConsecutiveCalls() to create a return value queue, and merging the two stacked expectations, allows the PHPUnit variant to pass. Unlike Mockery, if there were ten hasBookmarksTagged() calls, you would need to add all ten return values (Mockery let’s you set the last return value to act infinitely). The merging simply demonstrates that complex class interactions across classes will fall victim to the need to constantly merge expectations until they are unreadable and explain little.
While your mileage may vary, Mockery just doesn’t need reworking, deep thought or extra work. Just state what you want your Mock Object to do in plain unconfused English according to your natural thought order! If nothing else, it helps make the expected interaction obvious which makes your tests more readable and explicit.
Feedback
Any issues can be reported via our Github hosted issue tracker. If you wish to discuss Mockery in more detail, you’re welcome to join the mailing list at http://groups.google.com/group/phpmockery.


Recent Comments