Script to Download Windows PHP Binaries

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Script to Download Windows PHP Binaries

Post by Ambush Commander »

Requires CURL and PHP5.

Code: Select all

<?php

/**
 * Object-oriented wrapper for CURL, see http://php.net/manual/en/ref.curl.php
 */
class CURL
{
    
    public $handle;
    public $fileHandle;
    
    // basic constructs
    public function __construct($url = null) {
        $this->handle = curl_init();
        if ($url) $this->setopt(CURLOPT_URL, $url);
    }
    public function __destruct() {
        curl_close($this->handle);
    }
    
    // baton methods
    public function setopt($option, $value) {
        return curl_setopt($this->handle, $option, $value);
    }
    public function setoptArray($options) {
        return curl_setopt_array($this->handle, $options);
    }
    public function exec() {
        return curl_exec($this->handle);
    }
    public function errno() {
        return curl_errno($this->handle);
    }
    public function error() {
        return curl_error($this->handle);
    }
    
    // extra methods
    public function throwException($message) {
        $errno = $this->errno();
        $error = $this->error();
        throw new Exception("$message ($errno): $error");
    }
    
    
}

/**
 * Makes CURL classes, used primarily for unit testing purposes
 * @todo Generalize this into some sort of downloader interface abstract factory
 */
class CURLFactory
{
    
    public function make($url = false) {
        return new CURL($url);
    }
    
    /**
     * Generates a CURL object ready to download a file
     * @param $url URL of item to download
     * @param $filename Filename of location to download to, set to false
     *        to have contents returned by CURL->exec();
     * @return Instance of CURL ready to be executed
     */
    public function makeDownload($url, $filename = false) {
        $curl = $this->make($url);
        $curl->setopt(CURLOPT_URL, $url);
        $curl->setopt(CURLOPT_HEADER, false);
        $curl->setopt(CURLOPT_FOLLOWLOCATION, true);
        $fh = false;
        if ($filename) {
            $fh = fopen($filename, 'w');
            $curl->setopt(CURLOPT_FILE, $fh);
            $curl->fileHandle = $fh;
        } else {
            $curl->setopt(CURLOPT_RETURNTRANSFER, true);
        }
        return $curl;
    }
    
}

/**
 * Downloader interface to retrieve PHP distributions from php.net
 * @todo Allow user to specify arbitrary download path without having
 *       to subclass
 */
class PHPDownloader
{
    
    public $mirrorURL = 'http://us3.php.net';
    
    /**
     * Array of download links indexed by version number, and then
     * 'php' and 'pecl' for those downloads (will be missing if not
     * present). /from/a/mirror links are munged to /from/this/mirror .
     * If a path with no authority is given, use $mirrorURL.
     */
    public $index = false;
    
    /**
     * Contains an instance of CURLFactory, primarily for unit testing
     */
    protected $curlFactory;
    
    public function __construct($factory = false) {
        if (!$factory) $factory = new CURLFactory();
        $this->curlFactory = $factory;
    }
    
    protected function getReleasesURL() {
        return $this->mirrorURL . '/releases/';
    }
    protected function getDownloadsURL() {
        return $this->mirrorURL . '/downloads.php';
    }
    protected function getQAURL() {
        return 'http://qa.php.net';
    }
    protected function getSnapsURL() {
        return 'http://snaps.php.net';
    }
    
    public function getPHPFilename($version) {
        return $_ENV['TMP'] . "\\php-$version-Win32.zip";
    }
    
    public function getPECLFilename($version) {
        return $_ENV['TMP'] . "\\pecl-$version-Win32.zip";
    }
    
    protected function extractLinks($url) {
        $curl = $this->curlFactory->makeDownload($url);
        $html = $curl->exec();
        if (!$html) $curl->throwException('Releases download failed');
        $doc = new DOMDocument();
        @$doc->loadHTML($html);
        $xpath = new DOMXPath($doc);
        return $xpath->query('//a');
    }
    
    /**
     * Loads an index of download paths for all PHP versions
     */
    public function buildIndex() {
        if ($this->index !== false) return;
        $this->index = array();
        
        $link_sets = array();
        $link_sets[] = $this->extractLinks($this->getDownloadsURL());
        $link_sets[] = $this->extractLinks($this->getReleasesURL());
        $link_sets[] = $this->extractLinks($this->getQAURL());
        
        $first = true;
        
        foreach($link_sets as $links) {
            foreach ($links as $link) {
                $url = $link->getAttribute('href');
                if (!$url) continue;
                if (strpos($url, 'Win32') === false) continue;
                if (strpos($url, 'nts') !== false) continue; // ignore non-thread safe binaries
                $result = preg_match('#/(php|pecl)-([^-]+)-Win32\.zip#', $url, $matches);
                if (!$result) continue;
                list($full, $type, $version) = $matches;
                $url = str_replace('/from/a/mirror', '/from/this/mirror', $url); // make download ready
                if ($url[0] == '/') $url = $this->mirrorURL . $url; // heuristic assumes absolute paths are used
                $this->index[$version][$type] = $url;
                if (strpos($url, 'RC') !== false) {
                    $this->index['RC'][$type] = $url; // create alias
                } elseif (!isset($this->index['latest'][$type])) {
                    $this->index['latest'][$type] = $url;
                }
            }
        }
        
        // snapshots require a little different procedure
        $links = $this->extractLinks($this->getSnapsURL());
        foreach ($links as $link) {
            $url = $link->getAttribute('href');
            if (!$url) continue;
            if (strpos($url, 'win32') === false) continue;
            $result = preg_match('#/(php|pecl)([^-]+)-win32-\d+\.zip#', $url, $matches);
            if (!$result) continue;
            list($full, $type, $version) = $matches;
            $url = $this->getSnapsURL() . '/' . $url;
            $this->index[$version . '-dev'][$type] = $url;
        }
        
    }
    
    /**
     * Downloads relevant zips for PHP version to temporary directory
     * @param $version Version of PHP to download
     * @return array(PHP file download location, PECL file download location)
     */
    public function download($version) {
        $this->buildIndex();
        if (!isset($this->index[$version])) {
            throw new Exception("PHP $version not found on releases page");
        }
        if (!isset($this->index[$version]['php'])) {
            throw new Exception("No PHP download found for $version");
        }
        $urls = $this->index[$version];
        $curl = $this->curlFactory
            ->makeDownload($urls['php'], $php_file = $this->getPHPFilename($version));
        $result = $curl->exec();
        if (!$result) $curl->throwException('PHP download failed');
        if (isset($urls['pecl'])) {
            $curl = $this->curlFactory
                ->makeDownload($urls['pecl'], $pecl_file = $this->getPECLFilename($version));
            $result = $curl->exec();
            if (!$result) $curl->throwException('PECL download failed');
        } else {
            $pecl_file = false;
        }
        return array($php_file, $pecl_file);
    }
    
}
Usage:

Code: Select all

$downloader = new PHPDownloader();
list($php_file, $pecl_file) = $downloader->download($version);
// do something to $php_file and $pecl_file, which point to the filename file was downloaded into
Valid values for version are:

- Standard 'x.y.z'
- Development 'x.y-dev'
- Release candidate 'x.y.zRC#'
- Release candidate alias 'RC'
- Latest version alias 'latest'
Post Reply