Page 1 of 1

Script to Download Windows PHP Binaries

Posted: Wed Oct 24, 2007 10:01 pm
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'