#StackBounty: #php #ajax #laravel #render #laravel-blade Render multiple blade view sections on ajax request

Bounty: 50

When request is ajax, i am rendering content section and inserting it to DOM. It is working as expected.

However.. i can’t find out the way, how to render multiple sections, like content and title and more in the same time.

Controller:

public function awesome(Request $request) {
   if($request->ajax()){
       return view('awesome')->renderSections()['content'];
   }
   return view('awesome');
}

Ajax and pushstate

var load = function (url) {
    $.get(url).done(function (data) {
      $("#content").html(data);
    })
};

$(document).on('click', 'a[data-request="push"]', function (e) {
    e.preventDefault();
    var $this = $(this),
    url = $this.attr("href"),
    title = $this.attr('title');

    history.pushState({
       url: url,
       title: title
    }, title, url);

    // document.title = title;

    load(url);
});

layouts.app

<title>@yield('title')</title>
<meta name="description" content="@yield('desc')"/>

<a data-request="push" title="AWESOME" href="<?= url('/'); ?>/awesome">Awesome</a>
<a data-request="push" title="RANDOM" href="<?= url('/'); ?>/random">Random</a>

@yield('content')

Blade:

@extends('layouts.app')

@section('title', 'Awesome')
@section('desc', 'About awesome')

@section('content')
  some text from awesome page
@endsection

Question:

How to render both or more of them in same time? Should i use an array or something else? Please give example or full explanation.

Thanks for any answers.


Get this bounty!!!

#StackBounty: #php #node.js #redis #publish-subscribe #autobahn Real time chat in PHP +Redis +Pub/Sub +WebSockets (+NodeJS)

Bounty: 50

I want to develop real time chat with channels and these are my needs:

  • PHP backend to manage site
  • Redis as session and data primary storage
  • Pub/Sub to send messages only to channel’s interested users
  • one WebSocket connection with which the messages will be send and received.
  • (optional) NodeJS to use great npm packages like timesync or socket.io

I see two different architectures to achieve this:

  • with Socket.io

    socket.io

  • with Crossbar.io

    crossbar.io

These are my questions:

  1. Which architecture I should choose and why?
  2. The key is the user id cannot be obtained from client, because it can be malformed. So in the first architecture I think on every socket message I should attach PHPSESSID value from cookie and on sever-side retrieve PHP session from Redis. Am I right or there is better way to get user id?
  3. I wonder if getting user id in second architecture can be done differently?

Edit:

I choosed Crossbar.io, cause it is very powerful and allows to communicate many different language applications in real time. After studying examples, I come up with this:

  • On every login user have generated secret key in database.
  • PHP client (Thruway) connect to Crossbar server and register custom WAMP-CRA authenticator

  • User’s browser connect to Crossbar server and is challenged. Secret and auth_id (user id) are loaded from DB with page load, so it
    can accomplish challenge and send response.

  • PHP authenticator search in DB for user with provided secret and id equal to auth_id. If there is, then it successfully authenticate
    session. Now we can trust that auth_id is real user id.

These are my question:

  1. How I can get auth_id on subscribe?
  2. I also added cookie authentication and browser is remembered after authentication. But when I look in Chrome DevTools there is any cookie nor value in local storage. Even after clearing cache my browser is still remember by Crossbar. I wonder how it is possible?


Get this bounty!!!

#StackBounty: #php #mysql Dynamically created multi select box values not inserted correctly

Bounty: 50

<table>
<tr>
    <td><select class="form-control selectpicker" data-live-search="true" name="reson[]" required="required">
            <option>--Select--</option>
            <option value="1">AAA</option>
            <option value="2">BBB</option>
            <option value="3">CCC</option>
            <option value="4">DDD</option>
            <option value="5">EEE</option>
        </select>
    </td>
    <td>
        <select class="form-control selectpicker" data-live-search="true" name="service[]" id="service" multiple="multiple">
            <option>--Select--</option>
            <option value="1">List 1</option>
            <option value="2">List 2</option>
            <option value="3">List 3</option>
            <option value="4">List 4</option>
            <option value="5">List 5</option>
            <option value="6">List 6</option>
        </select>
    </td>
    <td>
        <input type="text" class="form-control" name="name[]" placeholder="Name" />
    </td>
</tr>
<tr>
    <td><select class="form-control selectpicker" data-live-search="true" name="reson[]" required="required">
            <option>--Select--</option>
            <option value="1">AAA</option>
            <option value="2">BBB</option>
            <option value="3">CCC</option>
            <option value="4">DDD</option>
            <option value="5">EEE</option>
        </select>
    </td>
    <td>
        <select class="form-control selectpicker" data-live-search="true" name="service[]" id="service" multiple="multiple">
            <option>--Select--</option>
            <option value="1">List 1</option>
            <option value="2">List 2</option>
            <option value="3">List 3</option>
            <option value="4">List 4</option>
            <option value="5">List 5</option>
            <option value="6">List 6</option>
        </select>
    </td>
    <td>
        <input type="text" class="form-control" name="name[]" placeholder="Name" />
    </td>
</tr>
<tr>
    <td><select class="form-control selectpicker" data-live-search="true" name="reson[]" required="required">
            <option>--Select--</option>
            <option value="1">AAA</option>
            <option value="2">BBB</option>
            <option value="3">CCC</option>
            <option value="4">DDD</option>
            <option value="5">EEE</option>
        </select>
    </td>
    <td>
        <select class="form-control selectpicker" data-live-search="true" name="service[]" id="service" multiple="multiple">
            <option>--Select--</option>
            <option value="1">List 1</option>
            <option value="2">List 2</option>
            <option value="3">List 3</option>
            <option value="4">List 4</option>
            <option value="5">List 5</option>
            <option value="6">List 6</option>
        </select>
    </td>
    <td>
        <input type="text" class="form-control" name="name[]" placeholder="Name" />
    </td>
</tr>
</table>

This table rows are generated dynamicaly, its input field values are passed as an array and one of the select box is multiselect

Here is my php code

<?php
extract($_POST);
foreach ($reson as $id => $value) {
    $resona = ($reson[$id]);
    $namep = ($name[$id]);
    $rsid = $ob->insert_data('tbl_reson',array("reson" => $resona, "name" => $namep), true);
    foreach ($service as $ii => $valu) {
        $r_service = ($service[$ii]);
        $ob->insert_data('tbl_service',array("reson_id" => $rsid, "service" => $r_service));
    }
}
?>

Suppose here we have 3 rows and I select two multiple option from first row and three options from second row and four options from third row.

And when inserted into the DB, the selected options become same for all rows (All the options selected in multiselect are grouped into one array and saved into each field).

  • First table

    ------------------------------
        id  |   resn    |   name
    ------------------------------
        1   |   1       |   Test
        2   |   2       |   aaa
        3   |   3       |   bbb
    ------------------------------
    
  • Second Table

    --------------------------------    
        id  |   resnid  |   service
    --------------------------------
        1   |   1       |       1
        2   |   1       |       2
        3   |   2       |       3
        4   |   2       |       4
        5   |   2       |       5
        6   |   3       |       6
    
  • Second Table Current list

    -------------------------------- 
        id  |   resnid  |   service
    --------------------------------
        1   |   1       |       1
        2   |   1       |       2
        3   |   1       |       3
        4   |   1       |       4
        5   |   1       |       5
        6   |   1       |       6
        7   |   2       |       1
        8   |   2       |       2
        9   |   2       |       3
        10  |   2       |       4
        11  |   2       |       5
        12  |   2       |       6
    
  • etc…

But what I need is to insert reson[] and name[] in one table and service in another table based on last inserted id of the first table

Please Help to achieve this.


Get this bounty!!!

#StackBounty: #php #rest #curl #phpunit Curl-based REST client library (round 2)

Bounty: 100

This is a second iteration on an earlier review – cURL based REST client library

I have done some refactoring to split out the REST HTTP response into it’s own class from what was in the previous review and implement.

You might find it useful to read the library’s README file before performing this review for further background and usage examples, which I have omitted here for brevity and to allow the question to focus on the code.

I am only reviewing the single REST call portion of the library here.

I have also opened a separate review to cover the curl_multi_* based functionality for performing multiple requests in parallel.

RestClient class

<?php

namespace MikeBrantRestClientLib;

/**
 * @desc Class for executing RESTful service calls using a fluent interface.
 */
class RestClient
{
    /**
      * Flag to determine if basic authentication is to be used.
     * 
     * @var boolean
     */
    protected $useBasicAuth = false;

    /**
     * User Name for HTTP Basic Auth
     * 
     * @var string
     */
    protected $basicAuthUsername = null;

    /**
     * Password for HTTP Basic Auth
     *
     * @var string
     */
    protected $basicAuthPassword = null;

    /**
     * Flag to determine if SSL is used
     * 
     * @var boolean
     */
    protected $useSsl = false;

    /**
     * Flag to determine is we are to run in test mode where host's SSL cert is not verified
     * 
     * @var boolean
     */
    protected $useSslTestMode = false;

    /**
     * Integer value representing number of seconds to set for curl timeout option. Defaults to 30 seconds.
     * 
     * @var integer
     */
    protected $timeout = 30;

    /**
     * Variable to store remote host name
     * 
     * @var string
     */
    protected $remoteHost = null;

    /**
     * Variable to hold setting to determine if redirects are followed
     * 
     * @var boolean
     */
    protected $followRedirects = false;

    /**
     * Variable to hold value for maximum number of redirects to follow for cases when redirect are being followed.
     * Default value of 0 will allow for following of unlimited redirects.
     * 
     * @var integer
     */
    protected $maxRedirects = 0;

    /**
     * Variable which can hold a URI base for all actions
     * 
     * @var string
     */
    protected $uriBase = '/';

    /**
     * Stores curl handle
     *
     * @var mixed
     */
    private $curl = null;

    /**
     * Variable to store request URL that is formed before a request is made
     * 
     * @var string
     */
    private $requestUrl = null;

    /**
     * Array containing headers to be used for request
     * 
     * @var array
     */
    private $headers = array();

    /**
     * Variable to store the request header as sent
     * 
     * @var string
     */

    /**
     * Variable to store CurlHttpResponse result from curl call
     * 
     * @var CurlHttpResponse
     */
    private $response = null;

    /**
     * Constructor method. Currently there is no instantiation logic.
     *
     * @return void
     */
    public function __construct() {}

    /**
     * Method to execute GET on server
     * 
     * @param string $action
     * @return CurlHttpResponse
     * @throws InvalidArgumentException
     * @throws Exception
     */
    public function get($action) {
        $this->validateAction($action);
        $this->curlSetup();
        $this->setRequestUrl($action);
        curl_setopt($this->curl, CURLOPT_HTTPGET, true);
        // execute call. Can throw Exception.
        $this->curlExec();

        return $this->response;
    }

    /**
     * Method to exexute POST on server
     * 
     * @param mixed $action
     * @param mixed $data
     * @return CurlHttpResponse
     * @throws InvalidArgumentException
     * @throws Exception
     */
    public function post($action, $data) {
        $this->validateAction($action);
        $this->validateData($data);
        $this->curlSetup();
        $this->setRequestUrl($action);
        $this->setRequestData($data);
        curl_setopt($this->curl, CURLOPT_POST, true);
        // execute call. Can throw Exception.
        $this->curlExec();

        return $this->response;
    }

    /**
     * Method to execute PUT on server
     * 
     * @param string $action
     * @param mixed $data
     * @return CurlHttpResponse
     * @throws InvalidArgumentException
     * @throws Exception
     */
    public function put($action, $data) {
        $this->validateAction($action);
        $this->validateData($data);
        $this->curlSetup();
        $this->setRequestUrl($action);
        $this->setRequestData($data);
        curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'PUT');
        // execute call. Can throw Exception.
        $this->curlExec();

        return $this->response;
    }

    /**
     * Method to execute DELETE on server
     * 
     * @param string $action
     * @return CurlHttpResponse
     * @throws InvalidArgumentException
     * @throws Exception
     */
    public function delete($action) {
        $this->validateAction($action);
        $this->curlSetup();
        $this->setRequestUrl($action);
        curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
        // execute call. Can throw Exception.
        $this->curlExec();

        return $this->response;
    }

    /**
     * Method to execute HEAD on server
     * 
     * @param string $action
     * @return CurlHttpResponse
     * @throws InvalidArgumentException
     * @throws Exception
     */
    public function head($action) {
        $this->validateAction($action);
        $this->curlSetup();
        $this->setRequestUrl($action);
        curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
        curl_setopt($this->curl, CURLOPT_NOBODY, true);
        // execute call. Can throw Exception.
        $this->curlExec();

        return $this->response;
    }

    /**
     * Sets host name of remote server
     * 
     * @param string $host
     * @return RestClient
     * @throws InvalidArgumentException
     */
    public function setRemoteHost($host) {
        if(empty($host)) {
            throw new InvalidArgumentException('Host name not provided.');
        } else if(!is_string($host)) {
            throw new InvalidArgumentException('Non-string host name provided.');
        }

        // remove any http(s):// at beginning of host name
        $httpsPattern = '#https://#i';
        $httpPattern = '#http://#i';
        if (1 === preg_match($httpsPattern, $host)) {
            // this needs to be SSL request
            $this->setUseSsl(true);
            $host = str_ireplace('https://', '', $host);
        } else if (1 === preg_match($httpPattern, $host)) {
            $host = str_ireplace('http://', '', $host);
        }

        // remove trailing slash in host name
        $host = rtrim($host, '/');

        // look for common SSL port values in host name to see if SSL is needed
        $portPatterns = array(
            '/:443$/',
            '/:8443$/',
        );
        foreach ($portPatterns as $pattern) {
            if (1 === preg_match($pattern, $host)) {
                $this->setUseSsl(true);
            }
        }

        $this->remoteHost = $host;

        return $this;
    }

    /**
     * Sets URI base for the instance
     * 
     * @param string $uriBase
     * @return RestClient
     * @throws InvalidArgumentException
     */
    public function setUriBase($uriBase) {
        if(empty($uriBase)) {
            throw new InvalidArgumentException('URI base not provided.');
        } else if(!is_string($uriBase)) {
            throw new InvalidArgumentException('Non-string URI base provided.');
        }

        // make sure we always have forward slash at beginning and end of uriBase
        $uriBase = '/' . ltrim($uriBase, '/');
        $uriBase = rtrim($uriBase, '/') . '/';
        $this->uriBase = $uriBase;

        return $this;
    }

    /**
     * Sets whether SSL is to be used
     * 
     * @param boolean $value
     * @return RestClient
     * @throws InvalidArgumentException
     */
    public function setUseSsl($value) {
        if (!is_bool($value)) {
            throw new InvalidArgumentException('Non-boolean value passed as parameter.');
        }
        $this->useSsl = $value;

        return $this;
    }

    /**
     * Sets whether SSL Test Mode is to be used
     * 
     * @param boolean $value
     * @return RestClient
     * @throws InvalidArgumentException
     */
    public function setUseSslTestMode($value) {
        if (!is_bool($value)) {
            throw new InvalidArgumentException('Non-boolean value passed as parameter.');
        }
        $this->useSslTestMode = $value;

        return $this;
    }
    /**
     * Sets basic authentication credentials
     * 
     * @param string $user
     * @param string $password
     * @return RestClient
     * @throws InvalidArgumentException
     */
    public function setBasicAuthCredentials($user, $password) {
        if (empty($user)) {
            throw new InvalidArgumentException('User name not provided when trying to set basic authentication credentials.');
        }
        if (empty($password)) {
            throw new InvalidArgumentException('Password not provided when trying to set basic authentication credentials.');
        }

        $this->useBasicAuth = true;
        $this->basicAuthUsername = $user;
        $this->basicAuthPassword = $password;

        return $this;
    }

    /**
     * Sets HTTP headers from an associative array where key is header name and value is the header value
     * 
     * @param array $headers
     * @return RestClient
     */
    public function setHeaders(array $headers) {
        if(empty($headers)) {
            throw new InvalidArgumentException('Empty array passed when triyng to set headers');
        }
        $this->headers = $headers;

        return $this;  
    }

    /**
     * Sets maximum timeout for curl requests
     * 
     * @param integer $seconds
     * @return RestClient
     * @throws InvalidArgumentException
     */
    public function setTimeout($seconds) {
        if(!is_integer($seconds) || $seconds < 0) {
            throw new InvalidArgumentException('A non-negative integer value must be passed when trying to set timeout');
        }
        $this->timeout = $seconds;

        return $this;
    }

    /**
     * Sets flag on whether to follow 3XX redirects.
     * 
     * @param boolean $follow
     * @return RestClient
     * @throws InvalidArgumentException
     */
    public function setFollowRedirects($follow) {
        if(!is_bool($follow)) {
            throw new InvalidArgumentException('Non-boolean value passed as parameter.');
        }
        $this->followRedirects = $follow;

        return $this;
    }

    /**
     * Sets maximum number of redirects to follow. A value of 0 represents no redirect limit. Also sets followRedirects property to true .
     * 
     * @param integer $redirects
     * @return RestClient
     * @throws InvalidArgumentException
     */
    public function setMaxRedirects($redirects) {
        if(!is_integer($redirects) || $redirects < 0) {
            throw new InvalidArgumentException('A non-negative integer value must be passed when trying to set max redirects.');
        }
        $this->maxRedirects = $redirects;
        $this->setFollowRedirects(true);

        return $this;
    }

    /**
     * Get remote host setting
     * 
     * @return string
     */
    public function getRemoteHost() {
        return $this->remoteHost;
    }

    /**
     * Get URI Base setting
     * 
     * @return string
     */
    public function getUriBase() {
        return $this->uriBase;
    }

    /**
     * Get boolean setting indicating whether SSL is to be used
     * 
     * @return boolean
     */
    public function isUsingSsl() {
        return $this->useSsl;
    }

    /**
     * Get boolean setting indicating whether SSL test mode is enabled
     * 
     * @return boolean
     */
    public function isUsingSslTestMode() {
        return $this->useSslTestMode;
    }

    /**
     * Get timeout setting
     * 
     * @return integer
     */
    public function getTimeout() {
        return $this->timeout;
    }

    /**
     * Get follow redirects setting
     * 
     * @return boolean
     */
    public function isFollowingRedirects() {
        return $this->followRedirects;
    }

    /**
     * Get max redirects setting
     * 
     * @return integer
     */
    public function getMaxRedirects() {
        return $this->maxRedirects;
    }

    /**
     * Method to initialize curl handle in object
     * 
     * @return void
     * @throws Exception
     */
    private function curlSetup() {        
        // reset all request/response properties
        $this->resetRequestResponseProperties();

        // initialize curl. Throws Exception on failure.
        $this->curl = $this->curlInit();
    }

    /**
     * Method to initilize a curl handle
     * 
     * @return resource
     * @throws Exception
     */
    protected function curlInit() {
        // initialize curl
        $curl = curl_init();
        if($curl === false) {
            throw new Exception('curl failed to initialize.');
        }
        // set timeout
        curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout);

        // set basic HTTP authentication settings
        if (true === $this->useBasicAuth) {
            curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
            curl_setopt($curl, CURLOPT_USERPWD, $this->basicAuthUsername . ':' . $this->basicAuthPassword);
        }

        // set headers
        if (!empty($this->headers)) {
            $headers = array();
            foreach ($this->headers as $key=>$val) {
                $headers[] = $key . ': ' . $val;
            }
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        }

        // if not in production environment, we want to ignore SSL validation
        if (true === $this->useSsl && true === $this->useSslTestMode) {
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        }

        // set option to add request header information to curl_getinfo output
        curl_setopt($curl, CURLINFO_HEADER_OUT, true);

        // set option to return content body
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

        // set redirect options
        if (true === $this->followRedirects) {
            curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
            if ($this->maxRedirects > 0) {
                curl_setopt($curl, CURLOPT_MAXREDIRS, $this->maxRedirects);
            }
        }

        return $curl;
    }

    /**
     * Method to to teardown curl fixtures at end of request
     * 
     * @return void
     */
    private function curlTeardown() {
        $this->curlClose($this->curl);
        $this->curl = null;
    }

    /**
     * Method to close curl handle
     * 
     * @return void
     */
    protected function curlClose($curl) {
        curl_close($curl);
    }

    /**
     * Method to execute curl call
     * 
     * @return void
     * @throws Exception
     */
    private function curlExec() {
        $curlResult = curl_exec($this->curl);
        if($curlResult === false) {
            // our curl call failed for some reason
            $curlError = curl_error($this->curl);
            $this->curlTeardown();
            throw new Exception('curl call failed with message: "' . $curlError. '"');
        }

        // return CurlHttpResponse
        try {
            $this->response = new CurlHttpResponse($curlResult, curl_getinfo($this->curl));
        } catch (InvalidArgumentException $e) {
            throw new Exception(
                'Unable to instantiate CurlHttpResponse. Message: "' . $e->getMessage() . '"',
                $e->getCode(),
                $e
            );
        } finally {
            $this->curlTeardown();
        }
    }

    /**
     * Method to reset all properties specific to a particular request/response sequence.
     * 
     * @return void
     */
    protected function resetRequestResponseProperties() {
        $this->requestUrl = null;
        $this->response = null;
    }

    /**
     * Method to set the url on curl handle based on passed action
     * 
     * @param string $action
     * @return void
     */
    protected function setRequestUrl($action) {
        $url = $this->buildUrl($action);
        $this->requestUrl = $url;
        curl_setopt($this->curl, CURLOPT_URL, $url);
    }

    /**
     * Method to build URL based on class settings and passed action
     * 
     * @param string $action
     * @return string
     */
    protected function buildUrl($action) {
        $url = 'http://';
        if (true === $this->useSsl) {
            $url = 'https://';
        }
        $url = $url . $this->remoteHost . $this->uriBase . $action;
        return $url;
    }

    /**
     * Method to set data to be sent along with POST/PUT requests
     * 
     * @param mixed $data
     * @return void
     */
    protected function setRequestData($data) {
        $this->requestData = $data;
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $data);
    }

    /**
     * Method to provide common validation for action parameters
     * 
     * @param string $action
     * @return void
     * @throws InvalidArgumentException
     */
    protected function validateAction($action) {
        if(!is_string($action)) {
            throw new InvalidArgumentException('A non-string value was passed for action parameter');
        }
    }

    /**
     * Method to provide common validation for data parameters
     * 
     * @param mixed $data
     * @return void
     * @throws InvalidArgumentException
     */
    protected function validateData($data) {
        if(empty($data)) {
            throw new InvalidArgumentException('An empty value was passed for data parameter');
        }
    }
}

RestClient Unit Tests

<?php

namespace MikeBrantRestClientLib;

use PHPUnitFrameworkTestCase;

/**
 * Mock for curl_init global function
 * 
 * @return mixed
 */
function curl_init() {
    if (!is_null(RestClientTest::$curlInitResponse)) {
        return RestClientTest::$curlInitResponse;
    }
    return curl_init();
}

/**
 * Mock for curl_exec global function
 * 
 * @param resource curl handle
 * @return mixed
 */
function curl_exec($curl) {
    if (!is_null(RestClientTest::$curlExecResponse)) {
        return RestClientTest::$curlExecResponse;
    }
    return curl_exec($curl);
}

/**
 * Mock for curl_error global function
 * 
 * @param resource curl handle
 * @return mixed
 */
function curl_error($curl) {
    if (!is_null(RestClientTest::$curlErrorResponse)) {
        return RestClientTest::$curlErrorResponse;
    }
    return curl_error($curl);
}

/**
 * This is hacky workaround for avoiding double definition of this global method override
 * when running full test suite on this library.
 */
if(!function_exists('MikeBrantRestClientLibcurl_getinfo')) {

    /**
     * Mock for curl_getinfo function
     * 
     * @param resource curl handle
     * @return mixed
     */
    function curl_getinfo($curl) {
        $backtrace = debug_backtrace();
        $testClass = $backtrace[1]['class'] . 'Test';
        if (!is_null($testClass::$curlGetinfoResponse)) {
            return $testClass::$curlGetinfoResponse;
        }
        return curl_getinfo($curl);
    }
}

class RestClientTest extends TestCase
{
    public static $curlInitResponse = null;

    public static $curlExecResponse = null;

    public static $curlErrorResponse = null;

    public static $curlGetinfoResponse = null;

    protected $client = null;

    protected $curlExecMockResponse = 'Test Response';

    protected $curlGetinfoMockResponse = array(
        'url' => 'http://google.com/',
        'content_type' => 'text/html; charset=UTF-8',
        'http_code' => 200,
        'header_size' => 321,
        'request_size' => 49,
        'filetime' => -1,
        'ssl_verify_result' => 0,
        'redirect_count' => 0,
        'total_time' => 1.123264,
        'namelookup_time' => 1.045272,
        'connect_time' => 1.070183,
        'pretransfer_time' => 1.071139,
        'size_upload' => 0,
        'size_download' => 219,
        'speed_download' => 194,
        'speed_upload' => 0,
        'download_content_length' => 219,
        'upload_content_length' => -1,
        'starttransfer_time' => 1.122377,
        'redirect_time' => 0,
        'redirect_url' => 'http://www.google.com/',
        'primary_ip' => '216.58.194.142',
        'certinfo' => array(),
        'primary_port' => 80,
        'local_ip' => '192.168.1.74',
        'local_port' => 59733,
        'request_header' => "GET / HTTP/1.1nHost: google.comnAccept: */*",
    );

    protected function setUp() {
        self::$curlInitResponse = null;
        self::$curlExecResponse = null;
        self::$curlErrorResponse = null;
        self::$curlGetinfoResponse = null;
        $this->client = new RestClient();
    }

    protected function tearDown() {
        $this->client = null;
    }

    public function notStringProvider() {
        return array(
            array(null),
            array(new stdClass()),
            array(1),
            array(0),
            array(true),
            array(false),
            array(array())
        );
    }

    public function emptyProvider() {
        return array(
            array(null),
            array(''),
            array(0),
            array(0.0),
            array(false),
            array('0'),
            array(array())
        );
    }

    public function notStringAndEmptyProvider() {
        return array(
            array(null),
            array(''),
            array(new stdClass()),
            array(1),
            array(0),
            array(0.0),
            array('0'),
            array(true),
            array(false),
            array(array())
        );
    }

    public function hostProvider() {
        return array(
            array('somedomain.com', 'somedomain.com', false),
            array('somedomain.com/', 'somedomain.com', false),
            array('https://somedomain.com', 'somedomain.com', true),
            array('http://somedomain.com', 'somedomain.com', false),
            array('somedomain.com:80', 'somedomain.com:80', false),
            array('somedomain.com:443', 'somedomain.com:443', true),
            array('somedomain.com:8443', 'somedomain.com:8443', true)
        );
    }

    public function notBooleanProvider() {
        return array(
            array(null),
            array(''),
            array('string'),
            array('true'),
            array('false'),
            array(1),
            array(0),
            array('1'),
            array('0'),
            array(0.0),
            array(new stdClass()),
            array(array())
        );
    }

    public function uriBaseProvider() {
        return array(
            array('test', '/test/'),
            array('/test', '/test/'),
            array('test/', '/test/'),
            array('/test/', '/test/')
        );
    }

    public function notZeroOrPositiveIntegerProvider() {
        return array(
            array(-1),
            array(null),
            array(''),
            array(new stdClass()),
            array(1.0),
            array('1'),
            array(array())
        );
    }

    public function headersProvider() {
        return array(
            array(
                array(
                    'header1' => 'header1 value',
                    'header2' => 'header2 value'
                )
            )
        );
    }

    public function curlExecExceptionProvider() {
        return array(
            array(false, $this->curlGetinfoMockResponse),
            array('test', array())
        );
    }

    public function buildUrlProvider() {
        return array(
            array(true, 'google.com', 'base', 'action', 'https://google.com/base/action'),
            array(false, 'google.com', 'base', 'action', 'http://google.com/base/action')
        );
    }

    /**
     * @dataProvider notStringProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::validateAction
     */
    public function testValidateActionThrowsExceptions($action) {
        $this->client->get($action);
    }

    /**
     * @dataProvider emptyProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::validateData
     */
    public function testValidateDataThrowsExceptions($data) {
        $this->client->post('', $data);
    }

    /**
     * @dataProvider notStringAndEmptyProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::setRemoteHost
     */
    public function testSetRemoteHostThrowsExceptions($host) {
        $this->client->setRemoteHost($host);
    }

    /**
     * @dataProvider hostProvider
     * @covers MikeBrantRestClientLibRestClient::setRemoteHost
     * @covers MikeBrantRestClientLibRestClient::getRemoteHost
     */
    public function testSetRemoteHost($hostInput, $hostOutput, $useSslSet) {
        $this->client->setRemoteHost($hostInput);
        $this->assertEquals($hostOutput, $this->client->getRemoteHost());
        $this->assertEquals($useSslSet, $this->client->isUsingSsl());
    }

    /**
     * @dataProvider notStringAndEmptyProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::setUriBase
     */
    public function testSetUriBaseThrowsExceptions($string) {
        $this->client->setUriBase($string);
    }

    /**
     * @dataProvider uriBaseProvider
     * @covers MikeBrantRestClientLibRestClient::setUriBase
     * @covers MikeBrantRestClientLibRestClient::getUriBase
     */
    public function testSetUriBase($stringInput, $stringOutput) {
        $this->client->setUriBase($stringInput);
        $this->assertEquals($stringOutput, $this->client->getUriBase());
    }

    /**
     * @dataProvider notBooleanProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::setUseSsl
     */
    public function testSetUseSslThrowsExceptions($boolean) {
        $this->client->setUseSsl($boolean);
    }

    /**
     * @covers MikeBrantRestClientLibRestClient::setUseSsl
     * @covers MikeBrantRestClientLibRestClient::isUsingSsl
     */
    public function testSetUseSsl() {
        $this->client->setUseSsl(true);
        $this->assertTrue($this->client->isUsingSsl());
        $this->client->setUseSsl(false);
        $this->assertFalse($this->client->isUsingSsl());
    }

    /**
     * @dataProvider notBooleanProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::setUseSslTestMode
     */
    public function testSetUseSslTestModeThrowsExceptions($boolean) {
        $this->client->setUseSslTestMode($boolean);
    }

    /**
     * @covers MikeBrantRestClientLibRestClient::setUseSslTestMode
     * @covers MikeBrantRestClientLibRestClient::isUsingSslTestMode
     */
    public function testSetUseSslTestMode() {
        $this->client->setUseSslTestMode(true);
        $this->assertTrue($this->client->isUsingSslTestMode());
        $this->client->setUseSslTestMode(false);
        $this->assertFalse($this->client->isUsingSslTestMode());
    }

    /**
     * @dataProvider emptyProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::setBasicAuthCredentials
     */
    public function testSetBasicAuthCredentialsThrowsExceptionOnEmptyUser($user) {
        $this->client->setBasicAuthCredentials($user, 'password');
    }

    /**
     * @dataProvider emptyProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::setBasicAuthCredentials
     */
    public function testSetBasicAuthCredentialsThrowsExceptionOnEmptyPassword($password) {
        $this->client->setBasicAuthCredentials('user', $password);
    }

    /**
     * @covers MikeBrantRestClientLibRestClient::setBasicAuthCredentials
     */
    public function testSetBasicAuthCredentials() {
        $this->client->setBasicAuthCredentials('user', 'password');
        $this->assertAttributeEquals('user', 'basicAuthUsername', $this->client);
        $this->assertAttributeEquals('password', 'basicAuthPassword', $this->client);
        $this->assertAttributeEquals(true, 'useBasicAuth', $this->client);
    }

    /**
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::setHeaders
     */
    public function testSetHeadersThrowsExceptionOnEmptyArray() {
        $this->client->setHeaders(array());
    }

    /**
     * @dataProvider headersProvider
     * @covers MikeBrantRestClientLibRestClient::setHeaders
     */
    public function testSetHeaders($headers) {
        $this->client->setHeaders($headers);
        $this->assertAttributeEquals($headers, 'headers', $this->client);
    }

    /**
     * @dataProvider notZeroOrPositiveIntegerProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::setTimeout
     */
    public function testSetTimeoutThrowsExceptions($int) {
        $this->client->setTimeout($int);
    }

    /**
     * @covers MikeBrantRestClientLibRestClient::setTimeout
     * @covers MikeBrantRestClientLibRestClient::getTimeout
     */
    public function testSetTimeout() {
        $this->client->setTimeout(30);
        $this->assertEquals(30, $this->client->getTimeout());
        $this->client->setTimeout(0);
        $this->assertEquals(0, $this->client->getTimeout());
    }

    /**
     * @dataProvider notBooleanProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::setFollowRedirects
     */
    public function testSetFollowRedirectsThrowsExceptions($boolean) {
        $this->client->setFollowRedirects($boolean);
    }

    /**
     * @covers MikeBrantRestClientLibRestClient::setFollowRedirects
     * @covers MikeBrantRestClientLibRestClient::isFollowingRedirects
     */
    public function testSetFollowRedirects() {
        $this->client->setFollowRedirects(true);
        $this->assertTrue($this->client->isFollowingRedirects());
        $this->client->setFollowRedirects(false);
        $this->assertFalse($this->client->isFollowingRedirects());
    }

    /**
     * @dataProvider notZeroOrPositiveIntegerProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestClient::setMaxRedirects
     */
    public function testSetMaxRedirectsThrowsExceptions($int) {
        $this->client->setMaxRedirects($int);
    }

    /**
     * @covers MikeBrantRestClientLibRestClient::setMaxRedirects
     * @covers MikeBrantRestClientLibRestClient::getMaxRedirects
     */
    public function testSetMaxRedirects() {
        $this->client->setMaxRedirects(1);
        $this->assertEquals(1, $this->client->getMaxRedirects());
        $this->assertTrue($this->client->isFollowingRedirects());
        $this->client->setMaxRedirects(0);
        $this->assertEquals(0, $this->client->getMaxRedirects());
        $this->assertTrue($this->client->isFollowingRedirects());
    }

    /**
     * @expectedException Exception
     * @covers MikeBrantRestClientLibRestClient::curlInit
     */
    public function testCurlInitThrowsException() {
        self::$curlInitResponse = false;
        $this->client->get('action');
    }

    /**
     * @dataProvider curlExecExceptionProvider
     * @expectedException Exception
     * @covers MikeBrantRestClientLibRestClient::curlExec
     */
    public function testCurlExecThrowsException($response, $getinfo) {
        self::$curlExecResponse = $response;
        self::$curlErrorResponse = 'test error';
        self::$curlGetinfoResponse = $getinfo;
        $this->client->get('action');
    }

    /**
     * @dataProvider buildUrlProvider
     * @covers MikeBrantRestClientLibRestClient::get
     * @covers MikeBrantRestClientLibRestClient::validateAction
     * @covers MikeBrantRestClientLibRestClient::buildUrl
     * @covers MikeBrantRestClientLibRestClient::curlSetup
     * @covers MikeBrantRestClientLibRestClient::resetRequestResponseProperties
     * @covers MikeBrantRestClientLibRestClient::curlInit
     * @covers MikeBrantRestClientLibRestClient::setRequestUrl
     * @covers MikeBrantRestClientLibRestClient::curlExec
     * @covers MikeBrantRestClientLibRestClient::curlTeardown
     * @covers MikeBrantRestClientLibRestClient::curlClose
     */
    public function testGet($useSsl, $host, $uriBase, $action, $expectedUrl) {
        self::$curlExecResponse = $this->curlExecMockResponse;
        self::$curlGetinfoResponse = $this->curlGetinfoMockResponse;
        $this->client->setBasicAuthCredentials('user', 'password')
                     ->setHeaders(array('header' => 'header value'))
                     ->setUseSsl($useSsl)
                     ->setUseSslTestMode(true)
                     ->setFollowRedirects(true)
                     ->setMaxRedirects(1)
                     ->setremoteHost($host)
                     ->setUriBase($uriBase);
        $response = $this->client->get($action);
        $this->assertInstanceOf(CurlHttpResponse::class, $response);
        $this->assertAttributeEquals(null, 'curl', $this->client);
    }

    /**
     * @covers MikeBrantRestClientLibRestClient::post
     * @covers MikeBrantRestClientLibRestClient::validateData
     * @covers MikeBrantRestClientLibRestClient::setRequestData
     */
    public function testPost() {
        self::$curlExecResponse = $this->curlExecMockResponse;
        self::$curlGetinfoResponse = $this->curlGetinfoMockResponse;
        $response = $this->client->post('', 'test post data');
        $this->assertInstanceOf(CurlHttpResponse::class, $response);
        $this->assertAttributeEquals(null, 'curl', $this->client);
   }

    /**
     * @covers MikeBrantRestClientLibRestClient::put
     */
    public function testPut() {
        self::$curlExecResponse = $this->curlExecMockResponse;
        self::$curlGetinfoResponse = $this->curlGetinfoMockResponse;
        $response = $this->client->put('', 'test put data');
        $this->assertInstanceOf(CurlHttpResponse::class, $response);
        $this->assertAttributeEquals(null, 'curl', $this->client);
    }

    /**
     * @covers MikeBrantRestClientLibRestClient::delete
     */
    public function testDelete() {
        self::$curlExecResponse = $this->curlExecMockResponse;
        self::$curlGetinfoResponse = $this->curlGetinfoMockResponse;
        $response = $this->client->delete('');
        $this->assertInstanceOf(CurlHttpResponse::class, $response);
        $this->assertAttributeEquals(null, 'curl', $this->client);
    }

    /**
     * @covers MikeBrantRestClientLibRestClient::head
     */
    public function testHead() {
        self::$curlExecResponse = '';
        self::$curlGetinfoResponse = $this->curlGetinfoMockResponse;
        $response = $this->client->head('');
        $this->assertInstanceOf(CurlHttpResponse::class, $response);
        $this->assertAttributeEquals(null, 'curl', $this->client);
    }
}

CurlHttpResponse class

<?php

namespace MikeBrantRestClientLib;

/**
 * @desc Class representing HTTP response as returned from curl call.
 */
class CurlHttpResponse
{
    /**
     * Variable to store response body
     */
    protected $body = null;

    /**
     * Variable to store HTTP repsonse code
     * 
     * @var integer
     */
    protected $httpCode = null;

    /**
     * Variable to store response content type header
     * 
     * @var string
     */
    protected $contentType = null;

    /**
     * Variable to store URL used in request as reported via curl_getinfo().
     * 
     * @var string
     */
    protected $requestUrl = null;

    /**
     * Variable to store header used in request as reported via curl_getinfo().
     * 
     * @var string
     */
    protected $requestHeader = null;

    /**
     * Variable to store curl getinfo array.
     * See documentation at http://php.net/manual/en/function.curl-getinfo.php for expected array format.
     * 
     * @var array
     */
    protected $curlGetinfo = null;

    /**
     * Constructor method.
     * 
     * @param mixed $responseBody Response body as returned from a curl request.
     * @param array $curlGetinto Array returned form curl_getinfo() function call for request.
     * @return void
     * @throws InvalidArgumentException
     */
    public function __construct($responseBody, array $curlGetinfo) {
        $this->validateGetinfoArray($curlGetinfo);
        $this->body = $responseBody;
        $this->httpCode = $curlGetinfo['http_code'];
        $this->contentType = $curlGetinfo['content_type'];
        $this->requestUrl = $curlGetinfo['url'];
        $this->requestHeader = $curlGetinfo['request_header'];
        $this->curlGetinfo = $curlGetinfo;
    }

    /**
     * Returns response body for request
     * 
     * @return mixed
     */
    public function getBody() {
        return $this->body;
    }

    /**
     * Returns HTTP response code for request
     * 
     * @return integer
     */
    public function getHttpCode() {
        return $this->httpCode;
    }

    /**
     * Returns URL used in request as reported via curl_getinfo().
     * 
     * @return string
     */
    public function getRequestUrl() {
        return $this->requestUrl;
    }

    /**
     * Returns header used in request as reported via curl_getinfo().
     * 
     * @return string
     */
    public function getRequestHeader() {
        return $this->requestHeader;
    }

    /**
     * Returns curl getinfo array.
     * See documentation at http://php.net/manual/en/function.curl-getinfo.php for expected array format.
     * 
     * @return array
     */
    public function getCurlGetinfo() {
        return $this->curlGetinfo;
    }

    /**
     * Method to perform minimal validation of input array as having keys expected to be returned from
     * curl_getinfo().
     * 
     * @throws InvalidArgumentException
     */
    protected function validateGetinfoArray(array $getinfo) {
        if(empty($getinfo)) {
            throw new InvalidArgumentException('Empty array passed. Valid curl_getinfo() result array expected.');
        }
        if(!isset($getinfo['http_code']) || !is_integer($getinfo['http_code'])) {
            throw new InvalidArgumentException('curl_getinfo() response array expects integer value at http_code key.');
        }
        if(!isset($getinfo['content_type']) || !is_string($getinfo['content_type'])) {
            throw new InvalidArgumentException('curl_getinfo() response array expects string value at content_type key.');
        }
        if(!isset($getinfo['url']) || !is_string($getinfo['url'])) {
            throw new InvalidArgumentException('curl_getinfo() response array expects string value at url key.');
        }
        if(!isset($getinfo['request_header']) || !is_string($getinfo['request_header'])) {
            throw new InvalidArgumentException('curl_getinfo() response array expects string value at request_header key.');
        }
    }
}

CurlHttpResponse Unit Tests

<?php

namespace MikeBrantRestClientLib;

use PHPUnitFrameworkTestCase;

class CurlHttpResponseTest extends TestCase
{
    protected $curlExecMockResponse = 'Test Response';

    protected $curlGetinfoMockResponse = array(
        'url' => 'http://google.com/',
        'content_type' => 'text/html; charset=UTF-8',
        'http_code' => 200,
        'header_size' => 321,
        'request_size' => 49,
        'filetime' => -1,
        'ssl_verify_result' => 0,
        'redirect_count' => 0,
        'total_time' => 1.123264,
        'namelookup_time' => 1.045272,
        'connect_time' => 1.070183,
        'pretransfer_time' => 1.071139,
        'size_upload' => 0,
        'size_download' => 219,
        'speed_download' => 194,
        'speed_upload' => 0,
        'download_content_length' => 219,
        'upload_content_length' => -1,
        'starttransfer_time' => 1.122377,
        'redirect_time' => 0,
        'redirect_url' => 'http://www.google.com/',
        'primary_ip' => '216.58.194.142',
        'certinfo' => array(),
        'primary_port' => 80,
        'local_ip' => '192.168.1.74',
        'local_port' => 59733,
        'request_header' => "GET / HTTP/1.1nHost: google.comnAccept: */*",
    );

    public function invalidGetinfoProvider() {
        return array(
            array(
                array()
            ),
            array(
                array('no keys')
            ),
            array(
                array(
                    'http_code' => 'not integer'
                )
            ),
            array(
                array(
                    'http_code' => 200
                )
            ),
            array(
                array(
                   'http_code' => 200,
                   'content_type' => false
                )
            ),
            array(
                array(
                   'http_code' => 200,
                   'content_type' => 'text/html'
                )
            ),
            array(
                array(
                   'http_code' => 200,
                   'content_type' => 'text/html',
                   'url' => false
                )
            ),
            array(
                array(
                   'http_code' => 200,
                   'content_type' => 'text/html',
                   'url' => 'htttp://somedomain.com'
                )
            ),
            array(
                array(
                   'http_code' => 200,
                   'content_type' => 'text/html',
                   'url' => 'htttp://somedomain.com',
                   'request_header' => false
                )
            )
        );
    }

    /**
     * @dataProvider invalidGetinfoProvider
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibCurlHttpResponse::validateGetinfoArray
     */
    public function testValidateGetinfoArrayThrowsExceptions($getinfo) {
        $response = new CurlHttpResponse('test', $getinfo);
    }

    /**
     * @covers MikeBrantRestClientLibCurlHttpResponse::__construct
     * @covers MikeBrantRestClientLibCurlHttpResponse::validateGetinfoArray
     * @covers MikeBrantRestClientLibCurlHttpResponse::getBody
     * @covers MikeBrantRestClientLibCurlHttpResponse::getHttpCode
     * @covers MikeBrantRestClientLibCurlHttpResponse::getRequestUrl
     * @covers MikeBrantRestClientLibCurlHttpResponse::getRequestHeader
     * @covers MikeBrantRestClientLibCurlHttpResponse::getCurlGetinfo
     */
    public function testConstructor() {
        $response = new CurlHttpResponse($this->curlExecMockResponse, $this->curlGetinfoMockResponse);
        $this->assertEquals($this->curlExecMockResponse, $response->getBody());
        $this->assertEquals($this->curlGetinfoMockResponse['http_code'], $response->getHttpCode());
        $this->assertEquals($this->curlGetinfoMockResponse['url'], $response->getRequestUrl());
        $this->assertEquals($this->curlGetinfoMockResponse['request_header'], $response->getRequestHeader());
        $this->assertEquals($this->curlGetinfoMockResponse, $response->getCurlGetinfo());
    }
}


Get this bounty!!!

#StackBounty: #php #rest #curl #phpunit Curl-based REST Client Library (round 3)

Bounty: 100

This code review request relates to this code review which covers the basic single REST call use case in this this REST client library.

This code review covers the classes and unit tests for the multiple parallel REST call functionality provided by the library, which leverage the PHP cURL extension’s curl_multi_* functionality.

You might find it useful to read the library’s README file before performing this review for further background and usage examples, which I have omitted here for brevity and to allow the question to focus on the code.

RestMultiClient class

<?php

namespace MikeBrantRestClientLib;

/**
* @desc Class which extendd RestClient to provide curl_multi capabilities, allowing for multiple REST calls to be made in parallel.
*/
class RestMultiClient extends RestClient
{
    /**
     * Store array of curl handles for multi_exec
     * 
     * @var array
     */
    private $curlHandles = array();

    /**
     * Stores curl multi handle to which individual handles in curlHandles are added
     * 
     * @var mixed
     */
    private $curlMultiHandle = null;

    /**
     * Variable to store the maximum number of handles to be used for curl_multi_exec
     * 
     * @var integer
     */
    private $maxHandles = 10;

    /**
     * Variable to store an array of request headers sent in a multi_exec request
     * 
     * @var array
     */
    private $requestHeaders = array();

    /**
     * Variable to store an array of request data sent for multi_exec POST/PUT requests.
     * 
     * @var array
     */
    private $requestDataArray = array();

    /**
     * Variable to store CurlMultiHttpResponse object
     * 
     * @var CurlMultiHttpResponse
     */
    private $curlMultiHttpResponse = null;

    /**
     * Constructor method. Currently there is no instantiation logic.
     * 
     * @return void
     */
    public function __construct() {}

    /**
     * Method to perform multiple GET actions using curl_multi_exec.
     * 
     * @param array $actions
     * @param integer $maxHandles
     * @return RestMultiClient
     * @throws Exception
     * @throws InvalidArgumentException
     * @throws LengthException
     */
    public function get($actions) {
        $this->validateActionArray($actions);

        // set up curl handles
        $this->curlMultiSetup(count($actions));
        $this->setRequestUrls($actions);
        foreach($this->curlHandles as $curl) {
            curl_setopt($curl, CURLOPT_HTTPGET, true); // explicitly set the method to GET    
        }
        $this->curlMultiExec();

        return $this->curlMultiHttpResponse;
    }

    /**
     * Method to perform multiple POST actions using curl_multi_exec.
     * 
     * @param array $actions
     * @param array $data
     * @return RestMultiClient
     * @throws Exception
     * @throws InvalidArgumentException
     * @throws LengthException
     */
    public function post($actions, $data) {
        $this->validateActionArray($actions);
        $this->validateDataArray($data);
        // verify that the number of data elements matches the number of action elements
        if (count($actions) !== count($data)) {
            throw new LengthException('The number of actions requested does not match the number of data elements provided.'); 
        }

        // set up curl handles
        $this->curlMultiSetup(count($actions));
        $this->setRequestUrls($actions);
        $this->setRequestDataArray($data);
        foreach($this->curlHandles as $curl) {
            curl_setopt($curl, CURLOPT_POST, true); // explicitly set the method to POST 
        }
        $this->curlMultiExec();

        return $this->curlMultiHttpResponse;
    }

    /**
     * Method to perform multiple PUT actions using curl_multi_exec.
     * 
     * @param array $actions
     * @param array $data
     * @return RestMultiClient
     * @throws Exception
     * @throws InvalidArgumentException
     * @throws LengthException
     */
    public function put($actions, $data) {
        $this->validateActionArray($actions);
        $this->validateDataArray($data);
        // verify that the number of data elements matches the number of action elements
        if (count($actions) !== count($data)) {
            throw new LengthException('The number of actions requested does not match the number of data elements provided.'); 
        }

        // set up curl handles
        $this->curlMultiSetup(count($actions));
        $this->setRequestUrls($actions);
        $this->setRequestDataArray($data);
        foreach($this->curlHandles as $curl) {
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT'); // explicitly set the method to PUT 
        }
        $this->curlMultiExec();

        return $this->curlMultiHttpResponse;
    }

    /**
     * Method to perform multiple DELETE actions using curl_multi_exec.
     * 
     * @param array $actions
     * @param integer $maxHandles
     * @return RestMultiClient
     * @throws Exception
     * @throws InvalidArgumentException
     * @throws LengthException
     */
    public function delete($actions) {
        $this->validateActionArray($actions);

        // set up curl handles
        $this->curlMultiSetup(count($actions));
        $this->setRequestUrls($actions);
        foreach($this->curlHandles as $curl) {
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); // explicitly set the method to DELETE
        }
        $this->curlMultiExec();

        return $this->curlMultiHttpResponse;
    }

    /**
     * Method to perform multiple HEAD actions using curl_multi_exec.
     * 
     * @param array $actions
     * @return RestMultiClient
     * @throws Exception
     * @throws InvalidArgumentException
     * @throws LengthException
     */
    public function head($actions) {
        $this->validateActionArray($actions);

        // set up curl handles
        $this->curlMultiSetup(count($actions));
        $this->setRequestUrls($actions);
        foreach($this->curlHandles as $curl) {
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
            curl_setopt($curl, CURLOPT_NOBODY, true);
        }
        $this->curlMultiExec();

        return $this->curlMultiHttpResponse;
    }

    /**
     * Sets maximum number of handles that will be instantiated for curl_multi_exec calls
     * 
     * @param integer $maxHandles
     * @return RestMultiClient
     * @throws InvalidArgumentException
     */
    public function setMaxHandles($maxHandles) {
        if (!is_integer($maxHandles) || $maxHandles <= 0) {
            throw new InvalidArgumentException('A non-integer value was passed for max_handles parameter.');     
        }
        $this->maxHandles = $maxHandles;

        return $this->curlMultiHttpResponse;
    }

    /**
     * Getter for maxHandles setting
     * 
     * @return integer
     */
    public function getMaxHandles() {
        return $this->maxHandles;
    }

    /**
     * Method to set up a given number of curl handles for use with curl_multi_exec
     * 
     * @param integer $handlesNeeded
     * @return void
     * @throws Exception
     */
    private function curlMultiSetup($handlesNeeded) {
        $multiCurl = curl_multi_init();
        if($multiCurl === false) {
            throw new Exception('multi_curl handle failed to initialize.');
        }
        $this->curlMultiHandle = $multiCurl;

        for($i = 0; $i < $handlesNeeded; $i++) {
            $curl = $this->curlInit();
            $this->curlHandles[$i] = $curl;
            curl_multi_add_handle($this->curlMultiHandle, $curl);
        }
    }

    /**
     * Method to reset the curlMultiHandle and all individual curlHandles related to it.
     * 
     * @return void
     */
    private function curlMultiTeardown() {
        foreach ($this->curlHandles as $curl) {
            curl_multi_remove_handle($this->curlMultiHandle, $curl);
            $this->curlClose($curl);
        }
        curl_multi_close($this->curlMultiHandle);
        $this->curlHandles = array();
        $this->curlMultiHandle = null;
    }

    /**
     * Method to execute curl_multi call
     * 
     * @return void
     * @throws Exception
     */
    private function curlMultiExec() {
        // start multi_exec execution
        do {
            $status = curl_multi_exec($this->curlMultiHandle, $active);
        } while ($status === CURLM_CALL_MULTI_PERFORM || $active);

        // see if there are any errors on the multi_exec call as a whole
        if($status !== CURLM_OK) {
            throw new Exception('curl_multi_exec failed with status "' . $status . '"');
        }

        // process the results. Note there could be individual errors on specific calls
        $this->curlMultiHttpResponse = new CurlMultiHttpResponse();
        foreach($this->curlHandles as $i => $curl) {
            try {
                $response = new CurlHttpResponse(
                    curl_multi_getcontent($curl),
                    curl_getinfo($curl)
                );
            } catch (InvalidArgumentException $e) {
                $this->curlMultiTeardown();
                throw new Exception(
                   'Unable to instantiate CurlHttpResponse. Message: "' . $e->getMessage() . '"',
                   $e->getCode(),
                   $e
                );
            }
            $this->curlMultiHttpResponse->addResponse($response);
        }
        $this->curlMultiTeardown();
    }

    /**
     * Method to reset all properties specific to a particular request/response sequence.
     * 
     * @return void
     */
    protected function resetRequestResponseProperties() {
        $this->$curlMultiHttpResponse = null;
        $this->requestHeaders = array();
        $this->requestDataArray = array();
    }

    /**
     * Method to set the urls for  multi_exec action
     * 
     * @param array $actions
     * @return void
     */
    private function setRequestUrls(array $actions) {
        for ($i = 0; $i < count($actions); $i++) {
            $url = $this->buildUrl($actions[$i]);
            $this->requestUrls[$i] = $url;
            curl_setopt($this->curlHandles[$i], CURLOPT_URL, $url);
        }   
    }

    /**
     * Method to set array of data to be sent along with multi_exec POST/PUT requests
     * 
     * @param array $data
     * @return void
     */
    private function setRequestDataArray(array $data) {
        for ($i = 0; $i < count($data); $i++) {
            $data = $data[$i];
            $this->requestDataArray[$i] = $data;
            curl_setopt($this->curlHandles[$i], CURLOPT_POSTFIELDS, $data);
        }
    }

    /**
     * Method to provide common validation for action array parameters
     * 
     * @param array $actions
     * @return void
     * @throws InvalidArgumentException
     * @throws LengthException
     */
    private function validateActionArray(array $actions) {
        if(empty($actions)) {
            throw new InvalidArgumentException('An empty array was passed for actions parameter.');
        }
        if(count($actions) > $this->maxHandles) {
            throw new LengthException('Length of actions array exceeds maxHandles setting.');
        }
        foreach($actions as $action) {
            $this->validateAction($action);
        }
    }

    /**
     * Method to provide common validation for data array parameters
     * 
     * @param array $data
     * @return void
     * @throws InvalidArgumentException
     * @throws LengthException
     */
    private function validateDataArray(array $data) {
        if(empty($data)) {
            throw new InvalidArgumentException('An empty array was passed for data parameter');
        }
        if(count($data) > $this->maxHandles) {
            throw new LengthException('Length of data array exceeds maxHandles setting.');
        }
        foreach($data as $item) {
            $this->validateData($item);
        }
    }
}

RestMultiClient unit tests

<?php

namespace MikeBrantRestClientLib;

use PHPUnitFrameworkTestCase;

/**
 * Mock for curl_multi_init global function
 * 
 * @return mixed
 */
function curl_multi_init() {
    if (!is_null(RestMultiClientTest::$curlMultiInitResponse)) {
        return RestMultiClientTest::$curlMultiInitResponse;
    }
    return curl_multi_init();
}

/**
 * Mock for curl_multi_exec global function
 * 
 * @param resource curl_multi handle
 * @param integer flag indicating if there are still active handles.
 * @return integer
 */
function curl_multi_exec($multiCurl, &$active) {
    if (is_null(RestMultiClientTest::$curlMultiExecResponse)) {
        return curl_multi_exec($multiCurl, $active);
    }
    $active = 0;
    return RestMultiClientTest::$curlMultiExecResponse;
}

/**
 * Mock for curl_multi_getcontent global function
 * 
 * @param resource curl handle
 * @return string
 */
function curl_multi_getcontent($curl) {
    if (!is_null(RestMultiClientTest::$curlMultiGetcontentResponse)) {
        return RestMultiClientTest::$curlMultiGetcontentResponse;
    }
    return curl_multi_getcontent($curl);
}

/**
 * This is hacky workaround for avoiding double definition of this global method override
 * when running full test suite on this library.
 */
if(!function_exists('MikeBrantRestClientLibcurl_getinfo')) {

    /**
     * Mock for curl_getinfo function
     * 
     * @param resource curl handle
     * @return mixed
     */
    function curl_getinfo($curl) {
        $backtrace = debug_backtrace();
        $testClass = $backtrace[1]['class'] . 'Test';
        if (!is_null($testClass::$curlGetinfoResponse)) {
            return $testClass::$curlGetinfoResponse;
        }
        return curl_getinfo($curl);
    }
}

class RestMultiClientTest extends TestCase
{
    public static $curlMultiInitResponse = null;

    public static $curlMultiExecResponse = null;

    public static $curlMultiGetcontentResponse = null;

    public static $curlGetinfoResponse = null;

    protected $client = null;

    protected $curlMultiExecFailedResponse = CURLM_INTERNAL_ERROR;

    protected $curlMultiExecCompleteResponse = CURLM_OK;

    protected $curlGetinfoMockResponse = array(
        'url' => 'http://google.com/',
        'content_type' => 'text/html; charset=UTF-8',
        'http_code' => 200,
        'header_size' => 321,
        'request_size' => 49,
        'filetime' => -1,
        'ssl_verify_result' => 0,
        'redirect_count' => 0,
        'total_time' => 1.123264,
        'namelookup_time' => 1.045272,
        'connect_time' => 1.070183,
        'pretransfer_time' => 1.071139,
        'size_upload' => 0,
        'size_download' => 219,
        'speed_download' => 194,
        'speed_upload' => 0,
        'download_content_length' => 219,
        'upload_content_length' => -1,
        'starttransfer_time' => 1.122377,
        'redirect_time' => 0,
        'redirect_url' => 'http://www.google.com/',
        'primary_ip' => '216.58.194.142',
        'certinfo' => array(),
        'primary_port' => 80,
        'local_ip' => '192.168.1.74',
        'local_port' => 59733,
        'request_header' => "GET / HTTP/1.1nHost: google.comnAccept: */*",
    );

    protected function setUp() {
        self::$curlMultiInitResponse = null;
        self::$curlMultiExecResponse = null;
        self::$curlMultiGetcontentResponse = null;
        self::$curlGetinfoResponse = null;
        $this->client = new RestMultiClient();
    }

    protected function tearDown() {
        $this->client = null;
    }

    /**
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestMultiClient::validateActionArray
     */
    public function testValidateActionArrayThrowsExceptionOnEmptyArray() {
        $this->client->get(array());
    }

    /**
     * @expectedException LengthException
     * @covers MikeBrantRestClientLibRestMultiClient::validateActionArray
     */
    public function testValidateActionArrayThrowsExceptionOnOversizedArray() {
        $maxHandles = $this->client->getMaxHandles();
        $this->client->get(
            array_fill(0, $maxHandles + 1, 'action')
        );
    }

    /**
     * @expectedException InvalidArgumentException
     * @covers MikeBrantRestClientLibRestMultiClient::validateDataArray
     */
    public function testValidateDataArrayThrowsExceptionOnEmptyArray() {
        $this->client->get(array());
    }

    /**
     * @expectedException LengthException
     * @covers MikeBrantRestClientLibRestMultiClient::validateDataArray
     */
    public function testValidateDataArrayThrowsExceptionOnOversizedArray() {
        $maxHandles = $this->client->getMaxHandles();
        $this->client->post(
            array_fill(0, $maxHandles, 'action'),
            array_fill(0, $maxHandles + 1, 'data')
        );
    }

    /**
     * @expectedException Exception
     * @covers MikeBrantRestClientLibRestMultiClient::curlMultiSetup
     */
    public function testCurlMultiSetupThrowsExceptionOnCurlMultiInitFailure() {
        self::$curlMultiInitResponse = false;
        $this->client->get(
            array_fill(0, 2, 'action')
        );
    }
    /**
     * @expectedException Exception
     * @covers MikeBrantRestClientLibRestMultiClient::curlMultiExec
     */
    public function testCurlMultiExecThrowsExceptionOnMultiCurlFailure() {
        self::$curlMultiExecResponse = $this->curlMultiExecFailedResponse;
        $this->client->get(
            array_fill(0, 2, 'action')
        );
    }

    /**
     * @expectedException Exception
     * @covers MikeBrantRestClientLibRestMultiClient::curlMultiExec
     */
    public function testCurlMultiExecThrowsExceptionOnMalformedCurlHttpResponse() {
        self::$curlMultiExecResponse = $this->curlMultiExecCompleteResponse;
        self::$curlMultiGetcontentResponse = 'test';
        self::$curlGetinfoResponse = array();
        $this->client->get(
            array_fill(0, 2, 'action')
        );
    }
    /**
     * @covers MikeBrantRestClientLibRestMultiClient::get
     * @covers MikeBrantRestClientLibRestMultiClient::validateActionArray
     * @covers MikeBrantRestClientLibRestMultiClient::curlMultiSetup
     * @covers MikeBrantRestClientLibRestMultiClient::resetRequestResponseProperties
     * @covers MikeBrantRestClientLibRestMultiClient::setRequestUrls
     * @covers MikeBrantRestClientLibRestMultiClient::curlMultiExec
     * @covers MikeBrantRestClientLibRestMultiClient::curlMultiTeardown
     */
    public function testGet() {
        self::$curlMultiExecResponse = $this->curlMultiExecCompleteResponse;
        self::$curlMultiGetcontentResponse = 'test';
        self::$curlGetinfoResponse = $this->curlGetinfoMockResponse;
        $response = $this->client->get(
            array_fill(0, 2, 'action')
        );
        $this->assertInstanceOf(CurlMultiHttpResponse::class, $response);
        $this->assertAttributeEquals(null, 'curlMultiHandle', $this->client);
    }

    /**
     * @expectedException LengthException
     * @covers MikeBrantRestClientLibRestMultiClient::post
     */
    public function testPostThrowsExceptionOnArraySizeMismatch() {
        $maxHandles = $this->client->getMaxHandles();
        $this->client->post(
            array_fill(0, $maxHandles, 'action'),
            array_fill(0, $maxHandles - 1, 'data')
        );
    }

    /**
     * @covers MikeBrantRestClientLibRestMultiClient::post
     * @covers MikeBrantRestClientLibRestMultiClient::validateData
     * @covers MikeBrantRestClientLibRestMultiClient::setRequestData
     */
    public function testPost() {
        self::$curlMultiExecResponse = $this->curlMultiExecCompleteResponse;
        self::$curlMultiGetcontentResponse = 'test';
        self::$curlGetinfoResponse = $this->curlGetinfoMockResponse;
        $response = $this->client->post(
            array_fill(0, 2, 'action'),
            array_fill(0, 2, 'data')
        );
        $this->assertInstanceOf(CurlMultiHttpResponse::class, $response);
        $this->assertAttributeEquals(null, 'curlMultiHandle', $this->client);
   }

    /**
     * @expectedException LengthException
     * @covers MikeBrantRestClientLibRestMultiClient::put
     */
    public function testPutThrowsExceptionOnArraySizeMismatch() {
        $maxHandles = $this->client->getMaxHandles();
        $this->client->put(
            array_fill(0, $maxHandles, 'action'),
            array_fill(0, $maxHandles - 1, 'data')
        );
    }

    /**
     * @covers MikeBrantRestClientLibRestMultiClient::put
     */
    public function testPut() {
        self::$curlMultiExecResponse = $this->curlMultiExecCompleteResponse;
        self::$curlMultiGetcontentResponse = 'test';
        self::$curlGetinfoResponse = $this->curlGetinfoMockResponse;
        $response = $this->client->put(
            array_fill(0, 2, 'action'),
            array_fill(0, 2, 'data')
        );
        $this->assertInstanceOf(CurlMultiHttpResponse::class, $response);
        $this->assertAttributeEquals(null, 'curlMultiHandle', $this->client);
    }

    /**
     * @covers MikeBrantRestClientLibRestMultiClient::delete
     */
    public function testDelete() {
        self::$curlMultiExecResponse = $this->curlMultiExecCompleteResponse;
        self::$curlMultiGetcontentResponse = 'test';
        self::$curlGetinfoResponse = $this->curlGetinfoMockResponse;
        $response = $this->client->delete(
            array_fill(0, 2, 'action')
        );
        $this->assertInstanceOf(CurlMultiHttpResponse::class, $response);
        $this->assertAttributeEquals(null, 'curlMultiHandle', $this->client);
    }

    /**
     * @covers MikeBrantRestClientLibRestMultiClient::head
     */
    public function testHead() {
        self::$curlMultiExecResponse = $this->curlMultiExecCompleteResponse;
        self::$curlMultiGetcontentResponse = 'test';
        self::$curlGetinfoResponse = $this->curlGetinfoMockResponse;
        $response = $this->client->head(
            array_fill(0, 2, 'action')
        );
        $this->assertInstanceOf(CurlMultiHttpResponse::class, $response);
        $this->assertAttributeEquals(null, 'curlMultiHandle', $this->client);
    }
}

CurlMultiHttpResponse class

<?php

namespace MikeBrantRestClientLib;

class CurlMultiHttpResponse
{
    /**
     * Variable to store individual CurlHttpResponse objects from curl_multi call
     * 
     * @var array
     */
    protected $curlHttpResponses = array();

    /**
     * Constructor method. Currently there is no instantiation logic.
     */
    public function __construct() {}

    /**
     * Method to add CurlHttpResponse object to collection
     * 
     * @param CurlHttpResponse $response
     * @return void
     */
    public function addResponse(CurlHttpResponse $response) {
        $this->curlHttpResponses[] = $response;
    }

    /**
     * Returns array of all CurlHttpResponse objects in collection.
     * 
     * @return array
     */
    public function getCurlHttpResponses() {
        return $this->curlHttpResponses;
    }

    /**
     * Alias for getCurlHttpResponses
     * 
     * @return array
     */
    public function getAll() {
        return $this->getCurlHttpResponses();
    }

    /**
     * Returns array of response bodies for each response in collection.
     * 
     * @return array
     */
    public function getResponseBodies() {
        return array_map(
            function(CurlHttpResponse $value) {
                return $value->getBody();
            },
            $this->curlHttpResponses
        );
    }

    /**
     * Returns array of response codes for each response in collection.
     * 
     * @return array
     */
    public function getHttpCodes() {
        return array_map(
            function(CurlHttpResponse $value) {
                return $value->getHttpCode();
            },
            $this->curlHttpResponses
        );
    }

    /**
     * Returns array of URL's used for each response in collectoin as returned via curl_getinfo.
     * 
     * @return array
     */
    public function getRequestUrls() {
        return array_map(
            function(CurlHttpResponse $value) {
                return $value->getRequestUrl();
            },
            $this->curlHttpResponses
        );
    }

    /**
     * Returns array of request headers for each response in collection as returned via curl_getinfo.
     * 
     * @return array
     */
    public function getRequestHeaders() {
        return array_map(
            function(CurlHttpResponse $value) {
                return $value->getRequestHeader();
            },
            $this->curlHttpResponses
        );
    }

    /**
     * Returns array of curl_getinfo arrays for each response in collection.
     * See documentation at http://php.net/manual/en/function.curl-getinfo.php for expected format for each array element.
     * 
     * @return array
     */
    public function getCurlGetinfoArrays() {
        return array_map(
            function(CurlHttpResponse $value) {
                return $value->getCurlGetinfo();
            },
            $this->curlHttpResponses
        );
    }
}

CurlMultiHttpResponse unit tests

<?php

namespace MikeBrantRestClientLib;

use PHPUnitFrameworkTestCase;

class CurlMultiHttpResponseTest extends TestCase
{
    protected $curlMultiHttpResponse = null;

    protected $curlExecMockResponse = 'Test Response';

    protected $curlGetinfoMockResponse = array(
        'url' => 'http://google.com/',
        'content_type' => 'text/html; charset=UTF-8',
        'http_code' => 200,
        'header_size' => 321,
        'request_size' => 49,
        'filetime' => -1,
        'ssl_verify_result' => 0,
        'redirect_count' => 0,
        'total_time' => 1.123264,
        'namelookup_time' => 1.045272,
        'connect_time' => 1.070183,
        'pretransfer_time' => 1.071139,
        'size_upload' => 0,
        'size_download' => 219,
        'speed_download' => 194,
        'speed_upload' => 0,
        'download_content_length' => 219,
        'upload_content_length' => -1,
        'starttransfer_time' => 1.122377,
        'redirect_time' => 0,
        'redirect_url' => 'http://www.google.com/',
        'primary_ip' => '216.58.194.142',
        'certinfo' => array(),
        'primary_port' => 80,
        'local_ip' => '192.168.1.74',
        'local_port' => 59733,
        'request_header' => "GET / HTTP/1.1nHost: google.comnAccept: */*",
    );

    protected function setUp() {
        $this->curlMultiHttpResponse = new CurlMultiHttpResponse();
    }

    public function curlHttpResponseProvider() {
        return array(
            array(
                new CurlHttpResponse($this->curlExecMockResponse, $this->curlGetinfoMockResponse)
            )
        );
    }

    /**
     * @dataProvider curlHttpResponseProvider
     * @covers MikeBrantRestClientLibCurlMultiHttpResponse::addResponse
     * @covers MikeBrantRestClientLibCurlMultiHttpResponse::getCurlHttpResponses
     * @covers MikeBrantRestClientLibCurlMultiHttpResponse::getAll
     */
    public function testAddResponse($curlHttpResponse) {
        $responseArray = array_fill(0, 5, $curlHttpResponse);
        for ($i = 0; $i < count($responseArray); $i++) {
            $this->curlMultiHttpResponse->addResponse($curlHttpResponse);
        }
        $this->assertEquals($responseArray, $this->curlMultiHttpResponse->getCurlHttpResponses());
        $this->assertEquals($responseArray, $this->curlMultiHttpResponse->getAll());
    }

    /**
     * @dataProvider curlHttpResponseProvider
     * @covers MikeBrantRestClientLibCurlMultiHttpResponse::getResponseBodies
     */
    public function testGetRepsonseBodies($curlHttpResponse) {
        $responseArray = array_fill(0, 5, $curlHttpResponse);
        for ($i = 0; $i < count($responseArray); $i++) {
            $this->curlMultiHttpResponse->addResponse($curlHttpResponse);
        }
        $responseBodies = array_map(
            function($val) {
                return $val->getBody();
            },
            $responseArray
        );
        $this->assertEquals($responseBodies, $this->curlMultiHttpResponse->getResponseBodies());
    }

    /**
     * @dataProvider curlHttpResponseProvider
     * @covers MikeBrantRestClientLibCurlMultiHttpResponse::getHttpCodes
     */
    public function testgetHttpCodes($curlHttpResponse) {
        $responseArray = array_fill(0, 5, $curlHttpResponse);
        for ($i = 0; $i < count($responseArray); $i++) {
            $this->curlMultiHttpResponse->addResponse($curlHttpResponse);
        }
        $responseCodes = array_map(
            function($val) {
                return $val->getHttpCode();
            },
            $responseArray
        );
        $this->assertEquals($responseCodes, $this->curlMultiHttpResponse->getHttpCodes());
    }

    /**
     * @dataProvider curlHttpResponseProvider
     * @covers MikeBrantRestClientLibCurlMultiHttpResponse::getRequestUrls
     */
    public function testGetRequestUrls($curlHttpResponse) {
        $responseArray = array_fill(0, 5, $curlHttpResponse);
        for ($i = 0; $i < count($responseArray); $i++) {
            $this->curlMultiHttpResponse->addResponse($curlHttpResponse);
        }
        $requestUrls = array_map(
            function($val) {
                return $val->getRequestUrl();
            },
            $responseArray
        );
        $this->assertEquals($requestUrls, $this->curlMultiHttpResponse->getRequestUrls());
    }

    /**
     * @dataProvider curlHttpResponseProvider
     * @covers MikeBrantRestClientLibCurlMultiHttpResponse::getRequestHeaders
     */
    public function testGetRequestHeaders($curlHttpResponse) {
        $responseArray = array_fill(0, 5, $curlHttpResponse);
        for ($i = 0; $i < count($responseArray); $i++) {
            $this->curlMultiHttpResponse->addResponse($curlHttpResponse);
        }
        $requestHeaders = array_map(
            function($val) {
                return $val->getRequestHeader();
            },
            $responseArray
        );
        $this->assertEquals($requestHeaders, $this->curlMultiHttpResponse->getRequestHeaders());
    }

    /**
     * @dataProvider curlHttpResponseProvider
     * @covers MikeBrantRestClientLibCurlMultiHttpResponse::getCurlGetinfoArrays
     */
    public function testGetCurlGetinfoArrays($curlHttpResponse) {
        $responseArray = array_fill(0, 5, $curlHttpResponse);
        for ($i = 0; $i < count($responseArray); $i++) {
            $this->curlMultiHttpResponse->addResponse($curlHttpResponse);
        }
        $requestInfoArrays = array_map(
            function($val) {
                return $val->getCurlGetinfo();
            },
            $responseArray
        );
        $this->assertEquals($requestInfoArrays, $this->curlMultiHttpResponse->getCurlGetinfoArrays());
    }
}


Get this bounty!!!

#StackBounty: #php #reactjs PHP V8Js render react written in typescript

Bounty: 50

I’ve been writing react components with typescript, following the instructions in official typescript docs:

https://www.typescriptlang.org/docs/handbook/react-&-webpack.html

I am using V8Js php extension to render React on server side,
but it seems I lack the understanding of what is the correct way using V8Js.

this is my react application: (client-side rendered)

http://codepen.io/MasterScripter/pen/xqRZQO

I tried to render on side-server:

<?php

$v8 = new V8Js();

$react = [];

// stubs, react
$react[] = "var console = {warn: function(){}, error: print}";
$react[] = "var global = {}";
$react[] = file_get_contents('./dist/react.js');
$react[] = "var React = global.React";

$react[] = file_get_contents('./dist/bundle.js');
$data = [
  'value' => 1,
  'onClick' => 'function'
];
$react[] = sprintf(
  "React.renderComponentToString(Square({data: %s}), print)",
  json_encode($data));
$react = implode(";", $react);

try {
  $v8->executeString($react);
} catch(V8JsException $e) {
  echo "
    File: {$e->getJsFileName()} n
    Line Number: {$e->getJsLineNumber()} n
    Source Line: {$e->getJsSourceLine()} n
    Trace: {$e->getJsTrace()}
  ";
}

‘react.js’ includes raw react & react-dom code,
‘bundle.js’ includes webpack bundle, same as the one from the pen above.

I get this error:

File: V8Js::compileString() 

Line Number: 209 

Source Line: module.exports = ReactDOM; 

Trace: ReferenceError: ReactDOM is not defined
at Object.setPrototypeOf.__proto__ (V8Js::compileString():209:18)
at __webpack_require__ (V8Js::compileString():47:30)
at Object.<anonymous> (V8Js::compileString():300:16)
at __webpack_require__ (V8Js::compileString():47:30)
at module.exports (V8Js::compileString():93:18)
at V8Js::compileString():96:10

any suggestions / hints how I can do this properly?


Get this bounty!!!

#StackBounty: #php #joomla #joomla3.0 Joomla component router

Bounty: 50

I’m trying to follow the instructions on this docs page, but I seem to be missing something:
https://docs.joomla.org/Supporting_SEF_URLs_in_your_component

The URI that needs to be corrected is: index.php?com_component&view=legal&page=customermasteragreement

It seems like the routing function should be simple, but the page is just displaying default instead of the sub-view.

Here’s my current code:

function ComponentBuildRoute(&$query)
{
    $segments = array();
        if (isset($query['view'])) {
            $segments[] = $query['view'];
            unset($query['view']);
       }
        if (isset($query['page'])) {
            $segments[] = $query['page'];
            unset($query['page']);
    }

    return $segments;
}

function ComponentParseRoute($segments)
{
       $vars = array();
       switch($segments[0])
       {
               case 'legal':
                       $vars['view'] = 'legal';
                       break;
               case 'customermasteragreement':
                       $vars['page'] = 'customermasteragreement';
                       break;

       }

       return $vars;
}

Update
This code works to display the subpage, but it gives me a URI like: legal-agreements/legal?page=customermasteragreement

class ComponentRouter extends JComponentRouterBase {

    public function build(&$query) {
        $segments = array();
        $view = null;

        if (isset($query['view'])) {
            $segments[] = $query['view'];
            $view = $query['view'];

            unset($query['view']);
        }

        if (isset($query['id'])) {
            if ($view !== null) {
                $segments[] = $query['id'];
            } else {
                $segments[] = $query['id'];
            }

            unset($query['id']);
        }

        return $segments;
    }


    public function parse(&$segments) {

        $vars = array();

        // View is always the first element of the array
        $vars['view'] = array_shift($segments);

        return $vars;
    }
}

EDIT 2

If it helps, here’s my model and views

models/legal.php

// import Joomla modelitem library
jimport('joomla.application.component.modelitem');

class ComponentModelLegal extends JModelItem {
    public function __construct($config = array())
            {
       JLoader::register('ComponentHelper', JPATH_COMPONENT_ADMINISTRATOR . '/helpers/component.php');
       parent::__construct($config);
            }

    /**
     * 
     * @return string
     */
    public function getLegal() {
        $app = JFactory::getApplication();
        $page = $app->input->get('page', '', 'STRING');

        if ($page) {
            ComponentHelper::add('type', $page);   //This is an API request to an external service, returning JSON formatted data
            $legal = ComponentHelper::getData('commons/legal-agreements.json', TRUE);


            if (isset($legal[0]['status'])) {
                JError::raiseError(400, $legal[0]['ERROR']);
                return false;
            } else {
                if (!isset($this->legal)) {
                    $this->legal = $legal;
                }
                return $this->legal;
            }
        }
    }

}

views/legal/view.html.php

class ComponentViewLegal extends JViewLegacy {
    function display($tpl = null) {
        // Assign data to the view
        $this->legal = $this->get('legal');

        // Check for errors.
        if (count($errors = $this->get('Errors'))) {
            JLog::add(implode('<br />', $errors), JLog::WARNING, 'jerror');
            return false;
        }

        // Display the view
        parent::display($tpl);
    }
}

views/legal/tmpl/default.php

$page = JRequest::getVar('page');
$pages = array(
    'resellermasteragreement',
    'customermasteragreement',
    'resellerdomainagreement',
    'customerdomainagreement',
    'resellerwebserviceagreement',
    'customerwebserviceagreement',
    'resellerdigicertagreement',
    'customerdigicertagreement',
    'registraragreement',
    'customerhostingproductagreement',
    'resellerhostingproductagreement'
);

    ?>
    
loadTemplate('legal') : $this->loadTemplate('home'); ?>

views/legal/tmpl/default_legal.php

$page = JRequest::getVar('page');

echo nl2br(htmlspecialchars($this->legal[$page]['defaultagreement'], ENT_NOQUOTES, "UTF-8"));
?>
<a type="button" class="btn btn-primary" href="<?php echo JROUTE::_("index.php?option=com_component&view=legal"); ?>">Back</a>


Get this bounty!!!

#StackBounty: #php #html #http-status-code-406 PHP – Not Acceptable – 406 – file could not be found on this server

Bounty: 50

I am getting this error

Not Acceptable

An appropriate representation of the requested resource
/admin/prc_res.php could not be found on this server.

Additionally, a 406 Not Acceptable error was encountered while trying
to use an ErrorDocument to handle the request.

There is a $_POST['additionalinfo'] (from html teaxtarea) which is giving me this error. If I remove this, code works fine.

But why I am getting 406 error?

My index1.php in unnat1ir.in/cdn/

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>406 Error</title>
</head>
<p><form method="post" action="process.php">
<textarea name="test" rows="10" style="width:100%"></textarea><br>
<button type="submit">Check for 406 Error</button>
</form>
</p>
<p>This form is posted to <code>process.php</code>. In <code>process.php</code> I am just echo-ing <code>$_POST['test']</code> of the <code>textare with tinymce plugin</code>. <strong>If little amount of data is entered in textarea, no issues, BUT LARGE DATA will cause <code>406</code> error</strong></p>
<h1>Please check by inserting large <code>html</code> data </h1>
<body>
</body>
</html>

And process.php

<?
ini_set("log_errors", 1);
ini_set("error_log", "err.txt");
?>
<h1>The <code> echo ($_POST['test'])</code> is</h1>
<hr>
<code><? echo htmlentities($_POST['test']); ?></code>
<hr>

All the above codes working fine on localhost


Get this bounty!!!

#StackBounty: #javascript #php #post #http-post Get bytes transferred using PHP5 for POST request

Bounty: 50

Note I am new to PHP, Apache and programming for servers so more thorough explanations will be appreciated.


Context

I created – in javascript – a progress bar to display when a file is uploaded. Current I have the progress bar update at a set frame-rate (to see if it works).

Clearly to make this an accurate progress bar, everything should in relation to the number of bytes transferred in comparison to the total number of bytes.

Question

using PHP5 how can I get information regarding the number of bytes transferred in relation to the total number of bytes of the file, such that I can pass that to a JS function updateProgress(bytesSoFar, totalBytes) to update my progress bar? Please verbosely walk me through the modifications needed to the code below to get this to work. I have seen xhr examples, but they are not thoroughly accessible.

I have just set up LocalHost and am using W3Schools’ PHP File Upload tutorial. To get the simulated ”upload” to work, I changed the local permissions as suggested in this S.O. post. I don’t necessarily need to read the file, I just want to know many bytes have been transferred.


Code

Currently I have two files:

  • index.php

  • upload.php

index.php

<!DOCTYPE html>
<html>
<body>

<form action="upload.php" method="post" enctype="multipart/form-data">
    Select image to upload:
    <input type="file" name="fileToUpload" id="fileToUpload">
    <input type="submit" value="Upload Image" name="submit">
</form>

</body>
</html>

upload.php

<?php
$target_dir = "uploads/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 1;
$imageFileType = pathinfo($target_file,PATHINFO_EXTENSION);
// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
    $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
    if($check !== false) {
        echo "File is an image - " . $check["mime"] . ".";
        $uploadOk = 1;
    } else {
        echo "File is not an image.";
        $uploadOk = 0;
    }
}
// Check if file already exists
if (file_exists($target_file)) {
    echo "Sorry, file already exists.";
    $uploadOk = 0;
}
// Check file size
if ($_FILES["fileToUpload"]["size"] > 500000) {
    echo "Sorry, your file is too large.";
    $uploadOk = 0;
}
// Allow certain file formats
if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
&& $imageFileType != "gif" ) {
    echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed.";
    $uploadOk = 0;
}
// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
    echo "Sorry, your file was not uploaded.";
// if everything is ok, try to upload file
} else {
    if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
        echo "The file ". basename( $_FILES["fileToUpload"]["name"]). " has been uploaded.";
    } else {
        echo "Sorry, there was an error uploading your file.";
    }
}
?>


Get this bounty!!!

#StackBounty: #php #cron #docker #alpine crond: can't set groups: Operation not permitted

Bounty: 50

This morning I upgraded my PHP version to 7.1 and am seeing an issue when cron tries to run php /var/www/html/artisan schedule:run (a simple PHP command) I see the output:

3/3/2017 10:39:00 AMcrond: can't set groups: Operation not permitted
3/3/2017 10:39:00 AMcrond: USER www-data pid 1562 cmd php /var/www/html/artisan schedule:run
3/3/2017 10:40:00 AMcrond: can't set groups: Operation not permitted
3/3/2017 10:40:00 AMcrond: USER www-data pid 1563 cmd php /var/www/html/artisan schedule:run
3/3/2017 10:41:00 AMcrond: can't set groups: Operation not permitted
3/3/2017 10:41:00 AMcrond: USER www-data pid 1564 cmd php /var/www/html/artisan schedule:run
3/3/2017 10:42:00 AMcrond: can't set groups: Operation not permitted
3/3/2017 10:42:00 AMcrond: USER www-data pid 1565 cmd php /var/www/html/artisan schedule:run
3/3/2017 10:43:00 AMcrond: can't set groups: Operation not permitted
3/3/2017 10:43:00 AMcrond: USER www-data pid 1566 cmd php /var/www/html/artisan schedule:run

The command being run is a Laravel artisan command. It’s run every minute allowing other scheduled work to be completed within the application itself. There’s nothing in this command that writes to any files or anything like that. The scheduled work talks to a database and sends some email. Application logs are sent to stdout since it’s a Docker container.

cron is run in a container with the command crond -f -d 8. Here’s the Dockerfile:

# This container should be used for any/all CLI processes
# including cron, queues, etc.
FROM php:7.1-alpine

# Copy the application files to the container
ADD . /var/www/html

WORKDIR /var/www/html

# fix permissions in CI
RUN sed -ri 's/^www-data:x:82:82:/www-data:x:1000:1000:/' /etc/passwd 
    && sed -ri 's/^www-data:x:82:/www-data:x:1000:/' /etc/group

# Install Composer dependencies
RUN apk add --update --no-cache git zip unzip 

        # needed for spatie/laravel-backup
        mysql-client 

        # needed for gd
        libpng-dev libjpeg-turbo-dev 

    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN docker-php-ext-install pdo_mysql gd 

        # needed for forking processes in laravel queues as of Laravel 5.3
        pcntl

# Ownership of the app dir for www-data
RUN chown -R www-data:www-data /var/www/html /home/www-data/

# Put php artisan schedule:run in a crontab
RUN echo "*       *       *       *       *       php /var/www/html/artisan schedule:run" > /etc/crontabs/www-data

# Make sure when users get into the container they aren't root
USER www-data

I’ve ruled out that php artisan schedule:run is the cause since I can run it manually and everything’s fine. This means it’s something within cron.

What is cron doing under the covers that could cause this error?


Get this bounty!!!