In my Beyond Frameworks talk, I explained how a component-based architecture can help answer some of the important (i.e. expensive!) questions you might face when creating long-lived apps that rely on a PHP framework. In this series of blog posts, I’m going to look at how to go about creating and working with components.

A component-based approach works well with the new namespace support added to PHP 5.3, and indeed I’m making full use of this in the components I’m building for this tutorial. But not everyone is ready to move to namespaces, and when you start to consume older components inside yours, you might run into errors.

Namespaces Recapped

The whole idea behind namespaces is to make life easier for developers, by doing away with having to type incredibly long class names all the time. For example, take the ConsoleDisplayLib component I recently released. In the pre-PHP 5.3 days, I would have had to define the classes it provides like this:

[code lang=”php”]
class Phix_Project_ConsoleDisplayLib_StdOut { … }
[/code]

… and then when I wanted to use it, I would have to do this:

[code lang=”php”]
$stdout = new Phix_Project_ConsoleDisplayLib_StdOut();
[/code]

A workable system, to be sure, and one that OO PHP programmers have used for many years, but hardly RSI-friendly, I’m sure you agree. With namespaces, however, things change. The class is now defined like this:

[code lang=”php”]
class Phix_ProjectConsoleDisplayLibStdOut { … }
[/code]

Note the use of ” instead of underscores. That’s PHP’s new namespace separator, and it tells PHP that the class StdOut lives inside the Phix_ProjectConsoleDisplayLib namespace, which in turn lives inside the Phix_Project namespace. Namespaces are just another kind of scope, like local scope vs global scope. Hold that thought, because it’s these new scopes that affect our attempts to mix in non-namespaced code.

If I’m writing code that is part of ConsoleDisplayLib, to create a new StdOut object, I now only need to do this:

[code lang=”php”]
namespace Phix_ProjectConsoleDisplayLib;

$stdout = new StdOut();
[/code]

PHP automatically expands ‘StdOut’ to mean ‘Phix_ProjectConsoleDisplayLibStdOut’, because I’ve used the namespace keyword to tell PHP that this code is part of Phix_ProjectConsoleDisplayLib.

And that is where the fun begins.

Adding HTTP_Request2 To RepustateApi

I’ve made the choice to reuse the existing PEAR component HTTP_Request2. The PEAR project doesn’t currently use namespaces; all classes defined by HTTP_Request2 exist in the global scope. So if I try to use HTTP_Request2 like this inside my Repustate API client:

[code lang=”php”]
namespace GradwellRepustateApi;

$httpClient = new HTTP_Request2($url, HTTP_Request2::METHOD_POST);
[/code]

… PHP reports the fatal error: Class GradwellRepustateApiHTTP_Request2 not found.

First time you see this, you might be wondering what is going on, and how to fix it. After all, the class is called HTTP_Request2. Why is PHP trying to load a class called GradwellRepustateApiHTTP_Request2?

The answer is simple. By using the namespace keyword at the top of the file, I’ve told PHP to assume that all of the code in this file is part of the GradwellRepustateAPI scope. So, just like in my ConsoleDisplayLib example where PHP automatically expanded StdOut to be Phix_ProjectConsoleDisplayLibStdOut, PHP is going to expand HTTP_Request2 to be GradwellRepustateApiHTTP_Request2.

That isn’t what we want, but that’s PHP’s fault. We need to tell PHP that, in this case, we want it to load HTTP_Request2, and not GradwellRepustateApiHTTP_Request2.

Load From The Global Namespace

Getting PHP to load code from the global namespace, instead of from your component’s namespace, is very easy … you’ve just got to remember to do it everywhere. Simply make sure you’ve put a ” at the start of any classnames that are part of the global namespace:

[code lang=”php”]
namespace GradwellRepustateApi;

$httpClient = new HTTP_Request2($url, HTTP_Request2::METHOD_POST);
[/code]

Remember to do this, and you’ll have no trouble working with non-namespaced components inside your own components.

Be the first to leave a comment »

In my Beyond Frameworks talk, I explained how a component-based architecture can help answer some of the important (i.e. expensive!) questions you might face when creating long-lived apps that rely on a PHP framework. In this series of blog posts, I’m going to look at how to go about creating and working with components.

Last week, I began work on a simple component to talk to the Repustate semantic analysis API. To save myself a bit of effort, I thought it would make sense to make my API client reuse PEAR’s existing HTTP_Request2 component. No sense in re-inventing the wheel, I thought. But that’s where my troubles began.

There are a few quirks in the way that the PEAR installer handles version numbers, and you need to know how to deal with them if you’re going to re-use PEAR project components in your own apps.

Adding A Dependency On HTTP_Request2

HTTP_Request2 is PEAR’s very useful wrapper around making HTTP requests from your app. At the time of writing, there are 22 PEAR packages that rely on it: a good indicator that this component is doing something right.

Making the repustateApi component rely on PEAR’s HTTP_Request2 should be as simple as adding the following to the %lt;required> section in package.xml:

<package>
  <name>HTTP_Request2</name>
  <channel>pear.php.net</channel>
  <min>2.0.0</min>
  <max>2.999.9999</max>
</package>

But, when I try to rebuild the vendor/ folder (this is the sandbox where all of the component’s dependencies are installed, so that we can run our unit tests), using the command phing build-vendor, I get an error:

[echo] Populating vendor/ with dependencies
[exec] Executing command: phix pear:register-channels 2>&1
[exec] Registering PEAR channel pear.gradwell.com
[exec] Adding Channel "pear.gradwell.com" succeeded
[exec] Discovery of channel "pear.gradwell.com" succeeded
[exec] Registering PEAR channel pear.php.net
[exec] Channel "pear.php.net" is already initialized
[exec] Executing command: pear -c /home/stuarth/Devel/sental/repustateApi/.tm
p/pear-config install --alldeps /home/stuarth/Devel/sental/repustateApi/.buil
d/package.xml 2>&1
[exec] Failed to download pear/HTTP_Request2 (version >= 2.0.0, version = 2.0.0, version <= 2.999.9999)
[exec] downloading Autoloader-2.0.1.tgz ...
[exec] Starting to download Autoloader-2.0.1.tgz (2,080 bytes)
[exec] ....done: 2,080 bytes
[exec] install ok: channel://pear.gradwell.com/Autoloader-2.0.1

So what’s going on? And what do we need to change to make this dependency work?

PEAR Version Numbers and Stability

There are two things preventing the PEAR installer from downloading the package:

  • The package’s version number is 2.0.0beta2, not plain old 2.0.0
  • The package has been marked as beta quality, rather than as a stable release.

From HTTP_Request2’s point of view, this is all perfectly reasonable. The package authors are currently transitioning from the older PHP4-based HTTP_Request, and they’re not yet ready to unleash HTTP_Request2 on the world.

As a result, the PEAR installer is (rightly) looking at the dependency information I’ve set, and telling me that HTTP_Request2 is not yet at version 2.0.0.

The PEAR installer doesn’t just use version numbers when handling dependencies. If you dig deep into package.xml, you’ll find not only a <version> tag, but also a <stability> tag. Here’s the tags from PEAR’s HTTP_Request2’s package.xml at the time of writing:

  <version>
    <release>2.0.0beta2</release>
    <api>2.0.0</api>
  </version>
  <stability>
    <release>beta</release>
    <api>beta</api>
  </stability>

How To Depend On A Beta PEAR Component

The ‘fix’ we need to make to package.xml is very simple; simply depend on the beta version:

<package>
  <name>HTTP_Request2</name>
  <channel>pear.php.net</channel>
  <min>2.0.0beta1</min>
  <max>2.999.9999</max>
</package>

With this change made, the PEAR installer does the right thing, and installs HTTP_Request2 just like I want it to 🙂

It remains to be seen what will happen when HTTP_Request-2.0.0 and beyond are released. Will the PEAR installer download it, or will I have to come back and edit the dependency in package.xml? I’ll post news on that when the time comes.

With the right dependency information in package.xml, I can now get back to cranking out the code for the repustateApi component.

Be the first to leave a comment »

This Month

March 2011
M T W T F S S
« Feb   Apr »
 123456
78910111213
14151617181920
21222324252627
28293031