The Phix Project Has A New Home

Posted by Stuart Herbert on September 23rd, 2011 in Examples, phix, Toolbox.

Phix now has a new home, as I’ve recently left Gradwell. And a new PEAR channel. And an awesome new logo thanks to Jeremy and Kerry from Magma Digital. And a roadmap on Trello.

And, if you’re using either Ubuntu or Fedora as your dev desktop/laptop, a new one-line installer that takes care of those pesky system-level dependencies that the PEAR Installer can’t help you with :)

How To Move To The New PEAR Channel

If you have already installed an older version of Phix from pear.gradwell.com, you’ll need to run the following commands in order to switch over to our new PEAR channel pear.phix-project.org.

$ sudo pear uninstall Gradwell/ComponentManager 
Gradwell/ComponentManagerPhpLibrary Gradwell/ComponentManagerPhpWebapp
Gradwell/ComponentManagerPhpDocbook Gradwell/ComponentManagerShared
Gradwell/ConsoleDisplayLib Gradwell/CommandLineLib
Gradwell/ValidationLib Gradwell/phix 
Gradwell/Autoloader Gradwell/ExtenderLib

Then, visit the installation instructions on phix-project.org. Try out our new one-line installers for Ubuntu or Fedora. (Got a one-line installer for another platform? Fork the website on GitHub and send me a pull request :)

If all goes well, you should end up with phix-0.13 installed on your computer:

$ pear list -c phix | grep phix
phix                       0.13.2  stable
phix4componentdev          0.13.2  stable

If You Have Components That Depend On Phix’s Components …

… you’ll need to edit their package.xml files:

  • Swap any references to pear.gradwell.com to pear.phix-project.org.
  • Check any <min> and <max> version numbers of any Phix components you depend on, as I’ve bumped all the version numbers.

All of Phix’s components now live in the Phix_Project namespace. You’ll need to update any classes that ‘use’ any of the Phix classes accordingly.

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.

This is a guest post by Martin Wernståhl. Martin is a university student at Chalmers University of Technology, Sweden, currently studying the first year of Engineering Physics. He started programming at an age of 9 by writing Not-Quite-C for LEGO robots. Since then he has been programming a little bit of everything until he started with PHP around five years ago. Using CodeIgniter at first, he soon started to write libraries and hack the core of CodeIgniter until he felt that it was too limiting and resorted to starting to experiment with creating his own ORM tools (IgnitedRecord, discontinued as he no longer uses CodeIgniter and RapidDataMapper, development paused because of indecision related to syntax) and lately he has gone back to experimenting with creating a very modular PHP framework. Martin has also been a very enthusiastic supporter and tester of Phix and ComponentManager since their first releases.

If you would like to contribute a guest post about PHP components, please poke @stuherbert on Twitter.

How to Host a PEAR Channel on GitHub

GitHub has a feature called GitHub pages which they introduced about 2.5 years ago. This enables you to host static files located in a specific branch in a repository (gh-pages) on GitHub’s webserver. This can also be used to host a PEAR channel and can be very useful for smaller projects or if you don’t have the time or money to configure a specific server for PEAR. An added bonus is that the content of your PEAR channel server will be versioned by git.

For an example of a GitHub hosted PEAR channel, see the Inject Framework’s PEAR channel page.

The Repository

First, create a new repository on GitHub for your PEAR channel. You may now wonder why I wrote a new repository, this is because of the fact that it is usually better to separate your projects and the PEAR channel repository because of the difference in content, plus, the repository name will be a part of the URL. You might also not want to limit which packages you can put in the channel because of the naming of the parent repository.

A recommended name for the new repository is pear, as that will be equivalent with ${yourusername}.github.com/pear. A must is that the name is lowercase, as GitHub’s URLs are case sensitive.

For an example repository hosting a PEAR channel, see the Inject Framework’s PEAR repo on GitHub.

Create Your Repository Locally

To create the repository we are going to use Pirum. Pirum is a PEAR channel server "generator" which generates static files which can be used by any PEAR installer to install your packages.

So, create a folder for your pear repository and then place a pirum.xml file there:

 
<?xml version="1.0" encoding="UTF-8" ?>
<server>
    <name>${yourusername}.github.com/${pear_repository}</name>
    <summary>${page_title}</summary>
    <alias>${short_prefix}</alias>
    <url>http://${yourusername}.github.com/${pear_repository}</url>
</server>

yourusername and page_title are fairly self-explanatory. pear_repository is the name of the GitHub repository you will store your PEAR channel in. But what does the <alias> tag do?

The contents of the <alias> tag is the channel alias which can be used instead of the contents of <name> when installing PEAR packages after you have run pear channel-discover. An example is the InjectFramework: instead of injectframework.github.com/pear/InjectStack you write injectfw/InjectStack [1].

Then we create our new Git repository, run pirum build . to create the PEAR channel, then link our repository with GitHub and finally create and push the gh-pages branch:

 
cd my/pear/repository
git init
pirum build .
git remote add origin git@github.com:${yourusername}/${pear_repository}.git
git add *
git commit
git branch gh-pages
git checkout gh-pages
git push -u origin master
git push origin gh-pages

Now you should receive a notification from GitHub (can be sent via email too depending on your notification settings) telling you that your page build was successful [2]. It might take as long as up to 10 minutes before your pages will appear on http://${yourusername}.github.com/${pear_repository} the first time you push your changes. Following pushes should update the pages almost instantly.

Now on to see if it works as it should; if pear channel-discover http://${yourusername}.github.com/${pear_repository} does not return any errors, everything went fine. You might want to check http://${yourusername}.github.com/${pear_repository} using a browser to see if it has been uploaded first, you will get a 404 if it is not.

Creating A PEAR Package Using Phix

If you are using Phix to help maintain your project, you have an environment which already is configured for generating PEAR packages. But first you have to configure Phix so that it will generate the proper package.xml file:

First we edit the project.channel value in build.properties:

 
project.channel=${yourusername}.github.com/${pear_repository} 

Then we go on to the package.xml file, most of the everyday stuff (like adding files etc.) will be handled by Phix, but the descriptions, version history and package requirements need to be edited manually. See the PEAR package.xml documentation for more information.

Don’t forget to bump version info in build.properties and change-log information in package.xml when you are creating a new package version.

When you have got the package.xml and build.properties configured properly, use the command phing pear-package to create a PEAR package which will be placed in dist/${<packagename>}-X.Y.Z.tgz

Adding The Package To Your PEAR Channel

To add a PEAR package, you just use:

 
pirum add my/pear/repository my_pear_package-X.Y.Z.tgz
cd my/pear/repository
git commit -a -m "Added my_pear_package-X.Y.Z"
git push

Then visit ${yourusername}.github.com/${pear_repository} with your browser to see a list of all the uploaded packages.

Conclusion

In the beginning there was only pear.php.net, the only resource for PEAR packages in the world. Then other package channels arrived, and publishing your own PEAR package channel became really easy with Pirum. Now you don’t even need your own web-server to host it; you can host it on GitHub, along with the source code for your PEAR package.

Now, if we only can get some kind of PEAR channel aggregator or similar thing to avoid having to use channel-discover for every new tool we want to install…

Footnotes

  1. The channel name must match the regular expression [a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*(/[a-zA-Z0-9-]+)*
  2. You might want to turn off email notifications for successful page builds, because the emails can easily pile up if you make many pushes.
Be the first to leave a comment »

If you’re building a web-based app, it’s always a good idea to build some instrumentation into your app. That way, you can see how your app is behaving, and how your users are interacting with your app over time.

I’m sure everyone who reads my blog is familiar with Google Analytics for tracking page hits. But what about what’s happening inside your app? Right now? Do you know?

Graphite is one way to graph the stats that you add to your app. Combine it with (say) statsd from Etsy, and adding any stats you want is easy. (Read this blog post from Etsy if you want to learn more about measuring your app, and how to add support for Statsd to your app).

Normally, you’ll probably be interested in looking at graphs that show your stats over a period of hours or days (for trend analysis), and both Graphite and Statsd are sensibly tuned for that. But what if you want to see what’s happening now, in real time? I couldn’t find any clear instructions on how to do that elsewhere, so here’s my take on how to do it. I’m assuming you’re already familiar with installing both Statsd and Graphite, and that you’ve already had both up and running successfully with their default configurations.

Making Statsd Forward Data In Real Time

By default, Etsy’s Statsd collects the data sent from your apps, and forwards it on to Graphite’s data collector (known as Carbon) every 10 seconds. For real time, we need the data forwarded on every second. To do that, edit your Statsd config, adding the ‘flushInterval’ value:

{
  graphitePort: 2003
, graphiteHost: "localhost"
, port: 8123
, flushInterval: 1000
}

A value of 1000 tells Statsd to forward data on every second.

Making Graphite Store Data At One Second Resolution

Graphite’s default / sample configuration tells it to store incoming data at 60-second resolution; that allows us to look at the total stats recorded minute by minute, but we can’t drill down to see what happens second by second. To do that, we have to tell Graphite to store the data on a second-by-second basis.

Edit /opt/graphite/conf/storage-schemas.conf, and add the following clause:

[real_time]
priority = 200
pattern = ^stats.*
retentions = 1:34560000

This tells Graphite that we want all the data received from Statsd to be kept on a second-by-second basis for 400 days … plenty long enough for any sort of comparison you might need to do.

I found that, to get Graphite to start using this new storage definition, I had to go and delete the existing data files by doing:

$ rm -rf /opt/graphite/storage/whisper/stats*

Getting Graphite To Draw Real-Time Graphs

Now all we need to do is to get Graphite showing you all the collected data in real-time. By default, Graphite will happily plot the data onto a graph, but will only generate an updated graph every 60 seconds. That’s perfect for an ops team looking for trends over hours, but it isn’t real-time.

If you’re using Memcache with Graphite, you’ll need to add this to your /opt/graphite/webapp/graphite/local_settings.py file, to tell Graphite to only cache data in Memcache for 1 second:

MEMCACHE_DURATION = 1

Is it worth caching the data at all at this resolution? Honestly, I don’t know. I guess that depends on how many people need to watch the data in real-time or not. Ideally, it would be better if Graphite dynamically set the Memcache timeout based on the data stored in the particular key, but for now, you need to either stop using Memcache, or set the cache duration to 1 second.

This now gives you graphs with 1-second resolution … now we just need to change the Graphite web-app’s auto-refresh feature to load a new graph every second. By default, it will only generate an updated graph every 60 seconds. To change this, we have to edit some of the app’s Javascript code.

  • Open the file /opt/graphite/webapp/content/js/composer_widgets.js, and locate the function ‘toggleAutoRefresh’.
  • Locate the ‘interval’ variable inside that function. Change its value from ’60’ to ‘1’.
  • Save the file, then refresh your web browser page.

Et voila. If you switch on auto refresh, you should now be able to see your app’s data being plotted second by second, giving you a real-time view of what your app is doing.

Be the first to leave a comment »

Steps For Building A Test VM

Posted by Stuart Herbert on September 18th, 2011 in phix, Toolbox.

Using virtual machines for testing software is great. I can try out anything I want, and there’s never any risk of trashing my main dev machine (which avoids hours of lost coding time!). I’ve been using virtual machines since the 1990’s, and frankly I couldn’t develop without them. (I’ve also got a lot of experience running a private cloud of VMs for one of the UK’s fastest-growing tech companies, but that’s a topic for another day or maybe a conversation over a nice whisky at one of the upcoming conferences 😉

Right now, I’m using them to test phix out on multiple Linux distros before release. It’s a sad fact of life that all of the major distros think they can create a better PHP than the folks upstream at PHP.net intended, and that unfortunately means that a default install of PHP on Ubuntu can’t always run the same PHP code as a default install of PHP on Fedora. I found this out the hard way with an early release of phix; now, every release gets tested in a small but growing library of test VMs before I push it up to my public website.

But the best thing of all? You can backup a VM (normally by creating a snapshot), and then rollback to your snapshot in future – resetting your test environment to a known state before you test again. Perfect for this weekend’s task, which has been documenting and double-checking the steps needed to install phix on Linux dev desktops.

I thought it would be useful (to jog my own memory when the Linux distros release their next versions), to write up the steps that I do when I create a new test VM.

  • Install Linux distro from latest available ISO image.
  • Run the software upgrade tools to make sure the system is fully patched.
  • Disable SELinux if it is enabled.
  • Make sure gcc and the Linux kernel headers are installed on the system.
  • Install VMWare tools (for VMWare Workstation / Fusion) or VirtualBox Addons (for VirtualBox OSE).
  • Use Firefox to install Google Chrome.
  • Install some useful dev tools: curl and git

At this point, I shut down the virtual machine, and take a snapshot – before PHP is installed. That’s because I need to make sure that phix’s upcoming installer script does the right thing when there is no PHP. If you’re building your own test VMs, you’ll probably want to get Apache, PHP and MySQL installed and your app’s vhosts serving static pages before you snapshot.

What else would you do to a test VM to make it ready to run your tests?

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.

I’m now going under the bonnet of our components, and looking at the different file roles that the PEAR installer expects to find when we distribute our component as a PEAR-compatible package. It isn’t very often that a component needs to ship web pages too, but should the need arise, here’s how to do it.

What Is A Web Page?

A web page (the ‘www’ file role supported by the PEAR Installer) is any sort of content meant to be served up by web server software such as Apache. This can be a HTML file, a PHP script, CSS files, Javascript files, images … you name it.

Where Do Web Pages Go Inside Your Component’s Structure?

If we look at the PhpInfo component, you’ll find that the web pages live in the src/www/ folder:

src/www/ is meant to be a folder that holds all of the web pages (and related content) that you want installed into the computer system.

Where Does The PEAR Installer Install The Web Pages?

When you use the PEAR installer to install your component:

$ pear install phix/PhpInfo

all of the web pages get installed into /usr/share/php/www/ on your computer:

The PEAR installer’s behaviour here is almost identical to command-line scripts; the installer installs your web pages directly into the main www_dir folder. This does create the potential for clashes, where two or more packages want to install files with identical names. In practice, PHP components include web pages so rarely that the problem is unlikely to happen.

The test file src/www/phpinfo.php therefore ends up installed onto your computer as /usr/share/php/www/phpinfo.php.

There’s always the possibility that some Linux distros (and Mac OS X) will install doc files into a different folder. You can see where the PEAR installer will put these files on your computer by running:

$ sudo pear config-show | grep www_dir
PEAR www files directory   www_dir          /usr/share/php/www

How Do I Get Apache To Serve These Files?

By default, and for very good reasons, Apache does not serve content directly from /usr/share/php/www/. You will need to copy any files from /usr/share/php/www/ to your website folders (normally found under /var/www/) before they can be used.

8 comments »

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.

I’m now going under the bonnet of our components, and looking at the different file roles that the PEAR installer expects to find when we distribute our component as a PEAR-compatible package. One of the most important file roles allows you to ship your tests with your package – and it’s vital that this is something that you get in the habit of doing routinely.

Test Your Code, And Ship Your Tests

If you want other developers to re-use your code in their projects, it is essential that you earn their trust. Trust has to be earned over time, by establishing a track record of dependability. There are many things you need to do to establish such a reputation, such as semantic versioning to flag backwards-compatibility breaks, and writing unit tests to prove that your code works as expected.

Although the PEAR Installer has long supported shipping unit test code, to date the PHP community hasn’t really embraced this, and it is still very rare for a PEAR package to include unit tests when downloaded. This is also compounded by unfortunate legacy choices in some Linux distributions (ubuntu bug, fedora bug), where unit test code currently ends up being installed under the same directory tree as the main code from the PEAR packages – something that only really matters since the PHP Community started to adopt PSR-0 as the standard for autoloading.

What Are PHP Tests?

PHP tests (the ‘test’ file role supported by the PEAR Installer) are executable tests. There is currently no formal standard for these tests; however, PHPUnit has emerged as the community’s de facto standard for unit tests, and is the approach that the PHP components skeleton supports out of the box to make life much easier for you.

Just as important as shipping tests is making sure that it is extremely easy for someone who knows nothing about your code to safely and reliably execute any tests that you ship. The components skeleton handles much of this for you, by including a PHPUnit bootstrap.php file, and a build.xml file with full instructions on how to execute the tests.

However, it is important that you write your tests in a way that doesn’t rely on anything that your users won’t have installed or configured.

Where Do Tests Go Inside Your Component’s Structure?

If we look at the CommandLineLibrary component, you’ll find that the unit test files live in the src/tests/unit-tests/ folder:

Important notes:

  1. The folder structure underneath src/tests/unit-tests exactly matches the folder structure for both PHP scripts and PHP files under the src/ folder.
  2. Each unit test script is designed for PSR-0 autoloading, just like the PHP scripts that they test.
  3. Each unit test script ends in the word ‘Test’. This is a convention that is supported in IDEs such as Netbeans.

Where Does The PEAR Installer Install The Tests?

When you use the PEAR installer to install your component:

$ pear install phix/CommandLineLib

all of the test files get installed into /usr/share/php/tests/<package-name> on your computer:

The PEAR installer’s behaviour here is different to both command-line scripts and PHP code; the installer creates a sub-folder with the name of your package, and then installs your test files into this sub-folder, and not into the main test_dir folder. This isn’t a problem in practice, as long as you are aware of the different behaviour here.

The test file src/php/unit-tests/Phix_Project/CommandLineLib/CommandLineParserTest.php therefore ends up installed onto your computer as /usr/share/php/tests/CommandLineLib/Phix_Project/CommandLineLib/CommandLineParserTest.php.

There’s always the possibility that some Linux distros (and Mac OS X) will install doc files into a different folder. You can see where the PEAR installer will put these files on your computer by running:

$ sudo pear config-show | grep test_dir
PEAR test directory   test_dir          /usr/share/php/tests

What About Other Kinds Of Test Code?

In the PHP component skeleton, the src/tests/functional-tests folder is available for anyone who wants to add any automated testing to prove that their app works. Selenium is a tool that might help with this.

There’s also the src/tests/integration-tests folder available for anyone who wants to maintain a strict separation between unit tests (which, technically, should prove that a single class works as required) and integration tests (which, technically, should prove that a collection of classes work together as required, especially across system boundaries).

At the time of writing, the component skeleton doesn’t do anything with code inside these folders. Patches are most welcome!

10 comments »

I’m a great believer in the power of self-education, especially if you are (or want to be) in a more senior role in your firm or organisation. A regular reading list is a great way to learn more about how others are solving problems that you might be, or are about to be, struggling with. I’ve been surprised at how little people I know read, though.

Over the years, I’ve built up a list of blogs that I read daily to try and keep up with what is happening in the wider digital world. It isn’t a complete list for sure, but it’s a good start.

I’m certain that there are many other blogs out there that belong on this list. If you know of any, please send me a pull request :)

Be the first to leave a comment »

I was just wondering if anyone has published instructions on how to take the latest PHP 5.3 and PHP 5.4 source tarballs, and turn them into drop-in packages for Debian and Ubuntu, a la the DotDeb or Damz’s packages?

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.

I’m now going under the bonnet of our components, and looking at the different file roles that the PEAR installer expects to find when we distribute our component as a PEAR-compatible package. Documentation is very important to your users, but what you ship in your PEAR-compatible package should really be just the bare minimum.

Documentation Is The Missing Feature

I’m sorry to report that there is currently a large gap when it comes to handling of documentation in PHP’s PEAR-driven packaged ecosystem.

  • There is no standard markup format(s) for documentation shipped in packages.
  • There is no standard structure to install a package’s documentation into.
  • The PEAR installer does not gather up all of the installed documentation to generate a single PHP Manual-style local website of all of the installed PEAR-compatible packages. This would be a killer feature for someone to create.
  • The PEAR installer does not auto-generate PHPdoc-compatible documentation from all of the installed PEAR-compatible packages after installation.

The pear.php.net website hosts documentation for the PEAR project, but at the time of writing this documentation isn’t shipped with PEAR (or PEAR-compatible) packages.

During the rehersals for my Beyond Frameworks talk, my test audience was crystal clear about the importance of documentation. The PHP Manual is undoubtedly one of PHP’s killer features, and it rightly sets a high standard for the documentation that developers should expect to both create and have provided to them. The process and toolset for documenting PEAR-compatible packages isn’t yet a mature practice, and there’s definitely an opportunity for someone to step up and plug this gap.

This doesn’t mean that it’s a waste of time documenting your packages; it just means that, today, you’re better off including some documentation in your package, and publishing the rest of the docs somewhere else.

What Are The Docs You Should Include In Your Package?

Every PHP component should, as a minimum, ship the following documentation inside the PEAR-compatible package:

  1. LICENSE.txt – what copyright and licensing terms apply to the package
  2. README.txt – general information about the package, and where to find the package’s main website

The LICENSE.txt file is absolutely essential: it tells responsible developers whether or not they can legally use your work in their project, and just as important it sets the scene for whether or not anyone can fork your component should you decide not to maintain it any more. This file should contain the full text of the license. If you are not sure which license to use, the PHP ecosystem strongly prefers the new BSD license or the MIT license.

(You should also include license details in your docblocks at the top of your source files; that is a topic for later in the series).

The README.txt file is also essential: at the very least, it tells developers where to go to find your component’s website and full documentation. If you publish your source code via GitHub, you can write this file as README.md (using GitHub’s flavour of Markdown) and GitHub will automatically pick up the file and display it on your repository’s homepage (as an example, see phix’s homepage).

Quite a few existing PHP components also include a CREDITS.txt file, although some PHP components prefer to publish this information on their website instead.

Where Do Docs Go Inside Your Component’s Structure?

If we look at the ComponentManagerPhpLibrary component, you’ll find that the essential documentation files actually live in the component’s top-level folder:

The component skeleton will automatically pick up any files in the top-level folder that match the patterns ‘*.txt’ or ‘*.md’.

Where Does The PEAR Installer Install The Docs?

When you use the PEAR installer to install your component:

$ pear install phix/ComponentManagerPhpLibrary

all of the documentation files get installed into /usr/share/php/docs/<package-name> on your computer:

The PEAR installer’s behaviour here is different to both command-line scripts and PHP code; the installer creates a sub-folder with the name of your package, and then installs your doc files into this sub-folder, and not into the main doc_dir folder. This isn’t a problem in practice, as long as you are aware of the different behaviour here.

The doc file README.md therefore ends up installed onto your computer as /usr/share/php/docs/ComponentManagerPhpLibrary/README.md.

There’s always the possibility that some Linux distros (and Mac OS X) will install doc files into a different folder. You can see where the PEAR installer will put these files on your computer by running:

$ sudo pear config-show | grep doc_dir
PEAR documentation directory   doc_dir          /usr/share/php/docs

Okay … So What Should We Do About Proper Documentation?

Anyone getting started with your component clearly needs a lot more documentation than a LICENSE and a basic README. Unfortunately, as discussed at the start of this article, today the PEAR installer has poor support for shipping the sort of high-quality, in-depth documentation that your users both expect and deserve.

For now, the right thing to do is to publish your documentation on a website. I’m going to look at that in detail later in this series, after I have finished looking at the rest of the file roles supported by the PEAR installer.

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.

I’ve just put out ComponentManager version 1.1.3, with the following (hopefully useful) changes:

Bug Fixes For The php-library Skeleton

  • Any text docs in your component’s top-level folder (such as LICENSE.txt and README.md) will now be automatically picked up and added to your PEAR-compatible package as documentation to install.

How To Upgrade

To upgrade your installation, please do the following:

$ pear clear-cache
$ pear upgrade pear/pear
$ pear upgrade Gradwell/ComponentManager

Once the latest version of ComponentManager has been installed, you will need upgrade the skeletons of your existing components by doing:

$ cd <where-I-put-it>/<my-component>
$ phix php-library:upgrade .

This release has been tested on:

  • Ubuntu 10.10
  • Fedora 14

Any problems, let me know.

Be the first to leave a comment »
Page 3 of 912345...Last »

This Month

May 2015
M T W T F S S
« Mar    
 123
45678910
11121314151617
18192021222324
25262728293031