Guzzle6 Extra Tutorial
If you use this extra and like it, please consider donating. The suggested donation for this extra is $10.00, but any amount you want to give is fine (really). For a one-time donation of $50.00 you can use all of my non-premium extras with a clear conscience.
Guzzle 6
Guzzle is rapidly becoming the standard for reliably making and processing HTTP requests. This extra does nothing by itself. It simply installs the Guzzle 6 files and autoloader for use on your site without the hassle of rolling your own cUrl code. Guzzle will use cURL if it's installed on your site, or it will use PHP streams if not. Guzzle 6 also includes PSR7 capabilities, though I haven't used them.
I don't claim to be an expert at Guzzle. The examples below work, but I can't guarantee that they are best practices.
This tutorial provides some simple examples for using Guzzle to fetch web pages, check if a remote URL exists, or download files from a web site. Most of the code below is from the UpgradeMODX extra.
Installing Guzzle6
Go to System | Package Management on the main menu in the MODX Manager and click on the "Download Extras" button. That will take you to the Revolution Repository. Put Guzzle6 in the search box and press Enter. Click on the "Download" button, and once the package is downloaded, click on the "Back to Package Manager" button. That should bring you back to your Package Management grid. Click on the "Install" button next to Guzzle6 in the grid. The Guzzle6 package should now be installed.
Basic Usage
For any code that uses Guzzle, you should include some version of these lines at the very top of your code (just below the PHP tag):
use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; /* Include these if you need Psr7 support */ // use GuzzleHttp\Psr7; // use GuzzleHttp\Psr7\Request; /* This next line can go anywhere, as long as it's above any code that uses Guzzle */ require_once MODX_CORE_PATH . 'components/guzzle6/vendor/autoload.php';
Once you've included the lines above you can use any Guzzle 6 features. There are some examples below, but you can also consult the Guzzle Documentation.
Simplest Possible Request
This will get the content from a web page or URL and echo it to the screen:
$client = new \GuzzleHttp\Client(); $url = '//api.github.com/repos/modxcms/revolution/tags'; $response = $client->request('GET', $url); echo $response->getBody();
Important: The examples below are inside a class. They will need to be modified if you use them outside of a class.
Parsing Guzzle Errors
Before we get to the rest of the examples, this is a handy function I created for parsing messages from Guzzle exceptions:
/** * Creates beautified error message based on * exception thrown * * @param $e GuzzleHttp\Exception\RequestException * @param $verbose - Show the full, raw message * @return string - Error message based on Exception * */ public function parseException($e, $verbose = false) { /** @var $response \Psr\Http\Message\MessageInterface */ $msg = $e->getMessage(); $prefix = $this->modx->lexicon('ugm_no_version_list_from_github') . ' -- '; $retVal = $msg; // default to entire message; $code = $e->getCode(); if ($e->hasResponse()) { $response = $e->getResponse(); $rawMessage = $response->getBody(); $message = json_decode( (string) $rawMessage); if (empty($message)) { $message = $response->getReasonPhrase(); $retVal = $code . ' ' . $message; } else { $ex = (array) $message; $retVal = $code . ' ' . $ex['message']; } } elseif (empty($code) || ($code >= 500)) { $code = ((int) $code === 0) ? 'No Code Returned' : $code; $retVal = $code . ' ' . 'Connection error (no internet?)'; } $retVal = $verbose? $prefix . ' ' . $msg : $prefix . $retVal; return $retVal; }
Adding Some Options
This method is a little more complex, since it gets data from GitHub, and has options for the API response, authentication, exception handling, and SSL verification:
/** * * Gets raw JSON version list from GitHub * Long param list is mainly for unit tests * * @throws \GuzzleHttp\Exception\ClientException * @throws \GuzzleHttp\Exception\GuzzleException * @param $url string -- GitHub URL * @param $githubTimeout int * @param $verifyPeer bool * @param null $githubUsername string * @param null $githubToken string * @param null $certPath string -- optional path to SSL cert file * @param bool $verbose -- show verbose error messages * * * @return mixed returns JSON version list as string or false on failure */ public function getRawVersions($url, $githubTimeout = 6, $verifyPeer = true, $githubUsername = null, $githubToken = null, $certPath = null, $verbose = false) { /* Create the client */ $this->client = new \GuzzleHttp\Client(); $options = array(); /* User GitHub credentials if we have them if ((!empty($githubUsername)) && (!empty($githubToken))) { $options['auth'] = array($githubUsername, $githubToken); } $options['header'] = array ( 'Cache-Control' => 'no-cache', 'Accept' => 'application/json', ); /* Optional Path to .pem file for SSL Usually not necessary */ if (!empty ($certPath)) { $options['cert'] = $certPath; } $options['timeout'] = $githubTimeout; if ($verifyPeer !== true) { $options['verify'] = false; } try { $response = $this->client->request('GET', $url, $options); $retVal = $response->getBody(); } catch (RequestException $e) { $msg = $this->parseException($e, $verbose); $retVal = false; } catch (\Exception $e) { /** @var $e \GuzzleHttp\Exception\RequestException */ $msg = $this->parseException($e, $verbose); $retVal = false; } return $retVal; }
Rather than have so many arguments, the method above could easily use class variables set elsewhere in the class (say, in the initialization() method). The multiple arguments make the method's behavior easier to test with unit tests.
The $certPath
setting is rarely necessary. It allows you to tell Guzzle where the certification file (in .pem format) is located by giving it the full path to the file. Guzzle looks in several places for the file and is very good at finding the server's SSL certification file without the setting. If it can't be found, usually the server admin can put it in a more traditional place, but you can also tell Guzzle where it is with this setting.
See if a Remote URL exists
This makes a very simple request to verify that the URL can be reached without problems. It will report a variety of kinds of HTTP errors and failures. It returns true
if things went well. If not, it returns false
and writes the error to the MODX error log.
It's handy for use in a link checker. UpgradeMODX uses it to make sure the MODX .zip file is available before downloading it.
public function remoteFileExists($url) { /** @var $client GuzzleHttp\Client */ $client = $this->client; try { $client->head($url); return true; } catch (GuzzleHttp\Exception\ClientException $e) { $this->modx->log(modX::LOG_LEVEL_ERROR, 'Request Failed: ' . $this->parseException($e)); return false; } catch (/Exception $e) { $this->modx->log(modX::LOG_LEVEL_ERROR, 'Request Failed: ' . $this->parseException($e)); return false; } }
Notice that there is a second catch
block using to catch regular exceptions using catch (/Exception
) in case of 500-level errors, or a failed internet connection.
Downloading a File
This method downloads a .zip file from a URL. In this case the $options
argument is required, since it tells Guzzle where to put the downloaded file. The method also shows an example of catching an exception, then throwing a new one that can be caught in the method that calls this one. I also throws its own exception if the file if the downloaded file is empty.
/** @throws Exception * @throws GuzzleException * @param $url string - URL to download fro * @param $destinationPath - full path to save file */ public function download($url, $destinationPath) { /* See if the file is available for download */ if (!$this->remoteFileExists($url)) { throw new Exception($this->modx->lexicon('ugm_no_such_version')); } $client = $this->client; $destFile = fopen($destinationPath, 'w'); if (!$destFile) { $msg = '[Download Files Processor] ' . $this->modx->lexicon('ugm_could_not_open') . ' ' . $this->destinationPath . ' ' . $this->modx->lexicon('ugm_for_writing'); throw new Exception($msg); } set_time_limit(0); $options = array( /* where to put the body of a response */ RequestOptions::SINK => $destFile, RequestOptions::CONNECT_TIMEOUT => $this->modxTimeout, RequestOptions::VERIFY => (bool) $this->sslVerifyPeer, // RequestOptions::TIMEOUT => 0.0, ); if (!empty($this->certPath)) { $options[RequestOptions::CERT] = $this->certPath; } $options['headers'] = array( 'Cache-Control' => 'no-cache', 'Accept' => 'application/zip' ); try { $response = $client->request('GET', $url, $options); } catch (Exception $e) { fclose($destFile); unlink($destinationPath); throw new exception($this->modx->lexicon('ugm_download_failed') . ' -- ' . $e->getMessage()); } fclose($destFile); if (filesize($this->destinationPath) === 0) { throw new exception($this->modx->lexicon('ugm_download_failed') . ' Filesize: 0'); } else { $msg = $this->modx->lexicon('ugm_downloaded') . ' ' . $_SESSION['ugm_version'] . ' -> ' . $destinationPath; echo $msg; } }
The code above may throw an exception, so it should always be called inside a try
block with an associated catch
block.
My book, MODX: The Official Guide - Digital Edition is now available here. The paper version of the book may still be available from Amazon.
If you have the book and would like to download the code, you can find it here.
If you have the book and would like to see the updates and corrections page, you can find it here.
MODX: The Official Guide is 772 pages long and goes far beyond this web site in explaining beginning and advanced MODX techniques. It includes detailed information on:
- Installing MODX
- How MODX Works
- Working with MODX resources and Elements
- Using Git with MODX
- Using common MODX add-on components like SPForm, Login, getResources, and FormIt
- MODX security Permissions
- Customizing the MODX Manager
- Using Form Customization
- Creating Transport Packages
- MODX and xPDO object methods
- MODX System Events
- Using PHP with MODX
Go here for more information about the book.
Thank you for visiting BobsGuides.com
— Bob Ray