<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5165080541063022388</id><updated>2012-02-16T09:00:46.805-05:00</updated><category term='PHP'/><category term='SCM'/><category term='Development'/><category term='jQuery'/><category term='Subversion'/><category term='REST'/><category term='Linux'/><category term='Phing'/><category term='PHPUnit'/><category term='JavaScript'/><category term='RPC'/><category term='API'/><category term='Programming'/><title type='text'>Yet Another PHPer's Blog</title><subtitle type='html'>Web development with PHP and the LAMP stack in general.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://rchouinard.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://rchouinard.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Ryan Chouinard</name><uri>http://www.blogger.com/profile/05769434834874214452</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_qsQJlxDEDUo/TMhhR9SBQHI/AAAAAAAAACk/ruYkshlqQ0U/s1600-R/3639f5b5ed310987f654d1bbadc75089%3Fs%3D150'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>7</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5165080541063022388.post-7828965936675495853</id><published>2010-10-27T14:14:00.000-04:00</published><updated>2010-10-27T14:14:54.917-04:00</updated><title type='text'>Change of Scenery</title><content type='html'>A few weeks ago, I turned in my notice at BizJournals and informed my manager that I would be accepting a position at another company. It was a difficult decision to make, made even more difficult by the timing of project due dates and the need for resources to be on-hand. In the end, though, it was an opportunity I simply couldn't pass on.&amp;nbsp;I met a lot of great people at BizJournals, and I hope to maintain many of the connections made there. It was a good place to work, and the development staff there was top-notch.&lt;br /&gt;&lt;br /&gt;That said, I've moved on to working at Oracle Corporation with the MySQL.com Web team. I'm now working from my home office, so the lack of direct human contact through the day will take some getting used to. I'm sure I'll be able to pull a blog post or two out of that...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5165080541063022388-7828965936675495853?l=rchouinard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rchouinard.blogspot.com/feeds/7828965936675495853/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://rchouinard.blogspot.com/2010/10/change-of-scenery.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/7828965936675495853'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/7828965936675495853'/><link rel='alternate' type='text/html' href='http://rchouinard.blogspot.com/2010/10/change-of-scenery.html' title='Change of Scenery'/><author><name>Ryan Chouinard</name><uri>http://www.blogger.com/profile/05769434834874214452</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_qsQJlxDEDUo/TMhhR9SBQHI/AAAAAAAAACk/ruYkshlqQ0U/s1600-R/3639f5b5ed310987f654d1bbadc75089%3Fs%3D150'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5165080541063022388.post-235753224853776864</id><published>2010-08-17T07:18:00.001-04:00</published><updated>2010-09-28T13:18:04.208-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='RPC'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Development'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='API'/><title type='text'>Battle of the APIs: REST vs. RPC</title><content type='html'>&lt;p&gt;It seems that every time I talk to another developer about building an API, there tends to be some confusion on terminology and implementation details. While many will say that they are implementing a RESTful Web service, when they start detailing the implementation it becomes clear they really mean RPC. I’m not sure where the confusion stems from, as both standards are pretty well documented. Still, I’m amazed at how few REST implementations get it right.&lt;/p&gt;&lt;p&gt;Both REST and RPC have their place, and may even coexist within a single project. They simply provide different ways of accessing things, and each has its own strengths and weaknesses which make it more or less suitable for a given purpose.&lt;/p&gt;&lt;h3&gt;What is RPC?&lt;/h3&gt;&lt;p&gt;RPC means &lt;a href="http://wikipedia.org/wiki/Remote_procedure_call"&gt;Remote Procedure Call&lt;/a&gt;, and is used to call a function or method exposed on a remote server. When discussing RPC, most developers tend to be referring to either &lt;a href="http://www.xmlrpc.com/spec"&gt;XML-RPC&lt;/a&gt; or &lt;a href="http://www.w3.org/TR/soap/"&gt;SOAP&lt;/a&gt;. Both are simply protocols, and can be used as completely valid RPC implementations. I’ve also seen many developers happily role their own implementations as well.&lt;/p&gt;&lt;p&gt;Most RPC implementations use XML or JSON to pass messages or payloads between the client and the server. The request message would contain the name of the remote method to call, along with the method arguments. The server would then respond with a message containing the return value of method and any other messages.&lt;/p&gt;&lt;p&gt;If an API uses SOAP, XML-RPC, or uses URLs like &lt;em&gt;http://example.com/api/method?arg1=foo&lt;/em&gt;, it is (most likely) an RPC interface. There are many ways to implement an RPC API, but in the last case of a home-grown API, requests might look like those below.&lt;/p&gt;&lt;p&gt;To request a list of user accounts in XML format from an RPC API, the request URI might be similar to this:&lt;/p&gt;&lt;p&gt;http://example.com/api/getUserList?format=xml&lt;/p&gt;&lt;p&gt;The API should send back an XML payload of user accounts, or it may respond with some sort of error message instead. In either case, a 200 OK status code will probably be returned.&lt;/p&gt;&lt;p&gt;The same API may allow the creation of a user account, either using GET or POST (broken apart for readability):&lt;/p&gt;&lt;pre class="brush: plain; gutter: false"&gt;http://example.com/api/createUser \&lt;br /&gt;    ?format=xml \&lt;br /&gt;    &amp;amp;firstName=Example \&lt;br /&gt;    &amp;amp;lastName=User \&lt;br /&gt;    &amp;amp;displayName=Example+User \&lt;br /&gt;    &amp;amp;passwordHash=5f4dcc3b5aa765d61d8327deb882cf99h238&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;In this case, the API would probably respond in much the same way, with an XML payload containing either a success flag or an error string.&lt;/p&gt;&lt;h3&gt;What is REST?&lt;/h3&gt;&lt;p&gt;The term REST means &lt;a href="http://wikipedia.org/wiki/Representational_State_Transfer"&gt;Representational State Transfer&lt;/a&gt;, and is a pretty popular buzzword on the Internet. A RESTful API presents application objects as Web resources, and uses standard HTTP methods and headers to pass information back and forth.&lt;/p&gt;&lt;p&gt;Communication with a REST endpoint is mostly done using the &lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3"&gt;GET&lt;/a&gt;, &lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5"&gt;POST&lt;/a&gt;, &lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6"&gt;PUT&lt;/a&gt;, and &lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7"&gt;DELETE&lt;/a&gt; HTTP methods. These roughly translate to read, create, edit, and delete actions, respectively. Parameters are passed using HTTP request headers or query strings, and responses can be returned in a number of formats (XML and JSON being very common). Various HTTP error codes are used to indicate the state of the resource following the request.&lt;/p&gt;&lt;p&gt;The request body of a RESTful call to return a list of of user accounts in XML format may look like this:&lt;/p&gt;&lt;pre class="brush: plain; gutter: false"&gt;GET /api/users HTTP/1.0&lt;br /&gt;Accept: text/xml&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The response may be a 200 OK status with an XML payload, or it might be a 401 Unauthorized if the API requires authentication but the credentials were not included with the request.&lt;/p&gt;&lt;p&gt;Creating a new user resource is also very easy, as shown below:&lt;/p&gt;&lt;pre class="brush: plain; gutter: false"&gt;POST /api/users HTTP/1.0&lt;br /&gt;Accept: text/xml&lt;br /&gt;Content-Type: text/xml&lt;br /&gt;Content-Length: 227&lt;br /&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;user&amp;gt;&lt;br /&gt;    &amp;lt;firstName value="Example" /&amp;gt;&lt;br /&gt;    &amp;lt;lastName value="User" /&amp;gt;&lt;br /&gt;    &amp;lt;displayName value="Example User" /&amp;gt;&lt;br /&gt;    &amp;lt;passwordHash value="5f4dcc3b5aa765d61d8327deb882cf99h238" /&amp;gt;&lt;br /&gt;&amp;lt;/user&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;If the new user resource was created successfully, the server may respond with a 201 Created status, and a Location header pointing to the newly created resource.&lt;/p&gt;&lt;pre class="brush: plain; gutter: false"&gt;201 Created&lt;br /&gt;Location: http://example.org/api/users/31337&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;A request to the URI provided in the Location header should return a payload containing the new user record.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Hopefully at this point the basic differences between the two API types are a little clearer. In my opinion, one interface type is not inherently superior to the other, and both can be used very effectively given proper implementations (and documentation!).&lt;/p&gt;&lt;p&gt;While I centered my examples here around a fictional user API which translates well into both interface types, I think it's important to also point out that there are some functions an API might have to support that don't work nearly as well. One such example might be an API which is used to perform some sort of action, such as processing a payment. This type of action would work very well in an RPC interface:&lt;/p&gt;&lt;pre class="brush: plain; gutter: false"&gt;http://example.com/api/processPayment \&lt;br /&gt;    ?format=xml \&lt;br /&gt;    &amp;amp;amount=313.37 \&lt;br /&gt;    &amp;amp;cardNo=4111111111111111&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;If you know how to do this in a RESTful way, please share! ;-)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5165080541063022388-235753224853776864?l=rchouinard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rchouinard.blogspot.com/feeds/235753224853776864/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://rchouinard.blogspot.com/2010/08/battle-of-apis-rest-vs-rpc.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/235753224853776864'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/235753224853776864'/><link rel='alternate' type='text/html' href='http://rchouinard.blogspot.com/2010/08/battle-of-apis-rest-vs-rpc.html' title='Battle of the APIs: REST vs. RPC'/><author><name>Ryan Chouinard</name><uri>http://www.blogger.com/profile/05769434834874214452</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_qsQJlxDEDUo/TMhhR9SBQHI/AAAAAAAAACk/ruYkshlqQ0U/s1600-R/3639f5b5ed310987f654d1bbadc75089%3Fs%3D150'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5165080541063022388.post-5807832885388977364</id><published>2010-07-18T23:58:00.001-04:00</published><updated>2010-07-30T08:27:08.893-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Subversion'/><category scheme='http://www.blogger.com/atom/ns#' term='SCM'/><title type='text'>Subversion Branches &amp; You</title><content type='html'>The topic of managing a subversion repository comes up a lot around the office. The development team where I work is still getting used to the idea of branching, and with that come the ideas of merging, rebasing, and reintegrating. I figured I'd go over some of these concepts for anyone else who may be searching.&lt;br /&gt;&lt;br /&gt;The typical layout of a subversion repository is to have three directories (sometimes called resources) under the root: trunk, branches, and tags. These directories serve to hold the mainline development, experimental or incomplete development, and release code, respectively. While most everybody who has used subversion is familiar with trunk, some are not clear on the purpose of the other two.&lt;br /&gt;&lt;br /&gt;To fully explain branches and tags, I'd like to start first with trunk. When it comes to source control, I operate under a simple philosophy: &lt;i&gt;Trunk is always stable.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;This means that under source control, the trunk resource should always be in a working state. A user or developer should be able to check out the contents of trunk and work with it right away. However, stable in this context does not mean that APIs or functionality can't change from revision to revision; it simply means that the code base should work without breaking. This is an important distinction.&lt;br /&gt;&lt;br /&gt;Of course, leaving trunk in this state while working on large change sets and experimental updates conflicts with another established philosophy: &lt;i&gt;Commit early, commit often&lt;/i&gt;. So, where do these changes go? Branches, of course!&lt;br /&gt;&lt;br /&gt;Branches are basically temporary copies of trunk where a developer can work and commit and break as they like. Once the changes to a branch are stable, the branch can be reintegrated to trunk, and the branch deleted. This keeps trunk clean and stable, while allowing the developer to retain the benefits of revision control. A project can have as many unique branches as the developer or team likes. Branch names don't really have any rules, but most teams have some sort of preferred convention.&lt;br /&gt;&lt;br /&gt;When a project is ready for a release, a special type of branch is usually created, called a tag. A tag can be a straight copy of trunk, serving as a marker in the project's revision timeline, or it can be a modified version of the code base derived from a build system. Tags are usually named for the release version they represent. Since tags represent a fixed release, they are not supposed to be modified once they are created. While subversion itself has no concept of tags (it treats them like any other branch), the established convention is that they are permanent and users do not expect them to change. Most third-party repository management tools will warn you if you are about to modify a tag.&lt;br /&gt;&lt;br /&gt;A typical active subversion repository may look like this:&lt;br /&gt;&lt;pre class="brush: plain; gutter: false"&gt;/&lt;br /&gt;    branches/&lt;br /&gt;        rchouinards_branch/&lt;br /&gt;        feature_195/&lt;br /&gt;        foo/&lt;br /&gt;    tags/&lt;br /&gt;        1.0/&lt;br /&gt;        1.1/&lt;br /&gt;        1.2/&lt;br /&gt;    trunk/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Let's say a developer - we'll call him Bob - is working on the project shown above. Bob is assigned Bug #209, and after reviewing it decides that the changes required to fix the bug warrant creating a branch. Bob creates his new branch from trunk, naming it bug_209, and switching his working copy to it. As Bob is working on his ticket, other developers are busy committing to their branches, and some are even committing to trunk. After a while, Bob decides he needs to check his code against the current trunk. To do so, Bob would merge the changes from trunk to his working copy, and if everything looks good, commit those changes back in to his branch. Bob has rebased his branch from trunk.&lt;br /&gt;&lt;br /&gt;A little while later, Bob decides that his work is done, and the bug is fixed as confirmed by his regression tests. Bob switches his working copy to trunk, and merges the changes from his branch to his working copy. One last successful run of the test suite later, Bob commits his working copy into trunk, deletes his branch, and marks Bug #209 as ready to deploy. Bob has just reintegrated his branch into trunk.&lt;br /&gt;&lt;br /&gt;Bob also happens to be the release master for his team, which makes him responsible for creating tags. Since Bug #209 was prioritized critical, he needs to push the fix into production as soon as possible. Bob uses the team's build system to make sure trunk is stable and ready to deploy, and then creates a release tag from the build output, which he names 1.2.1. Bob then uses the team's deployment tools to verify the tag and push the code out into production. Hooray!&lt;br /&gt;&lt;br /&gt;In Bob's case, the fact that his team uses branches and tags efficiently allowed him to easily deploy the application into a production environment. Hopefully, you have a little better understanding as to what branches are, and how a good branching strategy comes into play during the testing and deployment phase of development.&lt;br /&gt;&lt;br /&gt;I'm still working on a post that will describe a bit more of the magic that happens when Bob runs his build and deployment tools. :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5165080541063022388-5807832885388977364?l=rchouinard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rchouinard.blogspot.com/feeds/5807832885388977364/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://rchouinard.blogspot.com/2010/07/subversion-branches-you.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/5807832885388977364'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/5807832885388977364'/><link rel='alternate' type='text/html' href='http://rchouinard.blogspot.com/2010/07/subversion-branches-you.html' title='Subversion Branches &amp;amp; You'/><author><name>Ryan Chouinard</name><uri>http://www.blogger.com/profile/05769434834874214452</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_qsQJlxDEDUo/TMhhR9SBQHI/AAAAAAAAACk/ruYkshlqQ0U/s1600-R/3639f5b5ed310987f654d1bbadc75089%3Fs%3D150'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5165080541063022388.post-870953947200020443</id><published>2010-07-10T23:01:00.000-04:00</published><updated>2010-07-30T08:25:35.727-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PHP'/><category scheme='http://www.blogger.com/atom/ns#' term='PHPUnit'/><title type='text'>PHP Application Lifecycle: Unit Testing</title><content type='html'>The concept of unit testing is nothing new, but unfortunately it seems to still be rare among PHP developers. I believe it's not because developers don't think testing is a good idea, but instead that they think testing is hard, or makes development times longer. I actually used to be one of those developers.&lt;br /&gt;&lt;br /&gt;I think that most developers would agree that testing is a good thing, and we should all be doing it. Some&amp;nbsp;developers like to test their work simply by calling it in another bit of code&amp;nbsp;(or reloading the page in browser)&amp;nbsp;and observing the results. The problem with this approach is that it is inflexible. While it may work fine on smaller bits of code, larger classes and objects that interact with other objects may not be as easy to test. Most of the time, these quick one-off tests assume perfect conditions, which isn't always the case.&lt;br /&gt;&lt;br /&gt;By using a testing framework, a developer can quickly build test cases for a bit of code, and run those tests as they continue to make changes to the code in order to make sure nothing gets broken. In fact, this type of testing is called regression testing, and is only one type of test a developer can create. The most common types of tests are:&lt;br /&gt;&lt;dl&gt;&lt;dt&gt;Smoke Test&lt;/dt&gt;&lt;dd&gt;The first, simple test against a new bit of code. These are used to check the code for expected behavior with valid input.&lt;/dd&gt;&lt;dt&gt;Regression Test&lt;/dt&gt;&lt;dd&gt;A set of tests written to verify and fix specific bugs or usage scenarios. For example, if a method expects a string, and causes a bug if given an integer, a regression test should be written - which fails - to verify the presence of the problem. The code should then be fixed to make the test pass. Regression tests are then used to make sure a bug is not re-introduced in future code revisions.&lt;/dd&gt;&lt;dt&gt;Integration Test&lt;/dt&gt;&lt;dd&gt;More advanced testing which checks the interaction between two or more portions of code. An integration test might be written to make sure a library is properly writing data to the database.&lt;/dd&gt;&lt;dt&gt;Behavior Test&lt;/dt&gt;&lt;dd&gt;Another more advanced testing methodology in which the test isn't concerned so much with the result, but how the code works internally. If a bit of code is expected to log data to a file, a behavior test will call that bit of code, and watch for the proper call to the log method.&lt;/dd&gt;&lt;/dl&gt;&lt;br /&gt;PHP has two main unit testing tools: &lt;a href="http://www.lastcraft.com/simple_test.php"&gt;SimpleTest&lt;/a&gt; by Marcus Baker, and &lt;a href="http://www.phpunit.de/"&gt;PHPUnit&lt;/a&gt; by Sebastian Bergmann. SimpleTest's Website hasn't been updated in a while, and I'm not sure of the state of the tool. PHPUnit is the most widely accepted, and is compatible with the &lt;a href="http://en.wikipedia.org/wiki/XUnit"&gt;xUnit&lt;/a&gt; family of testing tools. I use and will focus on PHPUnit for this discussion. PHPUnit supports all the test types outlined above, but for brevity I'm only going to review a simple smoke test.&lt;br /&gt;&lt;br /&gt;Let's assume a simple class which provides a few math-based methods. It may look like this:&lt;br /&gt;&lt;pre class="brush: php; gutter: true"&gt;&amp;lt;?php&lt;br /&gt;class Calculator&lt;br /&gt;{&lt;br /&gt;&lt;br /&gt;    public function add($first, $second)&lt;br /&gt;    {&lt;br /&gt;        return (int) $first + (int) $second;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public function subtract($first, $second)&lt;br /&gt;    {&lt;br /&gt;        return (int) $first - (int) $second;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public function multiply($first, $second)&lt;br /&gt;    {&lt;br /&gt;        return (int) $first * (int) $second;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public function divide($first, $second)&lt;br /&gt;    {&lt;br /&gt;        return (int) $first / (int) $second;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A quick one-off test for this may look like this:&lt;br /&gt;&lt;pre class="brush: php; gutter: true"&gt;&amp;lt;?php&lt;br /&gt;$calc = new Calculator;&lt;br /&gt;&lt;br /&gt;echo "add(): ";&lt;br /&gt;// Should output "4"&lt;br /&gt;echo $calc-&amp;gt;add(2, 2);&lt;br /&gt;echo PHP_EOL;&lt;br /&gt;&lt;br /&gt;echo "subtract(): ";&lt;br /&gt;// Should output "2"&lt;br /&gt;echo $calc-&amp;gt;subtract(4, 2);&lt;br /&gt;echo PHP_EOL;&lt;br /&gt;&lt;br /&gt;echo "multiply(): ";&lt;br /&gt;// Should output "10"&lt;br /&gt;echo $calc-&amp;gt;multiply(5, 2);&lt;br /&gt;echo PHP_EOL;&lt;br /&gt;&lt;br /&gt;echo "divide(): ";&lt;br /&gt;// Should output "5"&lt;br /&gt;echo $calc-&amp;gt;divide(10, 2);&lt;br /&gt;echo PHP_EOL;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Output would look like this:&lt;br /&gt;&lt;pre class="brush: plain; gutter: false;"&gt;[rchouinard@beta ~]$ php testCalc.php&lt;br /&gt;add(): 4&lt;br /&gt;subtract(): 2&lt;br /&gt;multiply(): 10&lt;br /&gt;divide(): 5&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This approach seems simple, but some problems become apparent as development on the Calculator class continues. For starters, the test script doesn't really indicate what the test is checking for. The person invoking the script must know what output is expected in order to tell if the test passed or failed. This test script can be rewritten as a PHPUnit test case very easily:&lt;br /&gt;&lt;pre class="brush: php; gutter: true"&gt;&amp;lt;?php&lt;br /&gt;require_once 'Calculator.php';&lt;br /&gt;require_once 'PHPUnit\Framework\TestCase.php';&lt;br /&gt;&lt;br /&gt;class CalculatorTest extends PHPUnit_Framework_TestCase&lt;br /&gt;{&lt;br /&gt;&lt;br /&gt;    private $calc;&lt;br /&gt;&lt;br /&gt;    protected function setUp ()&lt;br /&gt;    {&lt;br /&gt;        parent::setUp();&lt;br /&gt;        $this-&amp;gt;calc = new Calculator;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    protected function tearDown ()&lt;br /&gt;    {&lt;br /&gt;        $this-&amp;gt;calc = null;&lt;br /&gt;        parent::tearDown();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public function testAdd ()&lt;br /&gt;    {&lt;br /&gt;        $this-&amp;gt;assertEquals(4, $this-&amp;gt;calc-&amp;gt;add(2, 2));&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public function testSubtract ()&lt;br /&gt;    {&lt;br /&gt;        $this-&amp;gt;assertEquals(2, $this-&amp;gt;calc-&amp;gt;subtract(4, 2));&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public function testMultiply ()&lt;br /&gt;    {&lt;br /&gt;        $this-&amp;gt;assertEquals(10, $this-&amp;gt;calc-&amp;gt;multiply(5, 2));&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public function testDivide ()&lt;br /&gt;    {&lt;br /&gt;        $this-&amp;gt;assertEquals(5, $this-&amp;gt;calc-&amp;gt;divide(10, 2));&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Running PHPUnit against this file gives us easy to read and understand output:&lt;br /&gt;&lt;pre class="brush: plain; gutter: false;"&gt;[rchouinard@beta ~]$ phpunit CalculatorTest.php&lt;br /&gt;PHPUnit 3.5.0beta1 by Sebastian Bergmann.&lt;br /&gt;&lt;br /&gt;....&lt;br /&gt;&lt;br /&gt;Time: 0 second, Memory: 1.00Mb&lt;br /&gt;&lt;br /&gt;OK (4 tests, 4 assertions)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If one of the assertions had failed, we would get output like this:&lt;br /&gt;&lt;pre class="brush: plain; gutter: false;"&gt;[rchouinard@beta ~]$ phpunit CalculatorTest.php&lt;br /&gt;PHPUnit 3.5.0beta1 by Sebastian Bergmann.&lt;br /&gt;&lt;br /&gt;.F..&lt;br /&gt;&lt;br /&gt;Time: 0 seconds, Memory: 1.00Mb&lt;br /&gt;&lt;br /&gt;There was 1 failure:&lt;br /&gt;&lt;br /&gt;1) Calculator::testSubtract&lt;br /&gt;Failed asserting that &lt;integer:5&gt; matches expected &lt;integer:4&gt;.&lt;br /&gt;&lt;br /&gt;/home/rchouinard/working/CalculatorTest.php:29&lt;br /&gt;&lt;br /&gt;FAILURES!&lt;br /&gt;Tests: 4, Assertions: 4, Failures: 1.&lt;br /&gt;&lt;/integer:4&gt;&lt;/integer:5&gt;&lt;/pre&gt;&lt;br /&gt;Hopefully some of the benefits of a testing framework are apparent now. Our test code doesn't have to deal with output, and we have immediate pass/fail feedback without having to know what values the test is expecting. PHPUnit even tells us exactly what went wrong and caused the test to fail.&lt;br /&gt;&lt;br /&gt;This has been a very simple intro to PHPUnit, and doesn't even begin to scratch the surface of what PHPUnit is capable of. I would encourage you to take a look at the &lt;a href="http://www.phpunit.de/manual/3.4/en/index.html"&gt;PHPUnit documentation&lt;/a&gt; to learn more.&amp;nbsp;For a working example of a PHPUnit setup, take a look at my &lt;a href="http://code.google.com/p/rych-components/source/browse/#svn/trunk/tests"&gt;PHP component library&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In coming posts, I'll discuss integrating PHPUnit into other tools for some truly powerful code analysis.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5165080541063022388-870953947200020443?l=rchouinard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rchouinard.blogspot.com/feeds/870953947200020443/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://rchouinard.blogspot.com/2010/07/php-application-lifecycle-unit-testing.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/870953947200020443'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/870953947200020443'/><link rel='alternate' type='text/html' href='http://rchouinard.blogspot.com/2010/07/php-application-lifecycle-unit-testing.html' title='PHP Application Lifecycle: Unit Testing'/><author><name>Ryan Chouinard</name><uri>http://www.blogger.com/profile/05769434834874214452</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_qsQJlxDEDUo/TMhhR9SBQHI/AAAAAAAAACk/ruYkshlqQ0U/s1600-R/3639f5b5ed310987f654d1bbadc75089%3Fs%3D150'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5165080541063022388.post-5908602062032535360</id><published>2010-07-07T22:57:00.001-04:00</published><updated>2010-07-30T08:24:29.236-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PHP'/><category scheme='http://www.blogger.com/atom/ns#' term='Phing'/><title type='text'>PHP Application Lifecycle: Build vs. Deploy</title><content type='html'>Over the past few days, I've been playing with &lt;a href="http://phing.info/"&gt;Phing&lt;/a&gt;, a build tool for PHP, similar to Ant for Java. I initially intended to use Phing to deploy a Web application I'm developing, but I've come to realize its power as a build tool while working on a set of PHP libraries as well. Through my time with the tool, I've realized the important distinction between a build tool, such as Phing, and a deployment tool.&lt;br /&gt;&lt;br /&gt;Many articles and tutorials around the Internet tend to focus on using Phing for three things:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Kicking off automated tests with &lt;a href="http://www.phpunit.de/"&gt;PHPUnit&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Building API documentation with &lt;a href="http://www.phpdoc.org/"&gt;PHPDocumentor&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Deploying code to a Web server&lt;/li&gt;&lt;/ul&gt;While Phing is definitely suited for these tasks, and indeed comes with many built-in tasks for these exact purposes, it can be used — and is intended to be used — to do so much more.&lt;br /&gt;&lt;br /&gt;In the PHP world, where code is not compiled, the primary purpose of a build tool is to prepare a code base for distribution. This usually means changing configuration files, cleaning out development-only artifacts, and packaging the code base in a neat little tarball, PEAR package, or other archive. While Phing and other build tools often have built-in support for simple deployments via rsync, scp, and other file transport mechanisms, they typically don't support truly robust deployment features.&lt;br /&gt;&lt;br /&gt;In many environments, especially those with only a single Web server, the simple mechanisms provided by the build tools may be just fine. However, in more complex environments, these methods quickly break down, and deployment should be handled by a dedicated utility. That's not to say that Phing doesn't belong in these environments; to the contrary, the real power of Phing can shine through the most here. By taking the code base from its development environment, transforming it, testing it, and packing it, a build tool can pass off the prepared code to the deployment system with a higher level of code confidence than before.&lt;br /&gt;&lt;br /&gt;Some time in the future, I'll discuss a little about how I'm managing my code with Phing, my deployment solutions, and introduce my other new friend, the &lt;a href="http://hudson-ci.org/"&gt;Hudson&lt;/a&gt; continuous integration server.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5165080541063022388-5908602062032535360?l=rchouinard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rchouinard.blogspot.com/feeds/5908602062032535360/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://rchouinard.blogspot.com/2010/07/php-application-lifecycle-build-vs.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/5908602062032535360'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/5908602062032535360'/><link rel='alternate' type='text/html' href='http://rchouinard.blogspot.com/2010/07/php-application-lifecycle-build-vs.html' title='PHP Application Lifecycle: Build vs. Deploy'/><author><name>Ryan Chouinard</name><uri>http://www.blogger.com/profile/05769434834874214452</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_qsQJlxDEDUo/TMhhR9SBQHI/AAAAAAAAACk/ruYkshlqQ0U/s1600-R/3639f5b5ed310987f654d1bbadc75089%3Fs%3D150'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5165080541063022388.post-762748597582020335</id><published>2010-06-01T19:35:00.014-04:00</published><updated>2010-07-30T08:19:34.305-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaScript'/><category scheme='http://www.blogger.com/atom/ns#' term='jQuery'/><title type='text'>jQuery Textbox Hint</title><content type='html'>It's been a while, but I'm back to share a quick jQuery plugin I created for a project at work. It's a simple textbox hint plugin I built after searching for one that fit my needs. While there are quite a few very nice solutions, most that I found either over-complicated things or simply lacked the one very basic thing I needed: the ability to apply a given CSS class to the textbox I need hinted.&lt;br /&gt;&lt;br /&gt;Here's the CSS and HTML:&lt;br /&gt;&lt;pre class="brush: html; gutter: true"&gt;&lt;style&gt;&lt;br /&gt;.hintText {&lt;br /&gt;color: #ccc;&lt;br /&gt;}&lt;br /&gt;&lt;/style&gt;&lt;br /&gt;&lt;br /&gt;&lt;form id="search-form" action="search" method="get"&gt;&lt;br /&gt;&lt;input type="text" class="hint" title="Enter keywords here!"&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And the JavaScript:&lt;br /&gt;&lt;pre class="brush: javascript; gutter: true"&gt;$(document).ready(function() {&lt;br /&gt;$("form#search-form input.hint").hint();&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Demo, docs, and tests are available in the archive.&lt;br /&gt;&lt;br /&gt;Download: &lt;a href="http://ryanchouinard.com/downloads/jquery_hint.tar.gz"&gt;jquery_hint.tar.gz&lt;/a&gt;&lt;br /&gt;MD5 Sum: a9cba941898e6e3c8427fee444333b71&lt;br /&gt;SHA1 Sum: 03ba6da74b2e0db37572e1cd1b744da0ea8da72b&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5165080541063022388-762748597582020335?l=rchouinard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rchouinard.blogspot.com/feeds/762748597582020335/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://rchouinard.blogspot.com/2010/06/jquery-textbox-hint.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/762748597582020335'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/762748597582020335'/><link rel='alternate' type='text/html' href='http://rchouinard.blogspot.com/2010/06/jquery-textbox-hint.html' title='jQuery Textbox Hint'/><author><name>Ryan Chouinard</name><uri>http://www.blogger.com/profile/05769434834874214452</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_qsQJlxDEDUo/TMhhR9SBQHI/AAAAAAAAACk/ruYkshlqQ0U/s1600-R/3639f5b5ed310987f654d1bbadc75089%3Fs%3D150'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5165080541063022388.post-8945582553481770263</id><published>2009-07-22T09:55:00.013-04:00</published><updated>2010-07-30T08:20:52.403-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Linux'/><title type='text'>iStat Linux Server 0.5.3 RPM</title><content type='html'>&lt;blockquote&gt;&lt;strong&gt;Edit (July 24, 2009):&lt;/strong&gt; Added 64-bit package. It can be found just under the original 32-bit download link.&lt;/blockquote&gt;&lt;br /&gt;The other day, I stumbled across the &lt;a href="http://bjango.com/apps/istat/"&gt;iStat iPhone App&lt;/a&gt;, which allows an administrator to view remote server statistics in a very nice graphical display on your iPhone. While the application developer, &lt;a href="http://bjango.com/apps/"&gt;Bjango&lt;/a&gt;, only officially supplies iStat Server for Mac OS, they promote a third-party code base for &lt;a href="http://code.google.com/p/istat/"&gt;iStat Server for Linux &amp;amp; Solaris&lt;/a&gt;. The third-party code base is still fairly rough, but it works and seems stable in my testing.&lt;br /&gt;&lt;br /&gt;The main drawback to the Linux server is that, at the time of this writing, it is currently distributed as a tarball. Since I maintain several CentOS servers, I don't want to have to install development tools and compile the code on them all. So, I fired up my development CentOS VM and got to work. The result is an RPM that installs the iStat server, and provides a handy init script. I made a couple changes to the default configuration file to use the user "nobody" instead of creating an "istat" user for running a single daemon.&lt;br /&gt;&lt;br /&gt;Download: &lt;a href="http://ryanchouinard.com/rpms/istat-server-0.5.3-1.el5.rych.i386.rpm"&gt;istat-server-0.5.3-1.el5.rych.i386.rpm&lt;/a&gt;&lt;br /&gt;MD5 Sum: 239a4d972bcc0d88dae2b93fdb998a07&lt;br /&gt;SHA1 Sum: 7310322eb8aea479a2ce9a2ee59acca8b63a6083&lt;br /&gt;&lt;br /&gt;Download: &lt;a href="http://ryanchouinard.com/rpms/istat-server-0.5.3-1.el5.rych.x86_64.rpm"&gt;istat-server-0.5.3-1.el5.rych.x86_64.rpm&lt;/a&gt;&lt;br /&gt;MD5 Sum: fc94b53bedb4afa3d9d6dcad08268628&lt;br /&gt;SHA1 Sum: d38b5cdef670b5b6555df2558478985ae78e95b5&lt;br /&gt;&lt;br /&gt;Installation is easy:&lt;br /&gt;&lt;pre class="brush: bash; gutter: false"&gt;&lt;br /&gt;$ sudo rpm -hiv http://ryanchouinard.com/rpms/istat-server-0.5.3-1.el5.rych.i386.rpm&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Once installed, edit the config file /etc/istat.conf with your editor of choice. Make sure to change the lines for server_code, monitor_net, and monitor_disk to suit your needs.&lt;br /&gt;&lt;pre class="brush: plain; highlight: [7,13,14]"&gt;&lt;br /&gt;#&lt;br /&gt;# /etc/istat.conf: Configuration for iStat server&lt;br /&gt;#&lt;br /&gt;&lt;br /&gt;# network_addr           127.0.0.1&lt;br /&gt;# network_port           5109&lt;br /&gt;server_code              &amp;lt;Must be a 5 digit code!&amp;gt;&lt;br /&gt;server_user              nobody&lt;br /&gt;# server_socket          /tmp/istat.sock&lt;br /&gt;server_pid               /tmp/istat.pid&lt;br /&gt;# cache_dir              /var/cache/istat&lt;br /&gt;&lt;br /&gt;monitor_net              eth0&lt;br /&gt;monitor_disk             ( /dev/sda1, /dev/sdb1 )&lt;br /&gt;&lt;br /&gt;# End of file&lt;br /&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;span style="font-weight:bold;"&gt;A note about the pid file:&lt;/span&gt; You may notice that the config file uses /tmp/istat.pid, instead of the standard /var/run/istat.pid. This is because the istatd processes seems to change users *before* creating the process files instead of after. This prevents the new user from writing to the root-owned /var/run/ directory. I don't know if this is a bug or design, but hopefully we can put this in the proper place in the future.&lt;/blockquote&gt;&lt;br /&gt;Once your configuration is done, you can start the service and test connectivity from your iPhone.&lt;br /&gt;&lt;pre class="brush: bash; gutter: false"&gt;&lt;br /&gt;$ sudo /sbin/service istatd start&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you want the service to start automatically on every boot, you can enable it with chkconfig.&lt;br /&gt;&lt;pre class="brush: bash; gutter: false"&gt;&lt;br /&gt;$ sudo /sbin/chkconfig istatd on&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And you're done!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5165080541063022388-8945582553481770263?l=rchouinard.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://rchouinard.blogspot.com/feeds/8945582553481770263/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://rchouinard.blogspot.com/2009/07/istat-linux-server-053-rpm.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/8945582553481770263'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5165080541063022388/posts/default/8945582553481770263'/><link rel='alternate' type='text/html' href='http://rchouinard.blogspot.com/2009/07/istat-linux-server-053-rpm.html' title='iStat Linux Server 0.5.3 RPM'/><author><name>Ryan Chouinard</name><uri>http://www.blogger.com/profile/05769434834874214452</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_qsQJlxDEDUo/TMhhR9SBQHI/AAAAAAAAACk/ruYkshlqQ0U/s1600-R/3639f5b5ed310987f654d1bbadc75089%3Fs%3D150'/></author><thr:total>3</thr:total></entry></feed>
