1. Co je wrapper

Jakmile si programátor zvykne na OOP přístup a začne se snažit dosáhnout co nejkvalitnějšího kódu, narazí na problém který provází jazyk PHP od jeho začátku. Zatímco se začínajícím programátorů mohlo hodit, že i relativně pokročilá operace jako například práce s databází byla možná vykonat bez znalosti OOP, pokročilejším programátorům to přináší komplikace. Jak bylo napsáno v článku o OOP, třída by se měla veřejně hlásit ke svým závislostem. Jak ale napsat závislost na možnosti komunikace přes CURL, když pro CURL neexistuje třída?

Odpověď je prostá, vytvoří se třída která obslouží komunikaci přes CURL a následně se tato třída použije jako závislost. Těmto třídám, které slouží jako mapování základních funkcí jzayka pro potřeby OOP, se říká „wrapper“. V následujících odstavcích bude popsána tvorba základního „CURL wrapperu“. Je však důležité si připomenout, že méně je obvykle více a proto by wrapper měl dělat pouze to, k čemu je určen, tedy by měl obsahovat pouze metody potřebné pro vytvoření OOP vrstvy. Neměl by se stát universální a všeobjímající knihovnou v jedné třídě. Pro pohodlnější práci však může obsahovat drobné syntaktické libůstky, neměli by však přesáhnou základní funkcionalitu wrapperu. Proto by například „CURL wrapper“ neměl obsahovat funkce pro dekódování odpovědí nebo naopak enkódování požadavků.

2. Příprava na tvorbu wrapperu

Základem tvorby každého wrapperu by mělo být seznámení se s problematikou a možnostmi jazyka, které by se měli do wrapperu zanést. Programovací jazyk PHP má opravdu kvalitní a udržovanou dokumentaci. Lze tedy najít například následující sekci manuálu věnující se technologi CURL, ve které se lze dočíst vše potřebné.

Aby bylo možné vytvořit instanci třídy a pracovat s jejím objektem, musí ona třída nejprve existovat. Z názvu třídy by mělo být patrné, k čemu třída slouží a zároveň by měla být umístěna do jmenného prostoru. Název souboru a jeho umístění v adresářové struktuře by mělo respektovat název třídy a její jmenný prostor, včetně velikosti písmen, jak bylo popsáno v článku věnovaném OOP.

<?php
//SimpleCurl.php
namespace Zeleznypa\Curl;

/**
 * Simple cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class SimpleCurl
{

} 
    

3. Základní jádro wrapperu

Komunikace přes technologii CURL je v jazyce PHP členěna na čtyři části (inicializace, nastavení, vykonáníukončení). Nemělo by být možné provést komunikaci bez této sekvence příkazů, jak ukazuje i příklad z manuálu.

Při bližším pohledu na vstupní a výstupní parametry jednotlivých funkcí je patrné, že pro CURL komunikaci je zapotřebí určit cílovou adresu požadavku. Dále se obvykle předávají alespoň základní parametry pro nastavení komunikace. Jistě se hodí mít i přístup k odpovědi ze serveru. A v neposlední řadě je zapotřebí udržovat takzvaný „resource“ po celou dobu práce s CURL. Nejjednodušší zapouzdření by tak mohlo vypadat následovně.

<?php
//SimpleCurl.php
namespace Zeleznypa\Curl;

/**
 * Simple cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class SimpleCurl
{
    /**
     * Execute cURL request with defined params
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @param array $options
     * @return string|bool Depend on option CURLOPT_RETURNTRANSFER
     */
    public function execute($url, array $options = array())
    {
        $ch = curl_init($url);
        curl_setopt_array($ch, $options);
        $result = curl_exec($ch);
        curl_close($ch);

        return $result;
    }

} 
    

Z pohledu na stránky PHP manuálu věnující se technologii CURL je rovněž patrné, že programovací jazyk PHP nabízí v této oblasti mnohem více možností. Obvykle se však vyplácí vytvořit nejprve jednoduchou malou knihovnu s univerzálním použitím a všechny pokročilejší funkce umístit do vlastní třídy, která rozšiřuje knihovnu obecnou. Díky tomu bude možné využívat všech výhod, ale v případě potřeby umožní knihovna svým návrhem i předem neočekávané použití.

V OOP se, na rozdíl od procedurálního programování, objekt nejprve nastaví a teprve v případě potřeby provede jeho funkcionalita. Proto bude potřeba přenést veškteré nastavení včetně používaného „resource“ do stavových proměnných třídy. Jednotlivé funkce pak budou ve třídě reprezentovány metodami, které budou s těmito stavovými proměnnými pracovat. Knihovna by však programátorovi pokud možno neměla umožnit její chybné použití. Proto by očekávana sekvence volaných metod měla být umístěna do samostané veřejné metody. Takto použité metody by tedy neměly být přístupné ve veřejném rozhraní třídy, aby nebyly nedopatřením zavolány a neuvedly tak objekt do nekonzistentního stavu.

<?php
//SimpleCurl.php
namespace Zeleznypa\Curl;

/**
 * Simple cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class SimpleCurl
{
    /** @var resource $handler cURL handle on success, false on errors. */
    private $handler;

    /** @var array $options */
    private $options = array();

    /** @var string|bool $result Depend on option CURLOPT_RETURNTRANSFER */
    private $result;

    /**
     * Constructor
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return void
     */
    public function __construct($url = NULL)
    {
        if ($url !== NULL)
        {
            $this->setUrl($url);
        }
    }

    /**
     * Execute cURL request with defined params
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    public function execute()
    {
        curl_setopt_array($this->getHandler(), $this->getOptions());        
        $this->result = curl_exec($this->getHandler());
        curl_close($this->getHandler());
        return $this;
    }

    /**
     * Get cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Get cURL option
     * @author Pavel Železný <[email protected]>
     * @param int $key One from cURL options
     * @param mixed $default Value returned if option is not present
     * @return mixed
     */
    public function getOption($key, $default = FALSE)
    {
        return (isset($this->options[$key]) === TRUE) ? $this->options[$key] : $default;
    }

    /**
     * Set cURL options by array
     * @author Pavel Železný <[email protected]>
     * @param array $options
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    public function setOptions(array $options)
    {
        foreach ($options as $key => $value)
        {
            $this->setOption($key, $value);
        }
        return $this;
    }

    /**
     * Set cURL option
     * @author Pavel Železný <[email protected]>
     * @param int $key
     * @param mixture $value
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    public function setOption($key, $value)
    {
        $this->options[$key] = $value;
        return $this;
    }

    /**
     * Get cURL responsponse result
     * @author Pavel Železný <[email protected]>
     * @return string|bool Depend on option CURLOPT_RETURNTRANSFER
     */
    public function getResult()
    {
        return $this->result;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->getOption(CURLOPT_URL, NULL);
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    public function setUrl($url)
    {
        return $this->setOption(CURLOPT_URL, $url);
    }

    /**
     * CURL handler getter
     * @author Pavel Železný <[email protected]>
     * @return resource cURL on success, false on error
     */
    protected function getHandler()
    {
        if ($this->handler === NULL)
        {
            $this->handler = curl_init();
        }

        return $this->handler;
    }

} 
    

Aby bylo možné wrapper pohodlnějí rozšířit, bude vhodné přesunout jednotlivé části metody execute() do samostatných metod, aby se případně daly přetížit a doplnit o další funkcionality. Zároveň by tyto metody neměly být samostatně volatelné z venku CURL wrapperu.

<?php
//SimpleCurl.php
namespace Zeleznypa\Curl;

/**
 * Simple cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class SimpleCurl
{
    /** @var resource $handler cURL handle on success, false on errors. */
    private $handler;

    /** @var array $options */
    private $options = array();

    /** @var string|bool $result Depend on option CURLOPT_RETURNTRANSFER */
    private $result;

    /**
     * Constructor
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return void
     */
    public function __construct($url = NULL)
    {
        if ($url !== NULL)
        {
            $this->setUrl($url);
        }
    }

    /**
     * Execute cURL request with defined params
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    public function execute()
    {
        $this->processOptions();
        $this->processResponse();
        $this->processClose();
        return $this;
    }

    /**
     * Get cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Get cURL option
     * @author Pavel Železný <[email protected]>
     * @param int $key One from cURL options
     * @param mixed $default Value returned if option is not present
     * @return mixed
     */
    public function getOption($key, $default = FALSE)
    {
        return (isset($this->options[$key]) === TRUE) ? $this->options[$key] : $default;
    }

    /**
     * Set cURL options by array
     * @author Pavel Železný <[email protected]>
     * @param array $options
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    public function setOptions(array $options)
    {
        foreach ($options as $key => $value)
        {
            $this->setOption($key, $value);
        }
        return $this;
    }

    /**
     * Set cURL option
     * @author Pavel Železný <[email protected]>
     * @param int $key
     * @param mixture $value
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    public function setOption($key, $value)
    {
        $this->options[$key] = $value;
        return $this;
    }

    /**
     * Get cURL responsponse result
     * @author Pavel Železný <[email protected]>
     * @return string|bool Depend on option CURLOPT_RETURNTRANSFER
     */
    public function getResult()
    {
        return $this->result;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->getOption(CURLOPT_URL, NULL);
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    public function setUrl($url)
    {
        return $this->setOption(CURLOPT_URL, $url);
    }

    /**
     * CURL handler getter
     * @author Pavel Železný <[email protected]>
     * @return resource cURL on success, false on error
     */
    protected function getHandler()
    {
        if ($this->handler === NULL)
        {
            $this->handler = curl_init();
        }

        return $this->handler;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getOptions());
        return $this;
    }

    /**
     * Process cURL response
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    protected function processResponse()
    {
        $this->result = curl_exec($this->getHandler());
        return $this;
    }

    /**
     * Process cURL close
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\SimpleCurl Provides fluent interface
     */
    protected function processClose()
    {
        curl_close($this->getHandler());
        return $this;
    }

}
    

4. Rozšíření wrapperu

Při běžné práci s CURL wrapperem se budou velice často opakovat stejná nastavení vlastností. Proto by bylo vhodné vytvořit CURL wrapper, který by výchozí vlastnosti nastavil automaticky. Vytvoří se tedy třída, která „rozšíří“ SimpleCurl wrapper tím, že na začátku volání metody execute() načte navíc doporučené nastavení. Mezi taková „výchozí“ nastavení lze počítat například maximální doba běhu požadavku (CURLOPT_TIMEOUT), maximální doba po kterou se CURL wrapper pokouší připojit k serveru (CURLOPT_CONNECTTIMEOUT), a nebo nastavení příznaku, který vrátí odpověď serveru do proměnné namísto standardního výstupu (CURLOPT_RETURNTRANSFER).

<?php
//Curl.php

namespace Zeleznypa\Curl;

/**
 * cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class Curl extends \Zeleznypa\Curl\SimpleCurl
{

    /**
     * Real request cURL options getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getRequestOptions()
    {
        return $this->getDefaultOptions() + $this->getOptions();
    }

    /**
     * Get default cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    protected function getDefaultOptions()
    {
        $options[CURLOPT_CONNECTTIMEOUT] = 30;
        $options[CURLOPT_RETURNTRANSFER] = TRUE;
        $options[CURLOPT_TIMEOUT] = 30;
        return $options;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getRequestOptions());
        return $this;
    }

}
    

U požadavku typu GET se data předávají jako součást URL adresy. Volat však po každé PHP funkci http_build_query() není určitě pohodlné. Mohlo by se tedy hodit mít možnost jednou metodou nastavit cílovou adresu serveru a jinou metodou nastavit předávaná data. Data se obvykle předávají jako pole, ale může se hodit mít možnost přidávat jednotlivé části postupně a proto by zde měla být nejen metoda pro přidání celého balíku dat, ale i metoda pro přidání jednotlivých částí. Zároveň se také hodí mít možnost získat finální url adresu, která byla volána.

Tento požadavek není tak jednoduchý, jak by se mohlo zdát. Každá nastavovaná část se musí uložit do své vlastní stavové proměnné, aby se dala kdykoliv vrátit v nezměněné podobě a výsledné URL se musí sestavit až v momentě potřeby. Při tom ale nesmí pozměnit uložená data. Navíc je potřeba počítat s tím, že cílová adresa již může obsahovat nějaká data předem, například autorizační token.

<?php
//Curl.php

namespace Zeleznypa\Curl;

/**
 * cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class Curl extends \Zeleznypa\Curl\SimpleCurl
{

    /** @var array $arguments */
    private $arguments = array();

    /** @var string $url */
    private $url;

    /**
     * URL argument getter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param mixed $default
     * @return mixed URL argument value or $default if argument is not set.
     * @throws \BadMethodCallException
     */
    public function getArgument($argumentName, $default = NULL)
    {
        if (array_key_exists($argumentName, $this->arguments))
        {
            return $this->arguments[$argumentName];
        }
        else
        {
            if (func_num_args() < 2)
            {
                throw new \BadMethodCallException('Missing argument "' . $argumentName . '".');
            }
            return $default;
        }
    }

    /**
     * Url argument setter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param string|integer $argumentValue
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \BadMethodCallException
     */
    public function setArgument($argumentName, $argumentValue, $overwrite = FALSE)
    {
        if ((array_key_exists($argumentName, $this->arguments) === FALSE) || ($overwrite === FALSE))
        {
            $this->arguments[$argumentName] = $argumentValue;
            return $this;
        }
        else
        {
            throw new \BadMethodCallException('Argument "' . $argumentName . '" has already been set.');
        }
    }

    /**
     * URL arguments getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * URL arguments setter
     * @author Pavel Železný <[email protected]>
     * @param array $arguments
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setArguments(array $arguments, $overwrite = FALSE)
    {
        foreach ($arguments as $argumentName => $argumentValue)
        {
            $this->setArgument($argumentName, $argumentValue, $overwrite);
        }
        return $this;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setUrl($url)
    {
        $this->url = $url;
        return $this;
    }

    /**
     * Real request url getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestUrl()
    {
        $urlArguments = http_build_query($this->getArguments());
        if ($urlArguments === '')
        {
            return $this->getUrl();
        }
        elseif (strpos($this->getUrl(), '?') === FALSE)
        {
            return $this->getUrl() . '?' . $urlArguments;
        }
        else
        {
            return $this->getUrl() . '&' . $urlArguments;
        }
    }

    /**
     * Real request cURL options getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getRequestOptions()
    {
        return $this->getDefaultOptions() + $this->getOptions();
    }

    /**
     * Get default cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    protected function getDefaultOptions()
    {
        $options[CURLOPT_CONNECTTIMEOUT] = 30;
        $options[CURLOPT_RETURNTRANSFER] = TRUE;
        $options[CURLOPT_TIMEOUT] = 30;
        $options[CURLOPT_URL] = $this->getRequestUrl();
        return $options;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getRequestOptions());
        return $this;
    }

}
    

Při komunikaci s API se obvykle nastavuje takzvaný „endpoint“. Nejedná se o nic složitějšího než o jiné pojmenování pro část url adresy za doménou. Api klient má tak nastavenou adresu serveru a rozdílným „endpointem“ volá příslušné metody. Pro pohodlnější práci s wrapperem se tedy může hodit mít možnost nastavit zvlášť adresu serveru a zvlášť volaný endpoint. Stačí přidat příslušné „gettery a settery“ a mírně přetížit volání původního získání adresy požadavku.

<?php
//Curl.php

namespace Zeleznypa\Curl;

/**
 * cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class Curl extends \Zeleznypa\Curl\SimpleCurl
{

    /** @var array $arguments */
    private $arguments = array();

    /** @var string $endpoint */
    private $endpoint;

    /** @var string $url */
    private $url;

    /**
     * URL argument getter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param mixed $default
     * @return mixed URL argument value or $default if argument is not set.
     * @throws \BadMethodCallException
     */
    public function getArgument($argumentName, $default = NULL)
    {
        if (array_key_exists($argumentName, $this->arguments))
        {
            return $this->arguments[$argumentName];
        }
        else
        {
            if (func_num_args() < 2)
            {
                throw new \BadMethodCallException('Missing argument "' . $argumentName . '".');
            }
            return $default;
        }
    }

    /**
     * Url argument setter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param string|integer $argumentValue
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \BadMethodCallException
     */
    public function setArgument($argumentName, $argumentValue, $overwrite = FALSE)
    {
        if ((array_key_exists($argumentName, $this->arguments) === FALSE) || ($overwrite === FALSE))
        {
            $this->arguments[$argumentName] = $argumentValue;
            return $this;
        }
        else
        {
            throw new \BadMethodCallException('Argument "' . $argumentName . '" has already been set.');
        }
    }

    /**
     * URL arguments getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * URL arguments setter
     * @author Pavel Železný <[email protected]>
     * @param array $arguments
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setArguments(array $arguments, $overwrite = FALSE)
    {
        foreach ($arguments as $argumentName => $argumentValue)
        {
            $this->setArgument($argumentName, $argumentValue, $overwrite);
        }
        return $this;
    }

    /**
     * Endpoint getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * Endpoint setter
     * @author Pavel Železný <[email protected]>
     * @param string $endpoint
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setEndpoint($endpoint)
    {
        $this->endpoint = '/' . ltrim($endpoint, '/');
        return $this;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setUrl($url)
    {
        $this->url = $url;
        return $this;
    }

    /**
     * Real request url getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestUrl()
    {
        $url = ($this->getEndpoint() !== NULL) ? rtrim($this->getUrl(), '/') . $this->getEndpoint() : $this->getUrl();
        $urlArguments = http_build_query($this->getArguments());
        if ($urlArguments === '')
        {
            return $url;
        }
        elseif (strpos($url, '?') === FALSE)
        {
            return $url . '?' . $urlArguments;
        }
        else
        {
            return $url . '&' . $urlArguments;
        }
    }

    /**
     * Real request cURL options getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getRequestOptions()
    {
        return $this->getDefaultOptions() + $this->getOptions();
    }

    /**
     * Get default cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    protected function getDefaultOptions()
    {
        $options[CURLOPT_CONNECTTIMEOUT] = 30;
        $options[CURLOPT_RETURNTRANSFER] = TRUE;
        $options[CURLOPT_TIMEOUT] = 30;
        $options[CURLOPT_URL] = $this->getRequestUrl();
        return $options;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getRequestOptions());
        return $this;
    }

}
    

Uživatel wrapperu si však při komunikaci s API nevystačí jen s požadavkem typu GET. Je proto nezbytně nutné umožnit nastavení typu požadavku. S tím souvisí i možnost předat v požadavku data. Ostatní metody se však při předávání dat od metody GET liší. Zatímco metoda GET využívá pro předávání dat prostor URL adresy a tělo požadavku má prázdné, ostatní metody využívají pro předávání „dat“ právě tělo požadavku. Nestačí tedy dosavadní práce s „argumenty“ ale musí vzniknout i možnost předání „dat“. Data se předávají výhradně jako string a proto by pro pohodlnost měla být přidána metoda, která se zavolá když předávaná data budou jiného typu. Navíc se jistě hodí mít možnost nastavit vlastní obsluhu a proto by měla být metoda volána callbackem. Díky tomuto přístupu si může programátor nastavit vlastní callback použitý pro předávání dat a vytvoří si tak velice rychle JSON-API klient.

<?php
//Curl.php

namespace Zeleznypa\Curl;

/**
 * cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class Curl extends \Zeleznypa\Curl\SimpleCurl
{

    const
            DELETE = 'DELETE',
            GET = 'GET',
            POST = 'POST',
            PUT = 'PUT';

    /** @var array $arguments */
    private $arguments = array();

    /** @var string $communicationMethod */
    private $communicationMethod = self::GET;

    /** @var mixed $data */
    private $data;

    /** @var string $endpoint */
    private $endpoint;

    /** @var callable $serializeDataFunction */
    private $serializeDataFunction;

    /** @var string $url */
    private $url;

    /**
     * Constructor
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return void
     */
    public function __construct($url = NULL)
    {
        parent::__construct($url);
        $this->setSerializeDataFunction(array($this, 'serializeData'));
    }

    /**
     * URL argument getter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param mixed $default
     * @return mixed URL argument value or $default if argument is not set.
     * @throws \BadMethodCallException
     */
    public function getArgument($argumentName, $default = NULL)
    {
        if (array_key_exists($argumentName, $this->arguments))
        {
            return $this->arguments[$argumentName];
        }
        else
        {
            if (func_num_args() < 2)
            {
                throw new \BadMethodCallException('Missing argument "' . $argumentName . '".');
            }
            return $default;
        }
    }

    /**
     * Url argument setter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param string|integer $argumentValue
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \BadMethodCallException
     */
    public function setArgument($argumentName, $argumentValue, $overwrite = FALSE)
    {
        if ((array_key_exists($argumentName, $this->arguments) === FALSE) || ($overwrite === FALSE))
        {
            $this->arguments[$argumentName] = $argumentValue;
            return $this;
        }
        else
        {
            throw new \BadMethodCallException('Argument "' . $argumentName . '" has already been set.');
        }
    }

    /**
     * URL arguments getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * URL arguments setter
     * @author Pavel Železný <[email protected]>
     * @param array $arguments
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setArguments(array $arguments, $overwrite = FALSE)
    {
        foreach ($arguments as $argumentName => $argumentValue)
        {
            $this->setArgument($argumentName, $argumentValue, $overwrite);
        }
        return $this;
    }

    /**
     * Communication method getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getCommunicationMethod()
    {
        return $this->communicationMethod;
    }

    /**
     * Communication method setter
     * @author Pavel Železný <[email protected]>
     * @param string $communicationMethod
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setCommunicationMethod($communicationMethod)
    {
        if (in_array($communicationMethod, $this->getAvailableCommunicationMethod()) === FALSE)
        {
            throw new \UnexpectedValueException('Requested communication method is not from allowed one ( ' . implode(', ', $this->getAvailableCommunicationMethod()) . ' )');
        }
        $this->communicationMethod = $communicationMethod;
        return $this;
    }

    /**
     * Post data getter
     * @author Pavel Železný <[email protected]>
     * @return mixed
     */
    public function getData()
    {
        return $this->data;
    }

    /**
     * Post data setter
     * @author Pavel Železný <[email protected]>
     * @param mixed $data
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setData($data)
    {
        $this->data = $data;
        return $this;
    }

    /**
     * Endpoint getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * Endpoint setter
     * @author Pavel Železný <[email protected]>
     * @param string $endpoint
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setEndpoint($endpoint)
    {
        $this->endpoint = '/' . ltrim($endpoint, '/');
        return $this;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @return callable
     */
    public function getSerializeDataFunction()
    {
        return $this->serializeDataFunction;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @param callable $serializeDataFunction
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setSerializeDataFunction($serializeDataFunction)
    {
        if (is_callable($serializeDataFunction) === FALSE)
        {
            throw new \UnexpectedValueException('Serialize data function have to be callable');
        }
        $this->serializeDataFunction = $serializeDataFunction;
        return $this;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setUrl($url)
    {
        $this->url = $url;
        return $this;
    }

    /**
     * Available communication method getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getAvailableCommunicationMethod()
    {
        return array(
            self::DELETE,
            self::GET,
            self::POST,
            self::PUT,
        );
    }

    /**
     * Real request post fields data getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestPostFields()
    {
        return call_user_func($this->getSerializeDataFunction(), $this->getData());
    }

    /**
     * Real request url getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestUrl()
    {
        $url = ($this->getEndpoint() !== NULL) ? rtrim($this->getUrl(), '/') . $this->getEndpoint() : $this->getUrl();
        $urlArguments = http_build_query($this->getArguments());
        if ($urlArguments === '')
        {
            return $url;
        }
        elseif (strpos($url, '?') === FALSE)
        {
            return $url . '?' . $urlArguments;
        }
        else
        {
            return $url . '&' . $urlArguments;
        }
    }

    /**
     * Real request cURL options getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getRequestOptions()
    {
        return $this->getDefaultOptions() + $this->getOptions();
    }

    /**
     * Get default cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    protected function getDefaultOptions()
    {
        $options[CURLOPT_CONNECTTIMEOUT] = 30;
        $options[CURLOPT_CUSTOMREQUEST] = $this->getCommunicationMethod();
        $options[CURLOPT_POST] = $this->getCommunicationMethod() !== self::GET;
        $options[CURLOPT_RETURNTRANSFER] = TRUE;
        $options[CURLOPT_TIMEOUT] = 30;
        $options[CURLOPT_URL] = $this->getRequestUrl();

        /** Some clients are not happy, when they recieve POST data in GET request */
        if ($this->getCommunicationMethod() !== self::GET)
        {
            $options[CURLOPT_POSTFIELDS] = $this->getRequestPostFields();
        }
        return $options;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getRequestOptions());
        return $this;
    }

    /**
     * Serialize POST data
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    protected function serializeData()
    {
        return http_build_query($this->getData());
    }

}
    

V mnoha případech se hodí mít možnost kontrolovat nejen tělo odpovědi, ale i její hlavičku. Je to například jeden z mála způsobu, jak zjistit cílovou adresu požadavku, který směruje na stránku s HTTP přesměrováním. Aby bylo možné kontrolovat obsah hlavičky, je nutné před samotným odesláním požadavku předat příslušné nastavení CURL wrapperu.

<?php
//Curl.php

namespace Zeleznypa\Curl;

/**
 * cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class Curl extends \Zeleznypa\Curl\SimpleCurl
{

    const
            DELETE = 'DELETE',
            GET = 'GET',
            POST = 'POST',
            PUT = 'PUT';

    /** @var array $arguments */
    private $arguments = array();

    /** @var string $communicationMethod */
    private $communicationMethod = self::GET;

    /** @var mixed $data */
    private $data;

    /** @var string $endpoint */
    private $endpoint;

    /** @var array $info */
    private $info;

    /** @var callable $serializeDataFunction */
    private $serializeDataFunction;

    /** @var string $url */
    private $url;

    /**
     * Constructor
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return void
     */
    public function __construct($url = NULL)
    {
        parent::__construct($url);
        $this->setSerializeDataFunction(array($this, 'serializeData'));
    }

    /**
     * URL argument getter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param mixed $default
     * @return mixed URL argument value or $default if argument is not set.
     * @throws \BadMethodCallException
     */
    public function getArgument($argumentName, $default = NULL)
    {
        if (array_key_exists($argumentName, $this->arguments))
        {
            return $this->arguments[$argumentName];
        }
        else
        {
            if (func_num_args() < 2)
            {
                throw new \BadMethodCallException('Missing argument "' . $argumentName . '".');
            }
            return $default;
        }
    }

    /**
     * Url argument setter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param string|integer $argumentValue
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \BadMethodCallException
     */
    public function setArgument($argumentName, $argumentValue, $overwrite = FALSE)
    {
        if ((array_key_exists($argumentName, $this->arguments) === FALSE) || ($overwrite === FALSE))
        {
            $this->arguments[$argumentName] = $argumentValue;
            return $this;
        }
        else
        {
            throw new \BadMethodCallException('Argument "' . $argumentName . '" has already been set.');
        }
    }

    /**
     * URL arguments getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * URL arguments setter
     * @author Pavel Železný <[email protected]>
     * @param array $arguments
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setArguments(array $arguments, $overwrite = FALSE)
    {
        foreach ($arguments as $argumentName => $argumentValue)
        {
            $this->setArgument($argumentName, $argumentValue, $overwrite);
        }
        return $this;
    }

    /**
     * Communication method getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getCommunicationMethod()
    {
        return $this->communicationMethod;
    }

    /**
     * Communication method setter
     * @author Pavel Železný <[email protected]>
     * @param string $communicationMethod
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setCommunicationMethod($communicationMethod)
    {
        if (in_array($communicationMethod, $this->getAvailableCommunicationMethod()) === FALSE)
        {
            throw new \UnexpectedValueException('Requested communication method is not from allowed one ( ' . implode(', ', $this->getAvailableCommunicationMethod()) . ' )');
        }
        $this->communicationMethod = $communicationMethod;
        return $this;
    }

    /**
     * Post data getter
     * @author Pavel Železný <[email protected]>
     * @return mixed
     */
    public function getData()
    {
        return $this->data;
    }

    /**
     * Post data setter
     * @author Pavel Železný <[email protected]>
     * @param mixed $data
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setData($data)
    {
        $this->data = $data;
        return $this;
    }

    /**
     * Endpoint getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * Endpoint setter
     * @author Pavel Železný <[email protected]>
     * @param string $endpoint
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setEndpoint($endpoint)
    {
        $this->endpoint = '/' . ltrim($endpoint, '/');
        return $this;
    }

    /**
     * cURL response info getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getInfo()
    {
        return $this->info;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @return callable
     */
    public function getSerializeDataFunction()
    {
        return $this->serializeDataFunction;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @param callable $serializeDataFunction
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setSerializeDataFunction($serializeDataFunction)
    {
        if (is_callable($serializeDataFunction) === FALSE)
        {
            throw new \UnexpectedValueException('Serialize data function have to be callable');
        }
        $this->serializeDataFunction = $serializeDataFunction;
        return $this;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setUrl($url)
    {
        $this->url = $url;
        return $this;
    }

    /**
     * Available communication method getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getAvailableCommunicationMethod()
    {
        return array(
            self::DELETE,
            self::GET,
            self::POST,
            self::PUT,
        );
    }

    /**
     * Real request post fields data getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestPostFields()
    {
        return call_user_func($this->getSerializeDataFunction(), $this->getData());
    }

    /**
     * Real request url getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestUrl()
    {
        $url = ($this->getEndpoint() !== NULL) ? rtrim($this->getUrl(), '/') . $this->getEndpoint() : $this->getUrl();
        $urlArguments = http_build_query($this->getArguments());
        if ($urlArguments === '')
        {
            return $url;
        }
        elseif (strpos($url, '?') === FALSE)
        {
            return $url . '?' . $urlArguments;
        }
        else
        {
            return $url . '&' . $urlArguments;
        }
    }

    /**
     * Real request cURL options getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getRequestOptions()
    {
        return $this->getDefaultOptions() + $this->getOptions();
    }

    /**
     * Get default cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    protected function getDefaultOptions()
    {
        $options[CURLOPT_CONNECTTIMEOUT] = 30;
        $options[CURLOPT_CUSTOMREQUEST] = $this->getCommunicationMethod();
        $options[CURLOPT_POST] = $this->getCommunicationMethod() !== self::GET;
        $options[CURLOPT_RETURNTRANSFER] = TRUE;
        $options[CURLOPT_TIMEOUT] = 30;
        $options[CURLOPT_URL] = $this->getRequestUrl();

        /** Some clients are not happy, when they recieve POST data in GET request */
        if ($this->getCommunicationMethod() !== self::GET)
        {
            $options[CURLOPT_POSTFIELDS] = $this->getRequestPostFields();
        }
        return $options;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getRequestOptions());
        return $this;
    }

    /**
     * Process cURL response
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processResponse()
    {
        parent::processResponse();
        $this->info = curl_getinfo($this->getHandler());
        return $this;
    }

    /**
     * Serialize POST data
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    protected function serializeData()
    {
        return http_build_query($this->getData());
    }

}
    

Jelikož se komunikace nemusí odehrávat jen přes port 80, který slouží pro HTTP požadavky, bude vhodné přidat i možnost nastavení komunikačního portu.

<?php
//Curl.php

namespace Zeleznypa\Curl;

/**
 * cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class Curl extends \Zeleznypa\Curl\SimpleCurl
{

    const
            DELETE = 'DELETE',
            GET = 'GET',
            POST = 'POST',
            PUT = 'PUT';

    /** @var array $arguments */
    private $arguments = array();

    /** @var string $communicationMethod */
    private $communicationMethod = self::GET;

    /** @var mixed $data */
    private $data;

    /** @var string $endpoint */
    private $endpoint;

    /** @var array $info */
    private $info;

    /** @var integer $port */
    private $port = 80;

    /** @var callable $serializeDataFunction */
    private $serializeDataFunction;

    /** @var string $url */
    private $url;

    /**
     * Constructor
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return void
     */
    public function __construct($url = NULL)
    {
        parent::__construct($url);
        $this->setSerializeDataFunction(array($this, 'serializeData'));
    }

    /**
     * URL argument getter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param mixed $default
     * @return mixed URL argument value or $default if argument is not set.
     * @throws \BadMethodCallException
     */
    public function getArgument($argumentName, $default = NULL)
    {
        if (array_key_exists($argumentName, $this->arguments))
        {
            return $this->arguments[$argumentName];
        }
        else
        {
            if (func_num_args() < 2)
            {
                throw new \BadMethodCallException('Missing argument "' . $argumentName . '".');
            }
            return $default;
        }
    }

    /**
     * Url argument setter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param string|integer $argumentValue
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \BadMethodCallException
     */
    public function setArgument($argumentName, $argumentValue, $overwrite = FALSE)
    {
        if ((array_key_exists($argumentName, $this->arguments) === FALSE) || ($overwrite === FALSE))
        {
            $this->arguments[$argumentName] = $argumentValue;
            return $this;
        }
        else
        {
            throw new \BadMethodCallException('Argument "' . $argumentName . '" has already been set.');
        }
    }

    /**
     * URL arguments getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * URL arguments setter
     * @author Pavel Železný <[email protected]>
     * @param array $arguments
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setArguments(array $arguments, $overwrite = FALSE)
    {
        foreach ($arguments as $argumentName => $argumentValue)
        {
            $this->setArgument($argumentName, $argumentValue, $overwrite);
        }
        return $this;
    }

    /**
     * Communication method getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getCommunicationMethod()
    {
        return $this->communicationMethod;
    }

    /**
     * Communication method setter
     * @author Pavel Železný <[email protected]>
     * @param string $communicationMethod
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setCommunicationMethod($communicationMethod)
    {
        if (in_array($communicationMethod, $this->getAvailableCommunicationMethod()) === FALSE)
        {
            throw new \UnexpectedValueException('Requested communication method is not from allowed one ( ' . implode(', ', $this->getAvailableCommunicationMethod()) . ' )');
        }
        $this->communicationMethod = $communicationMethod;
        return $this;
    }

    /**
     * Post data getter
     * @author Pavel Železný <[email protected]>
     * @return mixed
     */
    public function getData()
    {
        return $this->data;
    }

    /**
     * Post data setter
     * @author Pavel Železný <[email protected]>
     * @param mixed $data
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setData($data)
    {
        $this->data = $data;
        return $this;
    }

    /**
     * Endpoint getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * Endpoint setter
     * @author Pavel Železný <[email protected]>
     * @param string $endpoint
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setEndpoint($endpoint)
    {
        $this->endpoint = '/' . ltrim($endpoint, '/');
        return $this;
    }

    /**
     * cURL response info getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getInfo()
    {
        return $this->info;
    }

    /**
     * Communication port getter
     * @author Pavel Železný <[email protected]>
     * @return integer
     */
    public function getPort()
    {
        return $this->port;
    }

    /**
     * Communication port setter
     * @author Pavel Železný <[email protected]>
     * @param integer $port
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setPort($port)
    {
        $this->port = $port;
        return $this;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @return callable
     */
    public function getSerializeDataFunction()
    {
        return $this->serializeDataFunction;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @param callable $serializeDataFunction
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setSerializeDataFunction($serializeDataFunction)
    {
        if (is_callable($serializeDataFunction) === FALSE)
        {
            throw new \UnexpectedValueException('Serialize data function have to be callable');
        }
        $this->serializeDataFunction = $serializeDataFunction;
        return $this;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setUrl($url)
    {
        $this->url = $url;
        return $this;
    }

    /**
     * Available communication method getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getAvailableCommunicationMethod()
    {
        return array(
            self::DELETE,
            self::GET,
            self::POST,
            self::PUT,
        );
    }

    /**
     * Real request post fields data getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestPostFields()
    {
        return call_user_func($this->getSerializeDataFunction(), $this->getData());
    }

    /**
     * Real request url getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestUrl()
    {
        $url = ($this->getEndpoint() !== NULL) ? rtrim($this->getUrl(), '/') . $this->getEndpoint() : $this->getUrl();
        $urlArguments = http_build_query($this->getArguments());
        if ($urlArguments === '')
        {
            return $url;
        }
        elseif (strpos($url, '?') === FALSE)
        {
            return $url . '?' . $urlArguments;
        }
        else
        {
            return $url . '&' . $urlArguments;
        }
    }

    /**
     * Real request cURL options getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getRequestOptions()
    {
        return $this->getDefaultOptions() + $this->getOptions();
    }

    /**
     * Get default cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    protected function getDefaultOptions()
    {
        $options[CURLOPT_CONNECTTIMEOUT] = 30;
        $options[CURLOPT_CUSTOMREQUEST] = $this->getCommunicationMethod();
        $options[CURLOPT_PORT] = $this->getPort();
        $options[CURLOPT_POST] = $this->getCommunicationMethod() !== self::GET;
        $options[CURLOPT_RETURNTRANSFER] = TRUE;
        $options[CURLOPT_TIMEOUT] = 30;
        $options[CURLOPT_URL] = $this->getRequestUrl();

        /** Some clients are not happy, when they recieve POST data in GET request */
        if ($this->getCommunicationMethod() !== self::GET)
        {
            $options[CURLOPT_POSTFIELDS] = $this->getRequestPostFields();
        }
        return $options;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getRequestOptions());
        return $this;
    }

    /**
     * Process cURL response
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processResponse()
    {
        parent::processResponse();
        $this->info = curl_getinfo($this->getHandler());
        return $this;
    }

    /**
     * Serialize POST data
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    protected function serializeData()
    {
        return http_build_query($this->getData());
    }

}
    

CURL požadavek může také skončit chybou. Za tímto účelem je vhodné umožnit uživateli wrapperu práci s chybovými zprávami.

<?php
//Curl.php

namespace Zeleznypa\Curl;

/**
 * cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class Curl extends \Zeleznypa\Curl\SimpleCurl
{

    const
            DELETE = 'DELETE',
            GET = 'GET',
            POST = 'POST',
            PUT = 'PUT';

    /** @var array $arguments */
    private $arguments = array();

    /** @var string $communicationMethod */
    private $communicationMethod = self::GET;

    /** @var mixed $data */
    private $data;

    /** @var string $endpoint */
    private $endpoint;

    /** @var FALSE | array $error */
    private $error = FALSE;

    /** @var array $info */
    private $info;

    /** @var integer $port */
    private $port = 80;

    /** @var callable $serializeDataFunction */
    private $serializeDataFunction;

    /** @var string $url */
    private $url;

    /**
     * Constructor
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return void
     */
    public function __construct($url = NULL)
    {
        parent::__construct($url);
        $this->setSerializeDataFunction(array($this, 'serializeData'));
    }

    /**
     * URL argument getter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param mixed $default
     * @return mixed URL argument value or $default if argument is not set.
     * @throws \BadMethodCallException
     */
    public function getArgument($argumentName, $default = NULL)
    {
        if (array_key_exists($argumentName, $this->arguments))
        {
            return $this->arguments[$argumentName];
        }
        else
        {
            if (func_num_args() < 2)
            {
                throw new \BadMethodCallException('Missing argument "' . $argumentName . '".');
            }
            return $default;
        }
    }

    /**
     * Url argument setter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param string|integer $argumentValue
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \BadMethodCallException
     */
    public function setArgument($argumentName, $argumentValue, $overwrite = FALSE)
    {
        if ((array_key_exists($argumentName, $this->arguments) === FALSE) || ($overwrite === FALSE))
        {
            $this->arguments[$argumentName] = $argumentValue;
            return $this;
        }
        else
        {
            throw new \BadMethodCallException('Argument "' . $argumentName . '" has already been set.');
        }
    }

    /**
     * URL arguments getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * URL arguments setter
     * @author Pavel Železný <[email protected]>
     * @param array $arguments
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setArguments(array $arguments, $overwrite = FALSE)
    {
        foreach ($arguments as $argumentName => $argumentValue)
        {
            $this->setArgument($argumentName, $argumentValue, $overwrite);
        }
        return $this;
    }

    /**
     * Communication method getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getCommunicationMethod()
    {
        return $this->communicationMethod;
    }

    /**
     * Communication method setter
     * @author Pavel Železný <[email protected]>
     * @param string $communicationMethod
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setCommunicationMethod($communicationMethod)
    {
        if (in_array($communicationMethod, $this->getAvailableCommunicationMethod()) === FALSE)
        {
            throw new \UnexpectedValueException('Requested communication method is not from allowed one ( ' . implode(', ', $this->getAvailableCommunicationMethod()) . ' )');
        }
        $this->communicationMethod = $communicationMethod;
        return $this;
    }

    /**
     * Post data getter
     * @author Pavel Železný <[email protected]>
     * @return mixed
     */
    public function getData()
    {
        return $this->data;
    }

    /**
     * Post data setter
     * @author Pavel Železný <[email protected]>
     * @param mixed $data
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setData($data)
    {
        $this->data = $data;
        return $this;
    }

    /**
     * Endpoint getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * Endpoint setter
     * @author Pavel Železný <[email protected]>
     * @param string $endpoint
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setEndpoint($endpoint)
    {
        $this->endpoint = '/' . ltrim($endpoint, '/');
        return $this;
    }

    /**
     * cURL error getter
     * @author Pavel Železný <[email protected]>
     * @return FALSE | array
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * cURL response info getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getInfo()
    {
        return $this->info;
    }

    /**
     * Communication port getter
     * @author Pavel Železný <[email protected]>
     * @return integer
     */
    public function getPort()
    {
        return $this->port;
    }

    /**
     * Communication port setter
     * @author Pavel Železný <[email protected]>
     * @param integer $port
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setPort($port)
    {
        $this->port = $port;
        return $this;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @return callable
     */
    public function getSerializeDataFunction()
    {
        return $this->serializeDataFunction;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @param callable $serializeDataFunction
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setSerializeDataFunction($serializeDataFunction)
    {
        if (is_callable($serializeDataFunction) === FALSE)
        {
            throw new \UnexpectedValueException('Serialize data function have to be callable');
        }
        $this->serializeDataFunction = $serializeDataFunction;
        return $this;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setUrl($url)
    {
        $this->url = $url;
        return $this;
    }

    /**
     * Available communication method getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getAvailableCommunicationMethod()
    {
        return array(
            self::DELETE,
            self::GET,
            self::POST,
            self::PUT,
        );
    }

    /**
     * Real request post fields data getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestPostFields()
    {
        return call_user_func($this->getSerializeDataFunction(), $this->getData());
    }

    /**
     * Real request url getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestUrl()
    {
        $url = ($this->getEndpoint() !== NULL) ? rtrim($this->getUrl(), '/') . $this->getEndpoint() : $this->getUrl();
        $urlArguments = http_build_query($this->getArguments());
        if ($urlArguments === '')
        {
            return $url;
        }
        elseif (strpos($url, '?') === FALSE)
        {
            return $url . '?' . $urlArguments;
        }
        else
        {
            return $url . '&' . $urlArguments;
        }
    }

    /**
     * Real request cURL options getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getRequestOptions()
    {
        return $this->getDefaultOptions() + $this->getOptions();
    }

    /**
     * Get default cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    protected function getDefaultOptions()
    {
        $options[CURLOPT_CONNECTTIMEOUT] = 30;
        $options[CURLOPT_CUSTOMREQUEST] = $this->getCommunicationMethod();
        $options[CURLOPT_PORT] = $this->getPort();
        $options[CURLOPT_POST] = $this->getCommunicationMethod() !== self::GET;
        $options[CURLOPT_RETURNTRANSFER] = TRUE;
        $options[CURLOPT_TIMEOUT] = 30;
        $options[CURLOPT_URL] = $this->getRequestUrl();

        /** Some clients are not happy, when they recieve POST data in GET request */
        if ($this->getCommunicationMethod() !== self::GET)
        {
            $options[CURLOPT_POSTFIELDS] = $this->getRequestPostFields();
        }
        return $options;
    }

    /**
     * Process cURL error
     * @author Pavel Železný <[email protected]>
     * @param integer $errorCode
     * @param string $errorMessage
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processError($errorCode, $errorMessage)
    {
        if ($errorCode != 0)
        {
            $this->error = array('code' => $errorCode, 'message' => $errorMessage);
        }
        return $this;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getRequestOptions());
        return $this;
    }

    /**
     * Process cURL response
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processResponse()
    {
        parent::processResponse();
        $this->info = curl_getinfo($this->getHandler());
        $this->processError(curl_errno($this->getHandler()), curl_error($this->getHandler()));
        return $this;
    }

    /**
     * Serialize POST data
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    protected function serializeData()
    {
        return http_build_query($this->getData());
    }

}
    

V některých případech je potřeba kontrolovat návratový kód. Některá API z důvodů ušetření objemu přenášených dat využívají HTTP stavy pro informování o úspěšnosti požadavku. Tento údaj je obsažen v poli inforcmací vrácených metodou getInfo(). Stačí ji tedy upravit tak, aby bylo možné si vyžádat jen konrétní prvek pole a přidat příslušnou metodu pro pohodlnější přístup.

<?php
//Curl.php

namespace Zeleznypa\Curl;

/**
 * cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class Curl extends \Zeleznypa\Curl\SimpleCurl
{

    const
            DELETE = 'DELETE',
            GET = 'GET',
            POST = 'POST',
            PUT = 'PUT';

    /** @var array $arguments */
    private $arguments = array();

    /** @var string $communicationMethod */
    private $communicationMethod = self::GET;

    /** @var mixed $data */
    private $data;

    /** @var string $endpoint */
    private $endpoint;

    /** @var FALSE | array $error */
    private $error = FALSE;

    /** @var array $info */
    private $info;

    /** @var integer $port */
    private $port = 80;

    /** @var callable $serializeDataFunction */
    private $serializeDataFunction;

    /** @var string $url */
    private $url;

    /**
     * Constructor
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return void
     */
    public function __construct($url = NULL)
    {
        parent::__construct($url);
        $this->setSerializeDataFunction(array($this, 'serializeData'));
    }

    /**
     * URL argument getter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param mixed $default
     * @return mixed URL argument value or $default if argument is not set.
     * @throws \BadMethodCallException
     */
    public function getArgument($argumentName, $default = NULL)
    {
        if (array_key_exists($argumentName, $this->arguments))
        {
            return $this->arguments[$argumentName];
        }
        else
        {
            if (func_num_args() < 2)
            {
                throw new \BadMethodCallException('Missing argument "' . $argumentName . '".');
            }
            return $default;
        }
    }

    /**
     * Url argument setter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param string|integer $argumentValue
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \BadMethodCallException
     */
    public function setArgument($argumentName, $argumentValue, $overwrite = FALSE)
    {
        if ((array_key_exists($argumentName, $this->arguments) === FALSE) || ($overwrite === FALSE))
        {
            $this->arguments[$argumentName] = $argumentValue;
            return $this;
        }
        else
        {
            throw new \BadMethodCallException('Argument "' . $argumentName . '" has already been set.');
        }
    }

    /**
     * URL arguments getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * URL arguments setter
     * @author Pavel Železný <[email protected]>
     * @param array $arguments
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setArguments(array $arguments, $overwrite = FALSE)
    {
        foreach ($arguments as $argumentName => $argumentValue)
        {
            $this->setArgument($argumentName, $argumentValue, $overwrite);
        }
        return $this;
    }

    /**
     * Communication method getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getCommunicationMethod()
    {
        return $this->communicationMethod;
    }

    /**
     * Communication method setter
     * @author Pavel Železný <[email protected]>
     * @param string $communicationMethod
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setCommunicationMethod($communicationMethod)
    {
        if (in_array($communicationMethod, $this->getAvailableCommunicationMethod()) === FALSE)
        {
            throw new \UnexpectedValueException('Requested communication method is not from allowed one ( ' . implode(', ', $this->getAvailableCommunicationMethod()) . ' )');
        }
        $this->communicationMethod = $communicationMethod;
        return $this;
    }

    /**
     * Post data getter
     * @author Pavel Železný <[email protected]>
     * @return mixed
     */
    public function getData()
    {
        return $this->data;
    }

    /**
     * Post data setter
     * @author Pavel Železný <[email protected]>
     * @param mixed $data
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setData($data)
    {
        $this->data = $data;
        return $this;
    }

    /**
     * Endpoint getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * Endpoint setter
     * @author Pavel Železný <[email protected]>
     * @param string $endpoint
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setEndpoint($endpoint)
    {
        $this->endpoint = '/' . ltrim($endpoint, '/');
        return $this;
    }

    /**
     * cURL error getter
     * @author Pavel Železný <[email protected]>
     * @return FALSE | array
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * cURL response info getter
     * @author Pavel Železný <[email protected]>
     * @param string $code index of information
     * @return array
     */
    public function getInfo($code = NULL)
    {
        return $code === NULL ? $this->info : (isset($this->info[$code]) ? $this->info[$code] : NULL);
    }

    /**
     * Communication port getter
     * @author Pavel Železný <[email protected]>
     * @return integer
     */
    public function getPort()
    {
        return $this->port;
    }

    /**
     * Communication port setter
     * @author Pavel Železný <[email protected]>
     * @param integer $port
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setPort($port)
    {
        $this->port = $port;
        return $this;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @return callable
     */
    public function getSerializeDataFunction()
    {
        return $this->serializeDataFunction;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @param callable $serializeDataFunction
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setSerializeDataFunction($serializeDataFunction)
    {
        if (is_callable($serializeDataFunction) === FALSE)
        {
            throw new \UnexpectedValueException('Serialize data function have to be callable');
        }
        $this->serializeDataFunction = $serializeDataFunction;
        return $this;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setUrl($url)
    {
        $this->url = $url;
        return $this;
    }

    /**
     * Available communication method getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getAvailableCommunicationMethod()
    {
        return array(
            self::DELETE,
            self::GET,
            self::POST,
            self::PUT,
        );
    }

    /**
     * Real request post fields data getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestPostFields()
    {
        return call_user_func($this->getSerializeDataFunction(), $this->getData());
    }

    /**
     * Real request url getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestUrl()
    {
        $url = ($this->getEndpoint() !== NULL) ? rtrim($this->getUrl(), '/') . $this->getEndpoint() : $this->getUrl();
        $urlArguments = http_build_query($this->getArguments());
        if ($urlArguments === '')
        {
            return $url;
        }
        elseif (strpos($url, '?') === FALSE)
        {
            return $url . '?' . $urlArguments;
        }
        else
        {
            return $url . '&' . $urlArguments;
        }
    }

    /**
     * Real request cURL options getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getRequestOptions()
    {
        return $this->getDefaultOptions() + $this->getOptions();
    }

    /**
     * Get cURL response http code
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getResponseCode()
    {
        return $this->getInfo('http_code');
    }

    /**
     * Get default cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    protected function getDefaultOptions()
    {
        $options[CURLOPT_CONNECTTIMEOUT] = 30;
        $options[CURLOPT_CUSTOMREQUEST] = $this->getCommunicationMethod();
        $options[CURLOPT_PORT] = $this->getPort();
        $options[CURLOPT_POST] = $this->getCommunicationMethod() !== self::GET;
        $options[CURLOPT_RETURNTRANSFER] = TRUE;
        $options[CURLOPT_TIMEOUT] = 30;
        $options[CURLOPT_URL] = $this->getRequestUrl();

        /** Some clients are not happy, when they recieve POST data in GET request */
        if ($this->getCommunicationMethod() !== self::GET)
        {
            $options[CURLOPT_POSTFIELDS] = $this->getRequestPostFields();
        }
        return $options;
    }

    /**
     * Process cURL error
     * @author Pavel Železný <[email protected]>
     * @param integer $errorCode
     * @param string $errorMessage
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processError($errorCode, $errorMessage)
    {
        if ($errorCode != 0)
        {
            $this->error = array('code' => $errorCode, 'message' => $errorMessage);
        }
        return $this;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getRequestOptions());
        return $this;
    }

    /**
     * Process cURL response
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processResponse()
    {
        parent::processResponse();
        $this->info = curl_getinfo($this->getHandler());
        $this->processError(curl_errno($this->getHandler()), curl_error($this->getHandler()));
        return $this;
    }

    /**
     * Serialize POST data
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    protected function serializeData()
    {
        return http_build_query($this->getData());
    }

}
    

CURL wrapper by rovněž mohl implementovat návrhový vzor „factory“ a usnadnit tak práci při volání právě jednoho požadavku uprostřed kódu. Navíc se díky tomuto přístupu umožní provést takzvané „mockování“ při tvorbě testovacích scénářů.

<?php
//Curl.php

namespace Zeleznypa\Curl;

/**
 * cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class Curl extends \Zeleznypa\Curl\SimpleCurl
{

    const
            DELETE = 'DELETE',
            GET = 'GET',
            POST = 'POST',
            PUT = 'PUT';

    /** @var array $arguments */
    private $arguments = array();

    /** @var string $communicationMethod */
    private $communicationMethod = self::GET;

    /** @var mixed $data */
    private $data;

    /** @var string $endpoint */
    private $endpoint;

    /** @var FALSE | array $error */
    private $error = FALSE;

    /** @var array $info */
    private $info;

    /** @var integer $port */
    private $port = 80;

    /** @var callable $serializeDataFunction */
    private $serializeDataFunction;

    /** @var string $url */
    private $url;

    /**
     * Constructor
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return void
     */
    public function __construct($url = NULL)
    {
        parent::__construct($url);
        $this->setSerializeDataFunction(array($this, 'serializeData'));
    }

    /**
     * Factory
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public static function create()
    {
        return new self;
    }

    /**
     * URL argument getter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param mixed $default
     * @return mixed URL argument value or $default if argument is not set.
     * @throws \BadMethodCallException
     */
    public function getArgument($argumentName, $default = NULL)
    {
        if (array_key_exists($argumentName, $this->arguments))
        {
            return $this->arguments[$argumentName];
        }
        else
        {
            if (func_num_args() < 2)
            {
                throw new \BadMethodCallException('Missing argument "' . $argumentName . '".');
            }
            return $default;
        }
    }

    /**
     * Url argument setter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param string|integer $argumentValue
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \BadMethodCallException
     */
    public function setArgument($argumentName, $argumentValue, $overwrite = FALSE)
    {
        if ((array_key_exists($argumentName, $this->arguments) === FALSE) || ($overwrite === FALSE))
        {
            $this->arguments[$argumentName] = $argumentValue;
            return $this;
        }
        else
        {
            throw new \BadMethodCallException('Argument "' . $argumentName . '" has already been set.');
        }
    }

    /**
     * URL arguments getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * URL arguments setter
     * @author Pavel Železný <[email protected]>
     * @param array $arguments
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setArguments(array $arguments, $overwrite = FALSE)
    {
        foreach ($arguments as $argumentName => $argumentValue)
        {
            $this->setArgument($argumentName, $argumentValue, $overwrite);
        }
        return $this;
    }

    /**
     * Communication method getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getCommunicationMethod()
    {
        return $this->communicationMethod;
    }

    /**
     * Communication method setter
     * @author Pavel Železný <[email protected]>
     * @param string $communicationMethod
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setCommunicationMethod($communicationMethod)
    {
        if (in_array($communicationMethod, $this->getAvailableCommunicationMethod()) === FALSE)
        {
            throw new \UnexpectedValueException('Requested communication method is not from allowed one ( ' . implode(', ', $this->getAvailableCommunicationMethod()) . ' )');
        }
        $this->communicationMethod = $communicationMethod;
        return $this;
    }

    /**
     * Post data getter
     * @author Pavel Železný <[email protected]>
     * @return mixed
     */
    public function getData()
    {
        return $this->data;
    }

    /**
     * Post data setter
     * @author Pavel Železný <[email protected]>
     * @param mixed $data
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setData($data)
    {
        $this->data = $data;
        return $this;
    }

    /**
     * Endpoint getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * Endpoint setter
     * @author Pavel Železný <[email protected]>
     * @param string $endpoint
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setEndpoint($endpoint)
    {
        $this->endpoint = '/' . ltrim($endpoint, '/');
        return $this;
    }

    /**
     * cURL error getter
     * @author Pavel Železný <[email protected]>
     * @return FALSE | array
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * cURL response info getter
     * @author Pavel Železný <[email protected]>
     * @param string $code index of information
     * @return array
     */
    public function getInfo($code = NULL)
    {
        return $code === NULL ? $this->info : (isset($this->info[$code]) ? $this->info[$code] : NULL);
    }

    /**
     * Communication port getter
     * @author Pavel Železný <[email protected]>
     * @return integer
     */
    public function getPort()
    {
        return $this->port;
    }

    /**
     * Communication port setter
     * @author Pavel Železný <[email protected]>
     * @param integer $port
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setPort($port)
    {
        $this->port = $port;
        return $this;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @return callable
     */
    public function getSerializeDataFunction()
    {
        return $this->serializeDataFunction;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @param callable $serializeDataFunction
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setSerializeDataFunction($serializeDataFunction)
    {
        if (is_callable($serializeDataFunction) === FALSE)
        {
            throw new \UnexpectedValueException('Serialize data function have to be callable');
        }
        $this->serializeDataFunction = $serializeDataFunction;
        return $this;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setUrl($url)
    {
        $this->url = $url;
        return $this;
    }

    /**
     * Available communication method getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getAvailableCommunicationMethod()
    {
        return array(
            self::DELETE,
            self::GET,
            self::POST,
            self::PUT,
        );
    }

    /**
     * Real request post fields data getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestPostFields()
    {
        return call_user_func($this->getSerializeDataFunction(), $this->getData());
    }

    /**
     * Real request url getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestUrl()
    {
        $url = ($this->getEndpoint() !== NULL) ? rtrim($this->getUrl(), '/') . $this->getEndpoint() : $this->getUrl();
        $urlArguments = http_build_query($this->getArguments());
        if ($urlArguments === '')
        {
            return $url;
        }
        elseif (strpos($url, '?') === FALSE)
        {
            return $url . '?' . $urlArguments;
        }
        else
        {
            return $url . '&' . $urlArguments;
        }
    }

    /**
     * Real request cURL options getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getRequestOptions()
    {
        return $this->getDefaultOptions() + $this->getOptions();
    }

    /**
     * Get cURL response http code
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getResponseCode()
    {
        return $this->getInfo('http_code');
    }

    /**
     * Get default cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    protected function getDefaultOptions()
    {
        $options[CURLOPT_CONNECTTIMEOUT] = 30;
        $options[CURLOPT_CUSTOMREQUEST] = $this->getCommunicationMethod();
        $options[CURLOPT_PORT] = $this->getPort();
        $options[CURLOPT_POST] = $this->getCommunicationMethod() !== self::GET;
        $options[CURLOPT_RETURNTRANSFER] = TRUE;
        $options[CURLOPT_TIMEOUT] = 30;
        $options[CURLOPT_URL] = $this->getRequestUrl();

        /** Some clients are not happy, when they recieve POST data in GET request */
        if ($this->getCommunicationMethod() !== self::GET)
        {
            $options[CURLOPT_POSTFIELDS] = $this->getRequestPostFields();
        }
        return $options;
    }

    /**
     * Process cURL error
     * @author Pavel Železný <[email protected]>
     * @param integer $errorCode
     * @param string $errorMessage
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processError($errorCode, $errorMessage)
    {
        if ($errorCode != 0)
        {
            $this->error = array('code' => $errorCode, 'message' => $errorMessage);
        }
        return $this;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getRequestOptions());
        return $this;
    }

    /**
     * Process cURL response
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processResponse()
    {
        parent::processResponse();
        $this->info = curl_getinfo($this->getHandler());
        $this->processError(curl_errno($this->getHandler()), curl_error($this->getHandler()));
        return $this;
    }

    /**
     * Serialize POST data
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    protected function serializeData()
    {
        return http_build_query($this->getData());
    }

}
    

Tento wrapper je použitelný v rámci architektury MVC tím, že je možné část (adresa serveru nebo časový limit pro připojení) nastavit již v konfiguraci, část (endpoint a předávaná data) nastavit v modelu a o výsledek si zažádat z presenteru. Pokud se však má wrapper použít v jednoduchém skriptu, hodí se mít možnost vše potřebné získat jednou metodou. Za tímto účelem stačí vytvořit pár metod, jejichž jméno bude odpovídat typu požadavku a v parametrech budou očekávat cílovou adresu, cURL parametry a případně data.

<?php
//Curl.php

namespace Zeleznypa\Curl;

/**
 * cURL wrapper
 * @author Pavel Železný <[email protected]>
 */
class Curl extends \Zeleznypa\Curl\SimpleCurl
{

    const
            DELETE = 'DELETE',
            GET = 'GET',
            POST = 'POST',
            PUT = 'PUT';

    /** @var array $arguments */
    private $arguments = array();

    /** @var string $communicationMethod */
    private $communicationMethod = self::GET;

    /** @var mixed $data */
    private $data;

    /** @var string $endpoint */
    private $endpoint;

    /** @var FALSE | array $error */
    private $error = FALSE;

    /** @var array $info */
    private $info;

    /** @var integer $port */
    private $port = 80;

    /** @var callable $serializeDataFunction */
    private $serializeDataFunction;

    /** @var string $url */
    private $url;

    /**
     * Constructor
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return void
     */
    public function __construct($url = NULL)
    {
        parent::__construct($url);
        $this->setSerializeDataFunction(array($this, 'serializeData'));
    }

    /**
     * Factory
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public static function create()
    {
        return new self;
    }

    /**
     * Simple way to call DELETE request
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @param mixed $data
     * @param array $options
     * @return tring|FALSE False on error
     */
    public static function delete($url, $data = NULL, $options = array())
    {
        return self::create()
        ->setCommunicationMethod(self::DELETE)
        ->setUrl($url)
        ->setData($data)
        ->setOptions($options)
        ->execute()
        ->getResult();
    }

    /**
     * Simple way to call GET request
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @param array $options Optional cURL options
     * @return string|FALSE False on error
     */
    public static function get($url, $options = array())
    {
        return self::create()
        ->setCommunicationMethod(self::GET)
        ->setUrl($url)
        ->setOptions($options)
        ->execute()
        ->getResult();
    }

    /**
     * Simple way to call POST request
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @param mixed $data
     * @param array $options
     * @return tring|FALSE False on error
     */
    public static function post($url, $data = NULL, $options = array())
    {
        return self::create()
        ->setCommunicationMethod(self::POST)
        ->setUrl($url)
        ->setData($data)
        ->setOptions($options)
        ->execute()
        ->getResult();
    }

    /**
     * Simple way to call PUT request
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @param mixed $data
     * @param array $options
     * @return tring|FALSE False on error
     */
    public static function put($url, $data = NULL, $options = array())
    {
        return self::create()
        ->setCommunicationMethod(self::PUT)
        ->setUrl($url)
        ->setData($data)
        ->setOptions($options)
        ->execute()
        ->getResult();
    }

    /**
     * URL argument getter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param mixed $default
     * @return mixed URL argument value or $default if argument is not set.
     * @throws \BadMethodCallException
     */
    public function getArgument($argumentName, $default = NULL)
    {
        if (array_key_exists($argumentName, $this->arguments))
        {
            return $this->arguments[$argumentName];
        }
        else
        {
            if (func_num_args() < 2)
            {
                throw new \BadMethodCallException('Missing argument "' . $argumentName . '".');
            }
            return $default;
        }
    }

    /**
     * Url argument setter
     * @author Pavel Železný <[email protected]>
     * @param string|integer $argumentName
     * @param string|integer $argumentValue
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \BadMethodCallException
     */
    public function setArgument($argumentName, $argumentValue, $overwrite = FALSE)
    {
        if ((array_key_exists($argumentName, $this->arguments) === FALSE) || ($overwrite === FALSE))
        {
            $this->arguments[$argumentName] = $argumentValue;
            return $this;
        }
        else
        {
            throw new \BadMethodCallException('Argument "' . $argumentName . '" has already been set.');
        }
    }

    /**
     * URL arguments getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * URL arguments setter
     * @author Pavel Železný <[email protected]>
     * @param array $arguments
     * @param boolean $overwrite Allow overwrite already defined argument
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setArguments(array $arguments, $overwrite = FALSE)
    {
        foreach ($arguments as $argumentName => $argumentValue)
        {
            $this->setArgument($argumentName, $argumentValue, $overwrite);
        }
        return $this;
    }

    /**
     * Communication method getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getCommunicationMethod()
    {
        return $this->communicationMethod;
    }

    /**
     * Communication method setter
     * @author Pavel Železný <[email protected]>
     * @param string $communicationMethod
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setCommunicationMethod($communicationMethod)
    {
        if (in_array($communicationMethod, $this->getAvailableCommunicationMethod()) === FALSE)
        {
            throw new \UnexpectedValueException('Requested communication method is not from allowed one ( ' . implode(', ', $this->getAvailableCommunicationMethod()) . ' )');
        }
        $this->communicationMethod = $communicationMethod;
        return $this;
    }

    /**
     * Post data getter
     * @author Pavel Železný <[email protected]>
     * @return mixed
     */
    public function getData()
    {
        return $this->data;
    }

    /**
     * Post data setter
     * @author Pavel Železný <[email protected]>
     * @param mixed $data
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setData($data)
    {
        $this->data = $data;
        return $this;
    }

    /**
     * Endpoint getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * Endpoint setter
     * @author Pavel Železný <[email protected]>
     * @param string $endpoint
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setEndpoint($endpoint)
    {
        $this->endpoint = '/' . ltrim($endpoint, '/');
        return $this;
    }

    /**
     * cURL error getter
     * @author Pavel Železný <[email protected]>
     * @return FALSE | array
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * cURL response info getter
     * @author Pavel Železný <[email protected]>
     * @param string $code index of information
     * @return array
     */
    public function getInfo($code = NULL)
    {
        return $code === NULL ? $this->info : (isset($this->info[$code]) ? $this->info[$code] : NULL);
    }

    /**
     * Communication port getter
     * @author Pavel Železný <[email protected]>
     * @return integer
     */
    public function getPort()
    {
        return $this->port;
    }

    /**
     * Communication port setter
     * @author Pavel Železný <[email protected]>
     * @param integer $port
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setPort($port)
    {
        $this->port = $port;
        return $this;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @return callable
     */
    public function getSerializeDataFunction()
    {
        return $this->serializeDataFunction;
    }

    /**
     * Serialize data function setter
     * @author Pavel Železný <[email protected]>
     * @param callable $serializeDataFunction
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     * @throws \UnexpectedValueException
     */
    public function setSerializeDataFunction($serializeDataFunction)
    {
        if (is_callable($serializeDataFunction) === FALSE)
        {
            throw new \UnexpectedValueException('Serialize data function have to be callable');
        }
        $this->serializeDataFunction = $serializeDataFunction;
        return $this;
    }

    /**
     * Get cURL destination address
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Simplier way to set url
     * @author Pavel Železný <[email protected]>
     * @param string $url
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    public function setUrl($url)
    {
        $this->url = $url;
        return $this;
    }

    /**
     * Available communication method getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getAvailableCommunicationMethod()
    {
        return array(
            self::DELETE,
            self::GET,
            self::POST,
            self::PUT,
        );
    }

    /**
     * Real request post fields data getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestPostFields()
    {
        return call_user_func($this->getSerializeDataFunction(), $this->getData());
    }

    /**
     * Real request url getter
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getRequestUrl()
    {
        $url = ($this->getEndpoint() !== NULL) ? rtrim($this->getUrl(), '/') . $this->getEndpoint() : $this->getUrl();
        $urlArguments = http_build_query($this->getArguments());
        if ($urlArguments === '')
        {
            return $url;
        }
        elseif (strpos($url, '?') === FALSE)
        {
            return $url . '?' . $urlArguments;
        }
        else
        {
            return $url . '&' . $urlArguments;
        }
    }

    /**
     * Real request cURL options getter
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    public function getRequestOptions()
    {
        return $this->getDefaultOptions() + $this->getOptions();
    }

    /**
     * Get cURL response http code
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    public function getResponseCode()
    {
        return $this->getInfo('http_code');
    }

    /**
     * Get default cURL options
     * @author Pavel Železný <[email protected]>
     * @return array
     */
    protected function getDefaultOptions()
    {
        $options[CURLOPT_CONNECTTIMEOUT] = 30;
        $options[CURLOPT_CUSTOMREQUEST] = $this->getCommunicationMethod();
        $options[CURLOPT_PORT] = $this->getPort();
        $options[CURLOPT_POST] = $this->getCommunicationMethod() !== self::GET;
        $options[CURLOPT_RETURNTRANSFER] = TRUE;
        $options[CURLOPT_TIMEOUT] = 30;
        $options[CURLOPT_URL] = $this->getRequestUrl();

        /** Some clients are not happy, when they recieve POST data in GET request */
        if ($this->getCommunicationMethod() !== self::GET)
        {
            $options[CURLOPT_POSTFIELDS] = $this->getRequestPostFields();
        }
        return $options;
    }

    /**
     * Process cURL error
     * @author Pavel Železný <[email protected]>
     * @param integer $errorCode
     * @param string $errorMessage
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processError($errorCode, $errorMessage)
    {
        if ($errorCode != 0)
        {
            $this->error = array('code' => $errorCode, 'message' => $errorMessage);
        }
        return $this;
    }

    /**
     * Process cURL options
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processOptions()
    {
        curl_setopt_array($this->getHandler(), $this->getRequestOptions());
        return $this;
    }

    /**
     * Process cURL response
     * @author Pavel Železný <[email protected]>
     * @return \Zeleznypa\Curl\Curl Provides fluent interface
     */
    protected function processResponse()
    {
        parent::processResponse();
        $this->info = curl_getinfo($this->getHandler());
        $this->processError(curl_errno($this->getHandler()), curl_error($this->getHandler()));
        return $this;
    }

    /**
     * Serialize POST data
     * @author Pavel Železný <[email protected]>
     * @return string
     */
    protected function serializeData()
    {
        return http_build_query($this->getData());
    }

}
    

Možností jak rozšířit CURL wrapper je nepřeberné množství. Výše popsaná a implementovaná řešení by měla pokrýt nejtypičtější užití wrapperu. Nic však nebrání tomu, aby se vytvořila vlastní třída, rozšiřující tento wrapper a přinášející další možnosti.