Archive for the ‘Web’ category

A New PEAR Channel Frontend

December 21st, 2009

It seems like PEAR2 has been brewing forever. I mean, we’ve been talking about PEAR2, namespace usage, and a brand new installer for a long time. But, now that PHP 5.3.0 is a reality and 5.3.1 is so close out the door – things are actually taking shape and getting to the point where not only can we show you what we’ve been working on, but you can actually try things out for yourself.

What I’ve been working on the past couple of days months is a new PEAR channel frontend; the basis for the PEAR2 website. I wanted a simple PEAR channel frontend, that just looks at the local filesystem for all the channel info, and would display the basics: what packages are available, categories, releases etc. Basically, just a simple web view that would not need a database backend, or any complex installation.

It would be simple to create a standalone library that just parsed xml files and read out the data, but having each underlying piece a reusable component would be very beneficial.

  • a simpler XML library for parsing files
  • an HTTP request library
  • a library to talk to PEAR channels
  • a simple templating system with php as the template language
  • html/css templates to present the content in

So when you consider the all the pieces that are required, this is quite an undertaking.

Implementation:

Ordinarily the pear installer, or pyrus, communicates to a PEAR channel through the xml files that describe the packages available. What we’ve done, is used the installer as the backbone for channel discovery and information retrieval, and we’ve told it to look at the local filesystem instead of at a remote channel. This is done using a “filesystem” adapter for the PEAR2_HTTP_Request package, which turns your local filesystem into a fake Internet. This is a fairly simple concept, that makes for a very simple PEAR channel frontend.

So, if you couple that model for data retrieval with a completely new PHP 5.3+ template system with roots in Savant, we’ve got a really nifty base for building pear channel websites.

So! The package is in the pear2 sandbox and is titled PEAR2_SimpleChannelFrontend as a companion to the PEAR2_SimpleChannelServer which makes channel creation (with categories!) a breeze.

I’ve added it to my local PEAR channel by simply dropping the .phar file in the web root as index.php, add a small .htaccess file and that’s all that is needed. Check it out at http://pear.saltybeagle.com/

As of tonight you can try it out for yourself by grabbing the PEAR2_SimpleChannelFrontend-0.1.0.phar off of the PEAR2 website and saving it along side your channel.xml and browsing to the phar file. I named it index.php for simplicity, and placed the following .htaccess file in place:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]
</IfModule>

Also, my thanks to Amir for the coding help with SimpleChannelFrontend, and the pear2 site. Things are moving along quite nicely.

PHP Template Wars – A New Hope

November 6th, 2009

Templating has been a recent issue up for debate in the PHP community, which is why I think it’s time to share what we’ve been up to with PEAR2 and Savant.

Savant is the foundation of the top PHP-based templating systems. In particular Solar View and Zend Framework’s View have roots in Savant and the great work of Paul M. Jones. Each of these are very successful templating systems, and they make for a proven foundation for templating and ease of use. So now that PEAR2 is gathering energy, and needs a strong templating system, Savant makes a good fit. But, with PEAR2, we have a lot of new features available with PHP 5.3, which means we can re-think some aspects of templating.

First, I want to talk about template/view annoyances, then about how Savant has solved these issues in PEAR2_Templates_Savant.

Annoyances:

1.) assigning data to the view

This is a problematic issue. Either you manually assign all of the data to the view, or choose individual pieces of data. Assigning individual pieces takes too much time and can lead to missing data in the view if new fields are added to a model and not assigned, or mismatched variable names. Data assignment is also difficult when the model uses magic methods or iterators for data retrieval.

2.) view selection

If you’re a sane person you probably have a pretty strong map between your models and views. Meaning, for the BaseballPlayer model, you’re probably going to be using a BaseballPlayer[.tpl.php|.phtml] file. This model to view mapping should be logical and happen automatically.

3.) isolation/sandboxing view data and template system

The data used within the view should be isolated from the templating system. The contextual data should not interfere with the templating system and vice-versa. Protected/private methods in the template system should not be callable within the template.

4.) view system setup

You should only have to set up your view system once per application, not per template.

5.) controller and flow logic in templates

It’s easier to maintain one template file for the site rather than separate sandwich templates pieces for the header and footer. The sandwich template method can lead to out of sync HTML tags, but it is difficult to use one template for the site without introducing controller logic into this view.

6.) data escaping

Sometimes this is necessary, sometimes it isn’t. Should the developer handle this or the designer? Does it matter? Flexibility should be allowed for either, and if we want data to be escaped it should happen automatically while still allowing access to un-escaped data if I know what I’m doing.

PEAR2_Templates_Savant – A New Hope

First, I want to talk about the concept of partial templates. If you’ve used the partial helper within Zend View or Solar View, you know how beneficial this can be. Partials are a simple way to break up large templates into smaller templates. This makes for a very object-oriented template usage, which matches many MVC applications, and also aids in data isolation and limiting variable scope. This is the default method of rendering in PEAR2_Templates_Savant.

Now the pitch.

How PEAR2_Templates_Savant fixes the annoyances above

1.) assigning data to the view

Because partials are the default method of rendering, there is no more assignment of data to the view. The data is made available to the template as the $context variable. This means instead of $view->name = $baseballplayer->name; or using $view->assign($baseballplayer);, you’ll simply use $view->render($baseballplayer, [tpl]);

This means that there is never any confusion about what data was assigned, or how to access the data from within the template. It also means that views have access to the public methods of the object rendered, which helps for objects which use getters and setters, or magic methods for data retrieval.

For those of you used to the old way, you can still pass a stdClass object with your own custom assignments.

2.) view selection

The render method accepts two parameters, contextual data and the template you wish to use. BOTH are optional arguments, but the magic happens when the template name is null.

Back to our ’sane’ developers who know that a BaseballPlayer object should always be rendered using the BaseballPlayer.tpl.php template. They have taken the time to build a good structure for the models in their applications, and by doing so, their life is made easier by eliminating what template to select. PEAR2_Templates_Savant uses a mapper which will map objects to templates (configurable of course). The default mapper simply maps class names to template files.

BaseballPlayer => BaseballPlayer.tpl.php
BaseballCoach => BaseballCoach.tpl.php
BaseballTeam_Phillies => BaseballTeam/Phillies.tpl.php
BaseballTeam\Phillies => BaseballTeam/Phillies.tpl.php

You can assign simple replacements to apply to the class name and the file extension is also customizable for the .phtml fans out there. Of course you can provide your own template mapper which implements the MapperInterface.

ClassToTemplateMapper.php

3.) isolation/sandboxing view data and template system

This is where PHP 5.3 shines. Closures allow us to create little sandboxes for data to play in. Feel free to pass in a __config value to the view, knowing that the data won’t interfere with the view object (who would do this?) – without using nasty global variables. Also, your template system can have private/protected methods which aren’t available inside the view which makes the class easy to extend if you need to customize. Side effect, we don’t use $this-> to get to the data, we use the $context to access data given to the view, and $savant to render further partial templates. Win for data and template separation!

4.) view system setup

Because partials are used as the default rendering, the view setup happens once and is used through the entire application.

5.) controller and flow logic in templates

When used in an application which maps objects to templates, this makes it very easy to pass off data to other templates without knowing the type of data. This means there’s no more controller-ish logic within a template such as:

<html>

<body>
<?php
if ($this->mydata instanceof BaseballPlayer) echo $this->partial('baseballplayer.tpl.php', array('name', $this->mydata->name));
elseif ($this->mydata instanceof BaseballTeam) echo $this->partial('baseballteam.tpl.php', array('name', $this->mydata->name));
?>
</body>
</html>

Now, you can create one outer page template, and hand off data to another template without having to know what type of data is:

<html>

<body>
<?php echo $savant->render($context->mydata); ?>
</body>
</html>

Objects are mapped to templates, arrays are looped over, strings, ints, doubles etc are returned after they’ve passed through any escape methods you’ve assigned.

6.) data escaping

If the developer assigns methods to escape data, this happens automatically. This is handled through an ObjectProxy which handles all requests to the inner object. If you need direct/raw access to unescaped data, you can use $context->__raw(’fieldName’);

This makes escaping easy, automatic, and avoidable if you know what you’re doing.

Teh End.

There are a few other nuances that I can share in another post, but those are the basics. Also, I want to really thank Greg Beaver who contributed many of the ideas for the latest version of Savant – including the title of this post, so thanks Greg!

So feel free to try it out and give some feedback. And so far things are shaping up nicely in the PEAR2 universe – definitely something to keep an eye on in my biased opinion.

A full example is available in the repository.

The development version of PEAR2_Templates_Savant is available through the svn repository, or installable with pyrus.

SVN:
svn checkout http://svn.php.net/repository/pear2/Templates_Savant/trunk

Browse online:
http://svn.php.net/viewvc/pear2/Templates_Savant/trunk/src/Templates/Savant/

On the (under construction) PEAR2 website:
PEAR2_Templates_Savant

Install with pyrus
php pyrus.phar /putithere install PEAR2_Templates_Savant-0.1.0

Cross-Origin Resource Sharing Demo

September 11th, 2009

The Problem

Many times I’ve been frustrated with the same-origin policy for AJAX requests. I have legitimate content on another domain that I would like to retrieve and display on another page using AJAX.

As an example, department.unl.edu would like to display a list of upcoming events from events.unl.edu/department/.

Historically you would have to serve the content out as JSON, or use a proxy to convert the content to json, and reference the resource using a script tag which gets around the same-origin policy – and for years, this has worked well.

Unfortunately this requires you to either send content out in multiple formats (html + json for these requests), or use html and send all requests for cross-site resources through a proxy script, which adds a translation delay.

Serving the content out in two formats requires more work to maintain, and if the content output is cached, requires more resources to generate. If you use a proxy to translate the content, you have an open proxy unless you lock down what resources the proxy script can retrieve and translate.

CORS is the Answer

The answer is in the Cross-Origin Resource Sharing specification, which is supported in Firefox 3.5+, Safari 4+ and albeit using agent specific methods, supported in Internet Explorer 8+.

The specification allows resources on other domains to be retrieved through AJAX methods, if the resource announces support as a Cross-Origin Resource. This is done through special headers which identify to the user-agent which alternate origin domains can request the resource and what methods are supported.

Demo

For the demo, the html and js is served off of this domain, and the CORS resource is located on ucommbieber.unl.edu.

Click here to go to the demo.

This page demonstrates both GET and POST methods, with custom callbacks, using an api just like the jQuery get and post methods.

The HTML:

<html>
<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>

<script type="text/javascript" src="cors.js"></script>
<script type="text/javascript">

function testGet()
{
    getCORS('http://ucommbieber.unl.edu/CORS/cors.php', null, function(data){alert(data);});
}
function testPost(form)
{
    postCORS('http://ucommbieber.unl.edu/CORS/cors.php', {name: form.name.value}, function(data){alert(data);});
}
</script>
<body>
<h1>CORS Examples</h1>

<p>Test GET
    This page retrieves content from another server, using CORS<br />
    <a href="#" onclick="testGet(); return false;">Get content from another server</a>
</p>

<p>Test POST:
    <form action="#" onsubmit="testPost(this); return false">

        Name: <input type="text" name="name" />
        <input type="submit" />
    </form>

</p>
<a href="http://saltybeagle.com/2009/09/cross-origin-resource-sharing-demo/">More info</a>
</body>
</html>

The javascript:

/**
 * This is for Cross-site Origin Resource Sharing (CORS) requests.
 *
 * Additionally the script will fail-over to a proxy if you have one set up.
 *
 * @param string   url      the url to retrieve
 * @param mixed    data     data to send along with the get request [optional]
 * @param function callback function to call on successful result [optional]
 * @param string   type     the type of data to be returned [optional]
 */
function getCORS(url, data, callback, type) {
    try {
        // Try using jQuery to get data
        jQuery.get(url, data, callback, type);
    } catch(e) {
        // jQuery get() failed, try IE8 CORS, or use the proxy
        if (jQuery.browser.msie && window.XDomainRequest) {
            // Use Microsoft XDR
            var xdr = new XDomainRequest();
            xdr.open("get", url);
            xdr.onload = function() {
                callback(this.responseText, 'success');
            };
            xdr.send();
        } else {
            try {
                // Ancient browser, use our proxy
                var mycallback = function() {
                    var textstatus = 'error';
                    var data = 'error';
                    if ((this.readyState == 4)
                        && (this.status == '200')) {
                        textstatus = 'success';
                        data = this.responseText;
                    }
                    callback(data, textstatus);
                };
                // proxy_xmlhttp is a separate script you'll have to set up
                request = new proxy_xmlhttp();
                request.open('GET', url, true);
                request.onreadystatechange = mycallback;
                request.send();
            } catch(e) {
                // Could not fetch using the proxy
            }
        }
    }
}

/**
 * This method is for Cross-site Origin Resource Sharing (CORS) POSTs
 *
 * @param string   url      the url to post to
 * @param mixed    data     additional data to send [optional]
 * @param function callback a function to call on success [optional]
 * @param string   type     the type of data to be returned [optional]
 */
function postCORS(url, data, callback, type)
{
    try {
        // Try using jQuery to POST
        jQuery.post(url, data, callback, type);
    } catch(e) {
        // jQuery POST failed
        var params = '';
        for (key in data) {
            params = params+'&'+key+'='+data[key];
        }
        // Try XDR, or use the proxy
        if (jQuery.browser.msie && window.XDomainRequest) {
            // Use XDR
            var xdr = new XDomainRequest();
            xdr.open("post", url);
            xdr.send(params);
            xdr.onload = function() {
                callback(xdr.responseText, 'success');
            };
        } else {
            try {
                // Use the proxy to post the data.
                request = new proxy_xmlhttp();
                request.open('POST', url, true);
                request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                request.send(params);
            } catch(e) {
                // could not post using the proxy
            }
        }
    }
}

The PHP code responding from the server looks like this:

<?php

// Specify domains from which requests are allowed
header('Access-Control-Allow-Origin: *');

// Specify which request methods are allowed
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');

// Additional headers which may be sent along with the CORS request
// The X-Requested-With header allows jQuery requests to go through
header('Access-Control-Allow-Headers: X-Requested-With');

// Set the age to 1 day to improve speed/caching.
header('Access-Control-Max-Age: 86400');

// Exit early so the page isn't fully loaded for options requests
if (strtolower($_SERVER['REQUEST_METHOD']) == 'options') {
    exit();
}

// If raw post data, this could be from IE8 XDomainRequest
// Only use this if you want to populate $_POST in all instances
if (isset($HTTP_RAW_POST_DATA)) {
    
$data explode('&'$HTTP_RAW_POST_DATA);
    foreach (
$data as $val) {
        if (!empty(
$val)) {
            list(
$key$value) = explode('='$val);   
            
$_POST[$key] = urldecode($value);
        }
    }
}

echo 'Hello CORS, this is '
     
$_SERVER['SERVER_NAME'] . PHP_EOL
     
.'You sent a '.$_SERVER['REQUEST_METHOD'] . ' request.' PHP_EOL;

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    echo 
'Your name is ' htmlentities($_POST['name']);
}


Note the special headers that have to be returned to allow the request to complete:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: X-Requested-With

The X-Requested-With allows us to use jQuery’s internal GET method which adds in the additional header.

View all the code on GitHub.

Support for proxy fallback for older browsers is left up to you to code. The proxy requires you to set up a script which translates a page into JSON and handles the response. There are many examples of how to do this.

Apache Virtual Hosts On Mac OS X

August 1st, 2009

I’ve said, if it takes you more than 10 minutes to find an answer – it’s your responsibility to document it, and I’ve been slacking.

Today’s issue was enabling virtual hosts on Apache & Mac OS X. Normally not a big deal, but I was stumped for a while by a simple error. Apache would not start, and apachectl was giving me no error.

The workaround to find out what the heck was going on was just to execute httpd directly, which showed me what the problem was:

Syntax error on line 37 of /private/etc/apache2/extra/httpd-vhosts.conf:
CustomLog takes two or three arguments, a file name, a custom log format string or format name, and an optional "env=" clause (see docs)

Well the issue is, the default vhosts file on Mac OS X has a misplaced double quote:

CustomLog "/private/var/log/apache2/dummy-host2.example.com-access_log common"

Which should be:

CustomLog "/private/var/log/apache2/dummy-host2.example.com-access_log" common

of course.

So, the simple instructions:
edit /etc/apache2/httpd.conf and uncomment the line that says:
Include /private/etc/apache2/extra/httpd-vhosts.conf

Now add your virtual hosts by editing the
/etc/apache2/extra/httpd-vhosts.conf file, make sure you place that double quote in the correct place if you’re defining custom log files.

Then run sudo apachectl graceful

Remote PEAR Package Releases

June 17th, 2009

Sune Jensen sent me a note about a couple minor additions to the remote PEAR package releaser script, which would allow it to automatically create the package and maintainers if it did not exist on the PEAR channel managed with Chiara_PEAR_Server. This is now possible since Christian Weiske added the options to the Chiara_PEAR_Server admin interface a while ago.

I’ve added in the patch from Sune, and released it as Salty_PEAR_Server_RemoteReleaseDeployer-0.1.0.

You can grab the release here, or install it with

pear channel-discover pear.saltybeagle.com
pear install salty/Salty_PEAR_Server_RemoteReleaseDeployer-0.1.0

Thanks Sune!

Using SimpleChannelServer to manage a PEAR channel on Google Code

December 8th, 2008

I needed to build a quick pear channel for a library I had built – here’s all it takes:

For this example, we’ll assume your Google project is named: myproject

First, we need to check out the root of your Google Code project, so we can place the channel files here, away from your source code files.

svn checkout https://myproject.googlecode.com/svn/ myproject --username mygoogleusername

Now that we have a checkout, let’s build the channel:

cd myproject

Download the SimpleChannelServer phar file from here:

wget "http://svn.php.net/viewvc/pear2/sandbox/SimpleChannelServer/trunk/pearscs.phar?view=co" -O pearscs.phar

Using a recent version of PHP 5.3, let’s execute the pearscs.phar file, and create the channel for the project:

php pearscs.phar create myproject.googlecode.com/svn myproject

Now commit the files to your repository, and let’s try discovering your pear channel.

pear channel-discover myproject.googlecode.com/svn/

You should see the following message, telling you that the channel was added successfully:

Adding Channel "myproject.googlecode.com/svn" succeeded
Discovery of channel "myproject.googlecode.com/svn/" succeeded

Now let’s create our pear package. PEAR has many tools available to aid in package creation. To get up and running quickly, we’ll start from an example and modify it for our own needs.
Download http://simplecas.googlecode.com/svn/trunk/makepackage.php and save it to your local project as trunk/makepackage.php

Modify this file to use your channel name, package name, release information and maintainer info.
Now let’s create the package:

php makepackage.php
pear package

Now you should have a file myproject-0.1.0.tgz

Let’s release the package to the world, substitute the lead name with your handle:

php pearscs.phar release trunk/myproject-0.1.0.tgz leadhandle

This will add the distribution files to the /get/ directory, and update the channel xml files. There’s a small issue with using Google code to host the xml files, and that’s that the content-type needs to be sent as application/xml or text/xml for the channel files to be read correctly, so set the mime-type property for the xml files.

find . -name '*.xml' | xargs svn propset svn:mime-type application/xml

Now commit these files to your repository, and let’s try installing the package!

pear install myproject/myproject-0.1.0

If everything went correctly, pear should have installed the package. Now when you create a new release, just run the release command, update the mime-type on the xml files and commit your changes.

FYI – the package I needed a channel for is SimpleCAS a PHP5 library for authenticating users against a JA-SIG Central Authentication Service.

Updates

There are a couple gotchas for phar packaging. Here’s some info that might ease some problems with creating or opening phars or PEAR packages:
* Make sure you have zlib! ./configure –with-zlib
* php.ini settings

[phar]
phar.readonly = 0
phar.require_hash = 0

PHP Savant Has A New Home

August 20th, 2008

As Paul already announced on his blog, I am taking over the Savant project for him, and I will act as the steward for the project into the future.

Savant 3 logo

I’m very familiar with Savant, and use it in all my projects – so when Paul asked if anyone was interested, I volunteered for two reasons – firstly, I’m familiar with the project and knew I wanted to take part, and secondly, many of my projects are dependent on Savant and I would be in for a world of hurt if it went away!

So Paul and I have had quite a few email exchanges over the past couple of weeks, through DNS transfers, file exchanges, database dumps etc. And as of now I’m hosting the PHPSavant.com site, and hopefully will continue the project’s important position and beacon as ‘The Simple, Elegant Template System for PHP.’

Many thanks to Paul for his help over the past few weeks – and to those interested in helping out, feel free to get in contact with me. I’d be happy to let others participate in the project.

Oh, and I hope you like the new design for the homepage. I’ll be working on the site over the next few weeks, so things may be out of place at times, but I’ll do my best to make it easy to find the information developers need.

http://phpsavant.com/

Here’s to Savant!

Catalog of Courses/Graduate Bulletin

April 17th, 2008

Course catalogs are a beast of a publication. The documents are traditionally printed on regular intervals and offer a complete listing of requirements and courses of study for an academic institution. For some students, these books become the bible by which their lives revolve around for 4+ years of professional education. They become a carefully studied map guiding them through the courses required to earn a very expensive piece of paper.

I followed my bulletin closely and kept a copy of it in a secure location. I would refer to it after every semester to make sure I was still on track. Sure there were minute changes, but this was a mammoth printed catalog… and acted as a binding contract between the school and the student.

As the electronic era has progressed, we’re offered new opportunities for publishing these large documents, be it PDF, HTML or even print on demand. At some point I’m sure typesetters were working on this document and it was rarely updated. At some point it was transferred to an electronic format, but still printed to paper. But recently I had the opportunity to transfer one of these large publications into an electronic format, that will hopefully become a long lasting appointment.

I’m referring to, an online course catalog or bulletin of courses. In this instance, the University of Nebraska-Lincoln’s graduate bulletin. Not to a PDF, but to a solely electronic based medium that will act as the permanent location of the publication.

The old file was a meticulously updated FrameMaker document, which offered some flexibility in output, but nothing to the extent required. The process took probably 3 months to complete, and went online last November to some quiet fanfare. But today, I felt one of the most gratifying moments any web developer could ask for… an end user expressed an unprovoked appreciation and testimonial of the website.

There may never come a day when someone acknowledges the revisioning features, the XCRI XML output format, the simple URL structure or the ease of editing – but after today, it was all worth it.

With that, here’s the link – I commend everyone else that has transferred one of these large publications into an online format, it is no easy task.

The University of Nebraska-Lincoln 2007-2008 Graduate Studies Bulletin

Squeezing More Performance Out Of Your Web Applications

October 10th, 2007

Steve Souders of Yahoo! gives a talk on the important steps to take for ‘High Performance Websites.’ What I found interesting is that he follows a different perspective than most people when it comes to high performance websites. His research shows that the largest amount of improvement you can make is in the actual frontend loading of the page. Which is to say, what it takes to get your web app displayed in the client’s browser.

He basically outlines the 14 rules used in the YSlow browser extension and why they’re important. An interesting video if you’re interested in making your web applications run faster.

http://video.yahoo.com/video/play?vid=1040890

An interesting talk which takes a different line of thinking than backend improvements for high performance & scalability. I’m about halfway through Theo Schlossnagle’s book on Scalable Internet Architectures. If I ever finish that book I’ll try and post a review of the interesting points.

Unit Tests for HTML Validation

September 29th, 2007

It is now very easy to integrate testing for valid HTML into your web application’s test environment using the W3C’s validator and PHPUnit. I just committed a sample unit test for PHPUnit which will checks your html markup.

The test cases or suites are very easy to integrate into your existing environment with the Services_W3C_HTMLValidator package.

First you’ll need the PEAR package for html validation:
pear install Services_W3C_HTMLValidator-beta

And then you should copy down the two sample PHPUnit files ValidationSuite.php and URIValidationTest.php.

Within the ValidationSuite.php file, modify the array of URLs to check and simply run the tests with php ValidationSuite.php

A validation test will be automatically added for each URI you wish to validate.

– NOTE: If you plan on validating many pages, consider installing a local copy of the W3C validator – which isn’t that complicated and you’ll get faster results.

RoR has a similar plugin for html validation called ResponsibleMarkup, and I think the title is appropriately named.

Happy validating!