Creating ZIP files and streaming the response with Silex

Creating a zip file is easy and Rob Allen has you covered on that front to get you started. I needed to do this to create a zip file of PDF files generated by Dompdf. It turns out, you can add files to a ZipArchive object without actually creating the file if you can get the contents as a string. This boils down to:

// $html is what you're going to render.
$dompdf = new DOMPDF(['defaultPaperOrientation' => 'landscape']);
$dompdf->loadHtml($html);
// this generates the PDF contents but does not output them anywhere
$dompdf->render(); 

// now we create a zip file and add the PDF
$zip = new ZipArchive();
$zip->open('/tmp/archive.zip', ZipArchive::CREATE);
// 'report.pdf' is the filename inside the zip archive
$zip->addFromString('report.pdf', $dompdf->output());
$zip->close();

The above adds one file, clearly if you want to create multiple PDFs, you would do that in a loop and call addFromString for each one. Then, if you want to stream the response to the browser with Silex (which uses Symfony components) you do:

use use Symfony\Component\HttpFoundation\ResponseHeaderBag; 

// ...

// at the end of a controller...
return $app->sendFile('/tmp/example.zip')
           ->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'your-report.zip')
           ->deleteFileAfterSend(true);

Everything I learned to manage my career I learned from Soccer

This was originally posted on the mojolive blog 5 years ago, but I want to preserve it here if/when it goes down.

When I started working as a web developer, there wasn’t a lot of guidance about what that meant as a career choice. Of course, back then we were getting over the excitement of the blink tag and the new design opportunities afforded by table tags. I had to figure out a career path, and I did manage to do just that by watching a lot of soccer.

1. It’s not a solo effort.

Rare is the player that can take the ball from one end line, dribble past 11 opponents, and score the winning goal. Even if they manage to do it during a game, they’re likely to use their hand to score the next goal.

It is tempting to think of your career as a solo effort, particularly in technical fields where introverts seek shelter. But the people you work alongside, meet at conventions, and connect with can be invaluable resources when you’re looking for the next challenge in your career. Even at a small company, you’ll have to work alongside other programmers, manager, designers, and end users.

It pays off to make the effort and get to know them beyond the current project, I wouldn’t be working at mojoLive if I hadn’t worked with Sandy previously. Take part in your local community events and conferences too, they’re great for expanding your network’s reach outside of colleagues, schoolmates, and clients.

2. Serendipity complements planning

When you’re watching a game, and maybe dreading a scoreless tie, the entire game can change instantly.  A player with a simple flick, a mazy dribble, or an audacious goal can change the tempo and momentum of the rest of the match.  It’s impossible for a soccer coach to plan every movement or play, like an American football or baseball manager.

It’s also impossible to plan every single move in a career, no matter how good your plan is; economic conditions can change and new technologies may become popular. You have to leave room for chance and serendipity to play a part in your career, and be willing to take a risk and make a change when an opportunity presents itself.

3. Keep your skills sharp

Once a player passes 30 years old, fans and coaches inevitably start asking how much longer they can play. Like any other athlete, the shelf life of a soccer player is limited by his physical condition. Some players can defy time; Preki was named Major League Soccer’s MVP at the ripe old age of 40.

For most of us, physical traits don’t affect our job performance so drastically. But, your own shelf life is limited by the skills you acquire and use each year. If you find yourself in a rut, using the same programming language all the time, one technology stack, or targeting a single platform, make it a point to look outside your comfort zone.

4. The good players practice, practice, practice

The best players have an almost single-minded focus on soccer. It might make them dull at dinner parties but it gives them an edge. They spend extra time after practice running through drills on their own, or hitting the gym on their off days.

Likewise, extra-curricular projects can help you learn something new. Always building Drupal sites? Check out python and django. Have an idea that could be a useful product? Build it in your spare time, to see just what it takes to build something from start to finish.

5. Play with the best to get better

I came to the game late and only started playing soccer recreationally in college. I quickly learned that playing with better players made me improve faster. Not that I ever became more than a barely adequate player, but playing with them meant I had to try to be quicker, and play smarter.

Once you find a comfort zone, it’s tempting to stay in it but you risk stagnation. If you find that you’re a big fish in a little pond or you’re a little bored with the work you’re doing, its time to think about working on more ambitious projects. For programmers, a ready avenue to collaborate with other top-notch developers is through open source projects, or simply sharing code on github.

6. Don’t be a one trick pony

It seems every team has at least one player who is one dimensional but the coach loves for some reason. The worst is watching a player who can only shoot with his left foot. Inevitably, they’ll be one-on-one with the goalkeep, but the ball will be on their right foot.  As they waste time maneauvering to get the ball on their favored left foot, a defender has time to recover and tackle the ball away.

Don’t be predictable. Don’t stick blindly to always using the same solution whether its PHP, Drupal, or jQuery. For some projects, you’ll waste time trying to fit a square peg into a round hole, when a faster, cheaper solution. It’s also tempting to think of yourself as a “Back-end programmer” or a “Front-end engineer”. Learning to do both not only improves your versatility, but you can speak intelligently about what it takes to do both.

20 Years of PHP

Ben Ramsey shared how he got started with PHP and had the great idea of asking others to write about their stories and tagging it as #20yearsofphp. This is my story.

When I graduated from college in 2000, I began looking for a job without a clear idea of what I wanted to do. In grad school I had done some projects using HTML, ASP, and ShockWave for various professors and figured I could get a job building web sites until I decided on something. I replied to a job posting (I think it was on hotjobs.com) and in September 2000 I started working as a web developer at Forum One. Thanks to that job, I spent a week working in San Francisco after meeting my (future) wife on a previous trip to California. We’d get married in 2004.

At the time, PHP4 had just been released. I worked on projects which still used PHP3, or interfaced via Perl CGI scripts to save data in a custom-build in-house CMS. I think my first actual PHP project was for a local Jewish Temple. Like other junior devs at that job, I took a shot at replacing the Perl scripts with my own PHP versions. Luckily, I never inflicted them on my colleagues.

From there, PHP was a gateway to learning about Linux, web servers, databases & SQL, and so much more. Thanks to PHP (and Drupal) I worked for my favorite soccer team, D.C. United. Today I’m grateful that, through running php[architect] I get to work not only with Eli, Kevin, Sandy, and Heather on daily basis but also with the wider PHP community through php[architect]’s magazine, books, and conferences.

I don’t think I could have planned the last 15 years better. Here’s to the next 20!

 

Why global variables are bad

This question came up yesterday when Sandy and I presented at DC Web Women, an Introduction to  PHP [slides]. I couldn’t come up with a coherent set of arguments at the time, in a way that I could explain easily. These posts do a better job, first a general programming article on the subject:

Implicit coupling — A program with many global variables often has tight couplings between some of those variables, and couplings between variables and functions. Grouping coupled items into cohesive units usually leads to better programs.

From: Global Variables Are Bad

And a PHP specific article full of excellent examples

You may have heard that globals are bad. This is often thrown around as programming gospel by people who don’t completely understand what they’re saying. These people aren’t wrong, they just don’t often program what they preach. I’ve lost track of the number of times I’ve had the “globals are bad” conversation with someone (and been in agreement) only to find their code is littered with statics and singletons. These people are confusing globals (as in the $GLOBALS array) and global state.

From: Why global state is the devil, and how to avoid using it – TomNomNom.com

Remove unapproved comments from WordPress exports

Recently, I needed to migrate some WordPress blogs to another system. WordPress provides a handy way to export content in its WXR format. However, it’ll export all comments, whether approved or not. This is good from a data backup standpoint, but I didn’t need to import these. They were also bloating the XML file and affecting how long it took my import to process.  I needed a way to remove unapproved comments, the following code will do that using PHP’s DOMDocument extension to walk an input file. The cleaned up content is sent to STDOUT so you can pipe it to another file to save.

<?php
if (!isset($_SERVER['argv'][1])) {
    echo "\nSpecify input file \n";
    exit;
}

$infile = $_SERVER['argv'][1];

$doc = new DOMDocument();
$doc->recover = TRUE;
$doc->load($infile);

$comments = $doc->getElementsByTagName('comment');
$to_remove = array();

foreach ($comments as $comment) {
    if ($approved = $comment->getElementsByTagName('comment_approved')) {
        if ($approved->length > 0) {
            $app = $approved->item(0);

            // can't remove nodes while looping
            if (0 == $app->nodeValue) {
                $to_remove[] = $comment;
            }
        }
    }
}

if (count($to_remove)) {
    foreach ($to_remove as $elt) {
        $elt->parentNode->removeChild($elt);
    }
}

$doc->formatOutput = true;
$doc->preserveWhiteSpace = false;
echo $doc->saveXML();

Extract images from an HTML snippet

The function here will take an HTML fragment and return an array of useful images it finds.

<?php
/**
 * extractImages
 *
 * @param $text
 * @return array|bool
 */
function extractImages($text)
{
    $header = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
    $text = $header . $text;
    $dom = new DOMDocument();
    if (@$dom-&gt;loadHTML($text)) {
        $xpath = new DOMXpath($dom);
        if ($images = $xpath-&gt;evaluate("//img")) {
            $result = array();
            foreach ($images as $i =&gt; $img) {
                $ht = $img-&gt;getAttribute('height');
                $wd = $img-&gt;getAttribute('width');
                // if height &amp; width are 1 its a bug, ignore
                if (1 === (int)$ht &amp;&amp; 1 === (int)$wd) {
                    continue;
                }
                // if it doesn't end in an image file extension
                // then ignore
                $src = $img-&gt;getAttribute('src');
                if (!preg_match('/.(png|jpg|gif)$/i', $src)) {
                    continue;
                }
                // do we need to figure out the full url to the image?
                if (!preg_match('/^https?:///', $src)) {
                    continue;
                }
                $alt = $img-&gt;getAttribute('alt');
                $result[$i] = array('src' =&gt; $src, 'alt' =&gt; $alt, 'height' =&gt; $ht, 'width' =&gt; $wd);
            }
            if (!empty($result)) {
                return $result;
            }
        }
    }
    return false;
}

Fix SSL timeouts with the Facebook PHP-SDK

I ran into SSL timeouts on in local development setup when I was re-factoring some integration code with facebook and using their SDK. It was tricky to diagnose, I was sure that my changes couldn’t be the cause, and I finally confirmed it by running our production codebase. Since it was having the same timeout error, I knew the bug had to be in an underlying layer.

For the record, I’m running this version of curl on my Archlinux box:

<code>curl 7.25.0 (x86_64-unknown-linux-gnu) libcurl/7.25.0<br /> OpenSSL/1.0.1 zlib/1.2.6 libssh2/1.4.0
</code>

I also got the error from the command line with

<code>curl "https://graph.facebook.com/oauth/access_token"
</code>

But it is fixed with

<code>curl --sslv3 "https://graph.facebook.com/oauth/access_token"
</code>

Debian Server

On a debian squeeze server, with the latest (4/3/2011) version of curl:

<code>curl 7.21.0 (x86_64-pc-linux-gnu) libcurl/7.21.0<br /> OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.15 libssh2/1.2.6
</code>

The timeout does not happen with either of the following commands:

<code>curl "https://graph.facebook.com/oauth/access_token"
curl --sslv3 "https://graph.facebook.com/oauth/access_token"
</code>

OS X

The time out does not happen on OS X which runs curl 7.21.4

Thoughts

So, this timeout only seems to affect users with very new version of curl. Fixing it requires adding a line to the Facebook PHP SDK, which while minor, you have to remember if you ever upgrade it. At the same time, this bug could come back and bite you down the road if your operating system sneaks in a newer version of curl. You can see a fork of the PHP SDK with this fix on github.

Other references:

  1. Facebook bug ticket
  2. Maybe related PHP bug