<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Table;

use MicrosoftAzure\Storage\Common\Internal\Authentication\SharedAccessSignatureAuthScheme;
use MicrosoftAzure\Storage\Common\Internal\Middlewares\CommonRequestMiddleware;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;
use MicrosoftAzure\Storage\Common\Internal\ServiceRestTrait;
use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext;
use MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy;
use MicrosoftAzure\Storage\Common\LocationMode;
use MicrosoftAzure\Storage\Table\Internal\Authentication\TableSharedKeyLiteAuthScheme;
use MicrosoftAzure\Storage\Table\Internal\ITable;
use MicrosoftAzure\Storage\Table\Internal\JsonODataReaderWriter;
use MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter;
use MicrosoftAzure\Storage\Table\Internal\TableResources as Resources;
use MicrosoftAzure\Storage\Table\Models\TableServiceOptions;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\Query;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;
use MicrosoftAzure\Storage\Table\Models\Filters\PropertyNameFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\ConstantFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\UnaryFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\QueryStringFilter;
use MicrosoftAzure\Storage\Table\Models\GetTableResult;
use MicrosoftAzure\Storage\Table\Models\GetTableOptions;
use MicrosoftAzure\Storage\Table\Models\GetEntityOptions;
use MicrosoftAzure\Storage\Table\Models\TableServiceCreateOptions;
use MicrosoftAzure\Storage\Table\Models\QueryTablesOptions;
use MicrosoftAzure\Storage\Table\Models\QueryTablesResult;
use MicrosoftAzure\Storage\Table\Models\InsertEntityResult;
use MicrosoftAzure\Storage\Table\Models\UpdateEntityResult;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesResult;
use MicrosoftAzure\Storage\Table\Models\DeleteEntityOptions;
use MicrosoftAzure\Storage\Table\Models\GetEntityResult;
use MicrosoftAzure\Storage\Table\Models\BatchOperationType;
use MicrosoftAzure\Storage\Table\Models\BatchOperationParameterName;
use MicrosoftAzure\Storage\Table\Models\BatchResult;
use MicrosoftAzure\Storage\Table\Models\TableACL;
use MicrosoftAzure\Storage\Common\Internal\Http\HttpFormatter;
use MicrosoftAzure\Storage\Table\Internal\IODataReaderWriter;
use MicrosoftAzure\Storage\Table\Internal\IMimeReaderWriter;
use MicrosoftAzure\Storage\Common\Internal\Serialization\ISerializer;

/**
 * This class constructs HTTP requests and receive HTTP responses for table
 * service layer.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class TableRestProxy extends ServiceRestProxy implements ITable
{
    use ServiceRestTrait;

    /**
     * @var Internal\IODataReaderWriter
     */
    private $odataSerializer;

    /**
     * @var Internal\IMimeReaderWriter
     */
    private $mimeSerializer;

    /**
     * Builds a table service object, it accepts the following
     * options:
     *
     * - http: (array) the underlying guzzle options. refer to
     *   http://docs.guzzlephp.org/en/latest/request-options.html for detailed available options
     * - middlewares: (mixed) the middleware should be either an instance of a sub-class that
     *   implements {@see MicrosoftAzure\Storage\Common\Middlewares\IMiddleware}, or a
     *   `callable` that follows the Guzzle middleware implementation convention
     *
     * Please refer to
     * https://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string
     * for how to construct a connection string with storage account name/key, or with a shared
     * access signature (SAS Token).
     *
     * @param string $connectionString The configuration connection string.
     * @param array  $options          Array of options to pass to the service
     *
     * @return TableRestProxy
     */
    public static function createTableService(
        $connectionString,
        array $options = []
    ) {
        $settings = StorageServiceSettings::createFromConnectionString(
            $connectionString
        );

        $odataSerializer = new JsonODataReaderWriter();
        $mimeSerializer = new MimeReaderWriter();

        $primaryUri = Utilities::tryAddUrlScheme(
            $settings->getTableEndpointUri()
        );
        $secondaryUri = Utilities::tryAddUrlScheme(
            $settings->getTableSecondaryEndpointUri()
        );

        $tableWrapper = new TableRestProxy(
            $primaryUri,
            $secondaryUri,
            $odataSerializer,
            $mimeSerializer,
            $options
        );

        // Adding headers filter
        $headers               = array();
        $currentVersion        = Resources::DATA_SERVICE_VERSION_VALUE;
        $maxVersion            = Resources::MAX_DATA_SERVICE_VERSION_VALUE;
        $accept                = Resources::ACCEPT_HEADER_VALUE;
        $acceptCharset         = Resources::ACCEPT_CHARSET_VALUE;

        $headers[Resources::DATA_SERVICE_VERSION]     = $currentVersion;
        $headers[Resources::MAX_DATA_SERVICE_VERSION] = $maxVersion;
        $headers[Resources::ACCEPT_HEADER]            = $accept;
        $headers[Resources::ACCEPT_CHARSET]           = $acceptCharset;

        // Getting authentication scheme
        if ($settings->hasSasToken()) {
            $authScheme = new SharedAccessSignatureAuthScheme(
                $settings->getSasToken()
            );
        } else {
            $authScheme = new TableSharedKeyLiteAuthScheme(
                $settings->getName(),
                $settings->getKey()
            );
        }

        // Adding common request middleware
        $commonRequestMiddleware = new CommonRequestMiddleware(
            $authScheme,
            Resources::STORAGE_API_LATEST_VERSION,
            Resources::TABLE_SDK_VERSION,
            $headers
        );
        $tableWrapper->pushMiddleware($commonRequestMiddleware);

        return $tableWrapper;
    }

    /**
     * Creates contexts for batch operations.
     *
     * @param array $operations The batch operations array.
     *
     * @return array
     *
     * @throws \InvalidArgumentException
     */
    private function createOperationsContexts(array $operations)
    {
        $contexts = array();

        foreach ($operations as $operation) {
            $context = null;
            $type    = $operation->getType();

            switch ($type) {
                case BatchOperationType::INSERT_ENTITY_OPERATION:
                case BatchOperationType::UPDATE_ENTITY_OPERATION:
                case BatchOperationType::MERGE_ENTITY_OPERATION:
                case BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION:
                case BatchOperationType::INSERT_MERGE_ENTITY_OPERATION:
                    $table   = $operation->getParameter(
                        BatchOperationParameterName::BP_TABLE
                    );
                    $entity  = $operation->getParameter(
                        BatchOperationParameterName::BP_ENTITY
                    );
                    $context = $this->getOperationContext($table, $entity, $type);
                    break;

                case BatchOperationType::DELETE_ENTITY_OPERATION:
                    $table        = $operation->getParameter(
                        BatchOperationParameterName::BP_TABLE
                    );
                    $partitionKey = $operation->getParameter(
                        BatchOperationParameterName::BP_PARTITION_KEY
                    );
                    $rowKey       = $operation->getParameter(
                        BatchOperationParameterName::BP_ROW_KEY
                    );
                    $etag         = $operation->getParameter(
                        BatchOperationParameterName::BP_ETAG
                    );
                    $options      = new DeleteEntityOptions();
                    $options->setETag($etag);
                    $context = $this->constructDeleteEntityContext(
                        $table,
                        $partitionKey,
                        $rowKey,
                        $options
                    );
                    break;

                default:
                    throw new \InvalidArgumentException();
            }

            $contexts[] = $context;
        }

        return $contexts;
    }

    /**
     * Creates operation context for the API.
     *
     * @param string $table  The table name.
     * @param Entity $entity The entity object.
     * @param string $type   The API type.
     *
     * @return \MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext
     *
     * @throws \InvalidArgumentException
     */
    private function getOperationContext($table, Entity $entity, $type)
    {
        switch ($type) {
            case BatchOperationType::INSERT_ENTITY_OPERATION:
                return $this->constructInsertEntityContext($table, $entity, null);

            case BatchOperationType::UPDATE_ENTITY_OPERATION:
                return $this->constructPutOrMergeEntityContext(
                    $table,
                    $entity,
                    Resources::HTTP_PUT,
                    true,
                    null
                );

            case BatchOperationType::MERGE_ENTITY_OPERATION:
                return $this->constructPutOrMergeEntityContext(
                    $table,
                    $entity,
                    Resources::HTTP_MERGE,
                    true,
                    null
                );

            case BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION:
                return $this->constructPutOrMergeEntityContext(
                    $table,
                    $entity,
                    Resources::HTTP_PUT,
                    false,
                    null
                );

            case BatchOperationType::INSERT_MERGE_ENTITY_OPERATION:
                return $this->constructPutOrMergeEntityContext(
                    $table,
                    $entity,
                    Resources::HTTP_MERGE,
                    false,
                    null
                );

            default:
                throw new \InvalidArgumentException();
        }
    }

    /**
     * Creates MIME part body for batch API.
     *
     * @param array $operations The batch operations.
     * @param array $contexts   The contexts objects.
     *
     * @return array
     *
     * @throws \InvalidArgumentException
     */
    private function createBatchRequestBody(array $operations, array $contexts)
    {
        $mimeBodyParts = array();
        $contentId     = 1;
        $count         = count($operations);

        Validate::isTrue(
            count($operations) == count($contexts),
            Resources::INVALID_OC_COUNT_MSG
        );

        for ($i = 0; $i < $count; $i++) {
            $operation = $operations[$i];
            $context   = $contexts[$i];
            $type      = $operation->getType();

            switch ($type) {
                case BatchOperationType::INSERT_ENTITY_OPERATION:
                case BatchOperationType::UPDATE_ENTITY_OPERATION:
                case BatchOperationType::MERGE_ENTITY_OPERATION:
                case BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION:
                case BatchOperationType::INSERT_MERGE_ENTITY_OPERATION:
                    $contentType  = $context->getHeader(Resources::CONTENT_TYPE);
                    $body         = $context->getBody();
                    $contentType .= ';type=entry';
                    $context->addOptionalHeader(Resources::CONTENT_TYPE, $contentType);
                    // Use mb_strlen instead of strlen to get the length of the string
                    // in bytes instead of the length in chars.
                    $context->addOptionalHeader(
                        Resources::CONTENT_LENGTH,
                        strlen($body)
                    );
                    break;

                case BatchOperationType::DELETE_ENTITY_OPERATION:
                    break;

                default:
                    throw new \InvalidArgumentException();
            }

            $context->addOptionalHeader(Resources::CONTENT_ID, $contentId);
            $mimeBodyPart    = $context->__toString();
            $mimeBodyParts[] = $mimeBodyPart;
            $contentId++;
        }

        return $this->mimeSerializer->encodeMimeMultipart($mimeBodyParts);
    }

    /**
     * Constructs HTTP call context for deleteEntity API.
     *
     * @param string              $table        The name of the table.
     * @param string              $partitionKey The entity partition key.
     * @param string              $rowKey       The entity row key.
     * @param DeleteEntityOptions $options      The optional parameters.
     *
     * @return HttpCallContext
     */
    private function constructDeleteEntityContext(
        $table,
        $partitionKey,
        $rowKey,
        DeleteEntityOptions $options = null
    ) {
        Validate::canCastAsString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');
        Validate::isTrue(!is_null($partitionKey), Resources::NULL_TABLE_KEY_MSG);
        Validate::isTrue(!is_null($rowKey), Resources::NULL_TABLE_KEY_MSG);

        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_NO_CONTENT;
        $path        = $this->getEntityPath($table, $partitionKey, $rowKey);

        if (is_null($options)) {
            $options = new DeleteEntityOptions();
        }

        $etagObj = $options->getETag();
        $ETag    = !is_null($etagObj);
        $this->addOptionalHeader(
            $headers,
            Resources::IF_MATCH,
            $ETag ? $etagObj : Resources::ASTERISK
        );

        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            Resources::JSON_CONTENT_TYPE
        );

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        $context = new HttpCallContext();
        $context->setHeaders($headers);
        $context->setMethod($method);
        $context->setPath($path);
        $context->setQueryParameters($queryParams);
        $context->addStatusCode($statusCode);
        $context->setBody('');
        $context->setServiceOptions($options);

        return $context;
    }

    /**
     * Constructs HTTP call context for updateEntity, mergeEntity,
     * insertOrReplaceEntity and insertOrMergeEntity.
     *
     * @param string              $table   The table name.
     * @param Entity              $entity  The entity instance to use.
     * @param string              $verb    The HTTP method.
     * @param boolean             $useETag The flag to include etag or not.
     * @param TableServiceOptions $options The optional parameters.
     *
     * @return HttpCallContext
     */
    private function constructPutOrMergeEntityContext(
        $table,
        Entity $entity,
        $verb,
        $useETag,
        TableServiceOptions $options = null
    ) {
        Validate::canCastAsString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');
        Validate::notNullOrEmpty($entity, 'entity');
        Validate::isTrue($entity->isValid($msg), $msg);

        $method       = $verb;
        $headers      = array();
        $queryParams  = array();
        $statusCode   = Resources::STATUS_NO_CONTENT;
        $partitionKey = $entity->getPartitionKey();
        $rowKey       = $entity->getRowKey();
        $path         = $this->getEntityPath($table, $partitionKey, $rowKey);
        $body         = $this->odataSerializer->getEntity($entity);

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        if ($useETag) {
            $etag         = $entity->getETag();
            $ifMatchValue = is_null($etag) ? Resources::ASTERISK : $etag;

            $this->addOptionalHeader($headers, Resources::IF_MATCH, $ifMatchValue);
        }

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::JSON_CONTENT_TYPE
        );
        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            Resources::JSON_FULL_METADATA_CONTENT_TYPE
        );

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);
        $context = new HttpCallContext();
        $context->setBody($body);
        $context->setHeaders($headers);
        $context->setMethod($method);
        $context->setPath($path);
        $context->setQueryParameters($queryParams);
        $context->addStatusCode($statusCode);
        $context->setServiceOptions($options);

        return $context;
    }

    /**
     * Constructs HTTP call context for insertEntity API.
     *
     * @param string                    $table   The name of the table.
     * @param Entity                    $entity  The table entity.
     * @param TableServiceCreateOptions $options The optional parameters.
     *
     * @return HttpCallContext
     */
    private function constructInsertEntityContext(
        $table,
        Entity $entity,
        TableServiceCreateOptions $options = null
    ) {
        Validate::canCastAsString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');
        Validate::notNullOrEmpty($entity, 'entity');
        Validate::isTrue($entity->isValid($msg), $msg);

        $method      = Resources::HTTP_POST;
        $context     = new HttpCallContext();
        $headers     = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_CREATED;
        $path        = $table;
        $body        = $this->odataSerializer->getEntity($entity);

        if (is_null($options)) {
            $options = new TableServiceCreateOptions();
        }

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::JSON_CONTENT_TYPE
        );
        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            $options->getAccept()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::PREFER,
            $options->getDoesReturnContent() ? Resources::RETURN_CONTENT : null
        );

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);
        $context->setBody($body);
        $context->setHeaders($headers);
        $context->setMethod($method);
        $context->setPath($path);
        $context->setQueryParameters($queryParams);
        $context->addStatusCode($statusCode);
        $context->setServiceOptions($options);

        return $context;
    }

    /**
     * Constructs URI path for entity.
     *
     * @param string $table        The table name.
     * @param string $partitionKey The entity's partition key.
     * @param string $rowKey       The entity's row key.
     *
     * @return string
     */
    private function getEntityPath($table, $partitionKey, $rowKey)
    {
        $encodedPK = $this->encodeODataUriValue($partitionKey);
        $encodedRK = $this->encodeODataUriValue($rowKey);

        return "$table(PartitionKey='$encodedPK',RowKey='$encodedRK')";
    }

    /**
     * Creates a promie that does the actual work for update and merge entity
     * APIs.
     *
     * @param string              $table   The table name.
     * @param Entity              $entity  The entity instance to use.
     * @param string              $verb    The HTTP method.
     * @param boolean             $useETag The flag to include etag or not.
     * @param TableServiceOptions $options The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    private function putOrMergeEntityAsyncImpl(
        $table,
        Entity $entity,
        $verb,
        $useETag,
        TableServiceOptions $options = null
    ) {
        $context = $this->constructPutOrMergeEntityContext(
            $table,
            $entity,
            $verb,
            $useETag,
            $options
        );

        return $this->sendContextAsync($context)->then(function ($response) {
            return UpdateEntityResult::create(
                HttpFormatter::formatHeaders($response->getHeaders())
            );
        }, null);
    }

    /**
     * Builds filter expression
     *
     * @param Filter $filter The filter object
     *
     * @return string
     */
    private function buildFilterExpression(Filter $filter)
    {
        $e = Resources::EMPTY_STRING;
        $this->buildFilterExpressionRec($filter, $e);

        return $e;
    }

    /**
     * Builds filter expression
     *
     * @param Filter $filter The filter object
     * @param string &$e     The filter expression
     *
     * @return string
     */
    private function buildFilterExpressionRec(Filter $filter, &$e)
    {
        if (is_null($filter)) {
            return;
        }

        if ($filter instanceof PropertyNameFilter) {
            $e .= $filter->getPropertyName();
        } elseif ($filter instanceof ConstantFilter) {
            $value = $filter->getValue();
            // If the value is null we just append null regardless of the edmType.
            if (is_null($value)) {
                $e .= 'null';
            } else {
                $type = $filter->getEdmType();
                $e   .= EdmType::serializeQueryValue($type, $value);
            }
        } elseif ($filter instanceof UnaryFilter) {
            $e .= $filter->getOperator();
            $e .= '(';
            $this->buildFilterExpressionRec($filter->getOperand(), $e);
            $e .= ')';
        } elseif ($filter instanceof BinaryFilter) {
            $e .= '(';
            $this->buildFilterExpressionRec($filter->getLeft(), $e);
            $e .= ' ';
            $e .= $filter->getOperator();
            $e .= ' ';
            $this->buildFilterExpressionRec($filter->getRight(), $e);
            $e .= ')';
        } elseif ($filter instanceof QueryStringFilter) {
            $e .= $filter->getQueryString();
        }

        return $e;
    }

    /**
     * Adds query object to the query parameter array
     *
     * @param array $queryParam The URI query parameters
     * @param Query $query      The query object
     *
     * @return array
     */
    private function addOptionalQuery(array $queryParam, Query $query)
    {
        if (!is_null($query)) {
            $selectedFields = $query->getSelectFields();
            if (!empty($selectedFields)) {
                $final = $this->encodeODataUriValues($selectedFields);

                $this->addOptionalQueryParam(
                    $queryParam,
                    Resources::QP_SELECT,
                    implode(',', $final)
                );
            }

            if (!is_null($query->getTop())) {
                $final = strval($this->encodeODataUriValue($query->getTop()));

                $this->addOptionalQueryParam(
                    $queryParam,
                    Resources::QP_TOP,
                    $final
                );
            }

            if (!is_null($query->getFilter())) {
                $final = $this->buildFilterExpression($query->getFilter());
                $this->addOptionalQueryParam(
                    $queryParam,
                    Resources::QP_FILTER,
                    $final
                );
            }
        }

        return $queryParam;
    }

    /**
     * Encodes OData URI values
     *
     * @param array $values The OData URL values
     *
     * @return array
     */
    private function encodeODataUriValues(array $values)
    {
        $list = array();

        foreach ($values as $value) {
            $list[] = $this->encodeODataUriValue($value);
        }

        return $list;
    }

    /**
     * Encodes OData URI value
     *
     * @param string $value The OData URL value
     *
     * @return string
     */
    private function encodeODataUriValue($value)
    {
        // Replace each single quote (') with double single quotes ('') not doudle
        // quotes (")
        $value = str_replace('\'', '\'\'', $value);

        // Encode the special URL characters
        $value = rawurlencode($value);

        return $value;
    }

    /**
     * Initializes new TableRestProxy object.
     *
     * @param string             $primaryUri      The storage account primary uri.
     * @param string             $secondaryUri    The storage account secondary uri.
     * @param IODataReaderWriter $odataSerializer The odata serializer.
     * @param IMimeReaderWriter  $mimeSerializer  The MIME serializer.
     * @param array              $options         Array of options to pass to
     *                                            the service
     */
    public function __construct(
        $primaryUri,
        $secondaryUri,
        IODataReaderWriter $odataSerializer,
        IMimeReaderWriter $mimeSerializer,
        array $options = []
    ) {
        parent::__construct(
            $primaryUri,
            $secondaryUri,
            Resources::EMPTY_STRING,
            $options
        );
        $this->odataSerializer = $odataSerializer;
        $this->mimeSerializer = $mimeSerializer;
    }

    /**
     * Quries tables in the given storage account.
     *
     * @param QueryTablesOptions|string|Filter $options Could be optional
     *                                                  parameters, table prefix
     *                                                  or filter to apply.
     *
     * @return QueryTablesResult
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/query-tables
     */
    public function queryTables($options = null)
    {
        return $this->queryTablesAsync($options)->wait();
    }

    /**
     * Creates promise to query the tables in the given storage account.
     *
     * @param QueryTablesOptions|string|Filter $options Could be optional
     *                                                  parameters, table prefix
     *                                                  or filter to apply.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/query-tables
     */
    public function queryTablesAsync($options = null)
    {
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = 'Tables';

        if (is_null($options)) {
            $options = new QueryTablesOptions();
        } elseif (is_string($options)) {
            $prefix  = $options;
            $options = new QueryTablesOptions();
            $options->setPrefix($prefix);
        } elseif ($options instanceof Filter) {
            $filter  = $options;
            $options = new QueryTablesOptions();
            $options->setFilter($filter);
        }

        $query   = $options->getQuery();
        $next    = $options->getNextTableName();
        $prefix  = $options->getPrefix();

        if (!empty($prefix)) {
            // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> upperBound
            // is prefix + '{'
            $prefixFilter = Filter::applyAnd(
                Filter::applyGe(
                    Filter::applyPropertyName('TableName'),
                    Filter::applyConstant($prefix, EdmType::STRING)
                ),
                Filter::applyLe(
                    Filter::applyPropertyName('TableName'),
                    Filter::applyConstant($prefix . '{', EdmType::STRING)
                )
            );

            if (is_null($query)) {
                $query = new Query();
            }

            if (is_null($query->getFilter())) {
                // use the prefix filter if the query filter is null
                $query->setFilter($prefixFilter);
            } else {
                // combine and use the prefix filter if the query filter exists
                $combinedFilter = Filter::applyAnd(
                    $query->getFilter(),
                    $prefixFilter
                );
                $query->setFilter($combinedFilter);
            }
        }

        $queryParams = $this->addOptionalQuery($queryParams, $query);

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_NEXT_TABLE_NAME,
            $next
        );
        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            $options->getAccept()
        );

        // One can specify the NextTableName option to get table entities starting
        // from the specified name. However, there appears to be an issue in the
        // Azure Table service where this does not engage on the server unless
        // $filter appears in the URL. The current behavior is to just ignore the
        // NextTableName options, which is not expected or easily detectable.
        if (array_key_exists(Resources::QP_NEXT_TABLE_NAME, $queryParams)
            && !array_key_exists(Resources::QP_FILTER, $queryParams)
        ) {
            $queryParams[Resources::QP_FILTER] = Resources::EMPTY_STRING;
        }

        $odataSerializer = $this->odataSerializer;

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_OK,
            Resources::EMPTY_STRING,
            $options
        )->then(function ($response) use ($odataSerializer) {
            $tables = $odataSerializer->parseTableEntries($response->getBody());
            return QueryTablesResult::create(
                HttpFormatter::formatHeaders($response->getHeaders()),
                $tables
            );
        }, null);
    }

    /**
     * Creates new table in the storage account
     *
     * @param string                    $table   The name of the table.
     * @param TableServiceCreateOptions $options The optional parameters.
     *
     * @return \Psr\Http\Message\ResponseInterface
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/create-table
     */
    public function createTable($table, TableServiceCreateOptions $options = null)
    {
        return $this->createTableAsync($table, $options)->wait();
    }

    /**
     * Creates promise to create new table in the storage account
     *
     * @param string                    $table   The name of the table.
     * @param TableServiceCreateOptions $options The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/create-table
     */
    public function createTableAsync(
        $table,
        TableServiceCreateOptions $options = null
    ) {
        Validate::canCastAsString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');

        $method      = Resources::HTTP_POST;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = 'Tables';
        $body        = $this->odataSerializer->getTable($table);

        if (is_null($options)) {
            $options = new TableServiceCreateOptions();
        }

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::JSON_CONTENT_TYPE
        );
        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            $options->getAccept()
        );

        $this->addOptionalHeader(
            $headers,
            Resources::PREFER,
            $options->getDoesReturnContent() ? Resources::RETURN_CONTENT : null
        );
        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_CREATED,
            $body,
            $options
        );
    }

    /**
     * Gets the table.
     *
     * @param string          $table   The name of the table.
     * @param GetTableOptions $options The optional parameters.
     *
     * @return GetTableResult
     */
    public function getTable($table, GetTableOptions $options = null)
    {
        return $this->getTableAsync($table, $options)->wait();
    }

    /**
     * Creates the promise to get the table.
     *
     * @param string          $table   The name of the table.
     * @param GetTableOptions $options The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function getTableAsync(
        $table,
        GetTableOptions $options = null
    ) {
        Validate::canCastAsString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = "Tables('$table')";

        if (is_null($options)) {
            $options = new GetTableOptions();
        }

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::JSON_CONTENT_TYPE
        );
        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            $options->getAccept()
        );

        $odataSerializer = $this->odataSerializer;

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_OK,
            Resources::EMPTY_STRING,
            $options
        )->then(function ($response) use ($odataSerializer) {
            return GetTableResult::create($response->getBody(), $odataSerializer);
        }, null);
    }

    /**
     * Deletes the specified table and any data it contains.
     *
     * @param string              $table   The name of the table.
     * @param TableServiceOptions $options optional parameters
     *
     * @return void
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx
     */
    public function deleteTable($table, TableServiceOptions $options = null)
    {
        $this->deleteTableAsync($table, $options)->wait();
    }

    /**
     * Creates promise to delete the specified table and any data it contains.
     *
     * @param string              $table   The name of the table.
     * @param TableServiceOptions $options optional parameters
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx
     */
    public function deleteTableAsync(
        $table,
        TableServiceOptions$options = null
    ) {
        Validate::canCastAsString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');

        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = "Tables('$table')";

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_NO_CONTENT,
            Resources::EMPTY_STRING,
            $options
        );
    }

    /**
     * Quries entities for the given table name
     *
     * @param string                             $table   The name of
     *                                                    the table.
     * @param QueryEntitiesOptions|string|Filter $options Coule be optional
     *                                                    parameters, query
     *                                                    string or filter to
     *                                                    apply.
     *
     * @return QueryEntitiesResult
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/query-entities
     */
    public function queryEntities($table, $options = null)
    {
        return $this->queryEntitiesAsync($table, $options)->wait();
    }

    /**
     * Quries entities for the given table name
     *
     * @param string                             $table   The name of the table.
     * @param QueryEntitiesOptions|string|Filter $options Coule be optional
     *                                                    parameters, query
     *                                                    string or filter to
     *                                                    apply.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/query-entities
     */
    public function queryEntitiesAsync($table, $options = null)
    {
        Validate::canCastAsString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $table;

        if (is_null($options)) {
            $options = new QueryEntitiesOptions();
        } elseif (is_string($options)) {
            $queryString = $options;
            $options     = new QueryEntitiesOptions();
            $options->setFilter(Filter::applyQueryString($queryString));
        } elseif ($options instanceof Filter) {
            $filter  = $options;
            $options = new QueryEntitiesOptions();
            $options->setFilter($filter);
        }

        $queryParams = $this->addOptionalQuery($queryParams, $options->getQuery());

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_NEXT_PK,
            $options->getNextPartitionKey()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_NEXT_RK,
            $options->getNextRowKey()
        );

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::JSON_CONTENT_TYPE
        );

        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            $options->getAccept()
        );

        if (!is_null($options->getQuery())) {
            $dsHeader   = Resources::DATA_SERVICE_VERSION;
            $maxdsValue = Resources::MAX_DATA_SERVICE_VERSION_VALUE;
            $fields     = $options->getQuery()->getSelectFields();
            $hasSelect  = !empty($fields);
            if ($hasSelect) {
                $this->addOptionalHeader($headers, $dsHeader, $maxdsValue);
            }
        }

        $odataSerializer = $this->odataSerializer;

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_OK,
            Resources::EMPTY_STRING,
            $options
        )->then(function ($response) use ($odataSerializer) {
            $entities = $odataSerializer->parseEntities($response->getBody());

            return QueryEntitiesResult::create(
                HttpFormatter::formatHeaders($response->getHeaders()),
                $entities
            );
        }, null);
    }

    /**
     * Inserts new entity to the table.
     *
     * @param string                    $table   name of the table.
     * @param Entity                    $entity  table entity.
     * @param TableServiceCreateOptions $options optional parameters.
     *
     * @return InsertEntityResult
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/insert-entity
     */
    public function insertEntity(
        $table,
        Entity $entity,
        TableServiceCreateOptions $options = null
    ) {
        return $this->insertEntityAsync($table, $entity, $options)->wait();
    }

    /**
     * Inserts new entity to the table.
     *
     * @param string                    $table   name of the table.
     * @param Entity                    $entity  table entity.
     * @param TableServiceCreateOptions $options optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/insert-entity
     */
    public function insertEntityAsync(
        $table,
        Entity $entity,
        TableServiceCreateOptions $options = null
    ) {
        $context = $this->constructInsertEntityContext(
            $table,
            $entity,
            $options
        );

        $odataSerializer = $this->odataSerializer;

        return $this->sendContextAsync($context)->then(
            function ($response) use ($odataSerializer) {
                $body     = $response->getBody();
                $headers  = HttpFormatter::formatHeaders($response->getHeaders());
                return InsertEntityResult::create(
                    $body,
                    $headers,
                    $odataSerializer
                );
            },
            null
        );
    }

    /**
     * Updates an existing entity or inserts a new entity if it does not exist
     * in the table.
     *
     * @param string              $table   name of the table
     * @param Entity              $entity  table entity
     * @param TableServiceOptions $options optional parameters
     *
     * @return UpdateEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452241.aspx
     */
    public function insertOrMergeEntity(
        $table,
        Entity $entity,
        TableServiceOptions $options = null
    ) {
        return $this->insertOrMergeEntityAsync($table, $entity, $options)->wait();
    }

    /**
     * Creates promise to update an existing entity or inserts a new entity if
     * it does not exist in the table.
     *
     * @param string              $table   name of the table
     * @param Entity              $entity  table entity
     * @param TableServiceOptions $options optional parameters
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452241.aspx
     */
    public function insertOrMergeEntityAsync(
        $table,
        Entity $entity,
        TableServiceOptions $options = null
    ) {
        return $this->putOrMergeEntityAsyncImpl(
            $table,
            $entity,
            Resources::HTTP_MERGE,
            false,
            $options
        );
    }

    /**
     * Replaces an existing entity or inserts a new entity if it does not exist in
     * the table.
     *
     * @param string              $table   name of the table
     * @param Entity              $entity  table entity
     * @param TableServiceOptions $options optional parameters
     *
     * @return UpdateEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452242.aspx
     */
    public function insertOrReplaceEntity(
        $table,
        Entity $entity,
        TableServiceOptions $options = null
    ) {
        return $this->insertOrReplaceEntityAsync(
            $table,
            $entity,
            $options
        )->wait();
    }

    /**
     * Creates a promise to replace an existing entity or inserts a new entity if it does not exist in the table.
     *
     * @param string              $table   name of the table
     * @param Entity              $entity  table entity
     * @param TableServiceOptions $options optional parameters
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452242.aspx
     */
    public function insertOrReplaceEntityAsync(
        $table,
        Entity $entity,
        TableServiceOptions $options = null
    ) {
        return $this->putOrMergeEntityAsyncImpl(
            $table,
            $entity,
            Resources::HTTP_PUT,
            false,
            $options
        );
    }

    /**
     * Updates an existing entity in a table. The Update Entity operation replaces
     * the entire entity and can be used to remove properties.
     *
     * @param string              $table   The table name.
     * @param Entity              $entity  The table entity.
     * @param TableServiceOptions $options The optional parameters.
     *
     * @return UpdateEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179427.aspx
     */
    public function updateEntity(
        $table,
        Entity $entity,
        TableServiceOptions $options = null
    ) {
        return $this->updateEntityAsync($table, $entity, $options)->wait();
    }

    /**
     * Creates promise to update an existing entity in a table. The Update Entity
     * operation replaces the entire entity and can be used to remove properties.
     *
     * @param string              $table   The table name.
     * @param Entity              $entity  The table entity.
     * @param TableServiceOptions $options The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179427.aspx
     */
    public function updateEntityAsync(
        $table,
        Entity $entity,
        TableServiceOptions $options = null
    ) {
        return $this->putOrMergeEntityAsyncImpl(
            $table,
            $entity,
            Resources::HTTP_PUT,
            true,
            $options
        );
    }

    /**
     * Updates an existing entity by updating the entity's properties. This operation
     * does not replace the existing entity, as the updateEntity operation does.
     *
     * @param string              $table   The table name.
     * @param Entity              $entity  The table entity.
     * @param TableServiceOptions $options The optional parameters.
     *
     * @return Models\UpdateEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179392.aspx
     */
    public function mergeEntity(
        $table,
        Entity $entity,
        TableServiceOptions $options = null
    ) {
        return $this->mergeEntityAsync($table, $entity, $options)->wait();
    }

    /**
     * Creates promise to update an existing entity by updating the entity's
     * properties. This operation does not replace the existing entity, as the
     * updateEntity operation does.
     *
     * @param string              $table   The table name.
     * @param Entity              $entity  The table entity.
     * @param TableServiceOptions $options The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179392.aspx
     */
    public function mergeEntityAsync(
        $table,
        Entity $entity,
        TableServiceOptions $options = null
    ) {
        return $this->putOrMergeEntityAsyncImpl(
            $table,
            $entity,
            Resources::HTTP_MERGE,
            true,
            $options
        );
    }

    /**
     * Deletes an existing entity in a table.
     *
     * @param string              $table        The name of the table.
     * @param string              $partitionKey The entity partition key.
     * @param string              $rowKey       The entity row key.
     * @param DeleteEntityOptions $options      The optional parameters.
     *
     * @return void
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135727.aspx
     */
    public function deleteEntity(
        $table,
        $partitionKey,
        $rowKey,
        DeleteEntityOptions $options = null
    ) {
        $this->deleteEntityAsync($table, $partitionKey, $rowKey, $options)->wait();
    }

    /**
     * Creates promise to delete an existing entity in a table.
     *
     * @param string              $table        The name of the table.
     * @param string              $partitionKey The entity partition key.
     * @param string              $rowKey       The entity row key.
     * @param DeleteEntityOptions $options      The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135727.aspx
     */
    public function deleteEntityAsync(
        $table,
        $partitionKey,
        $rowKey,
        DeleteEntityOptions $options = null
    ) {
        $context = $this->constructDeleteEntityContext(
            $table,
            $partitionKey,
            $rowKey,
            $options
        );

        return $this->sendContextAsync($context);
    }

    /**
     * Gets table entity.
     *
     * @param string                $table        The name of the table.
     * @param string                $partitionKey The entity partition key.
     * @param string                $rowKey       The entity row key.
     * @param GetEntityOptions|null $options      The optional parameters.
     *
     * @return GetEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx
     */
    public function getEntity(
        $table,
        $partitionKey,
        $rowKey,
        GetEntityOptions $options = null
    ) {
        return $this->getEntityAsync(
            $table,
            $partitionKey,
            $rowKey,
            $options
        )->wait();
    }

    /**
     * Creates promise to get table entity.
     *
     * @param string                $table        The name of the table.
     * @param string                $partitionKey The entity partition key.
     * @param string                $rowKey       The entity row key.
     * @param GetEntityOptions|null $options      The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx
     */
    public function getEntityAsync(
        $table,
        $partitionKey,
        $rowKey,
        GetEntityOptions $options = null
    ) {
        Validate::canCastAsString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');
        Validate::isTrue(!is_null($partitionKey), Resources::NULL_TABLE_KEY_MSG);
        Validate::isTrue(!is_null($rowKey), Resources::NULL_TABLE_KEY_MSG);

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $queryParams = array();
        $path        = $this->getEntityPath($table, $partitionKey, $rowKey);

        if (is_null($options)) {
            $options = new GetEntityOptions();
        }

        // TODO: support payload format options
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::JSON_CONTENT_TYPE
        );
        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            $options->getAccept()
        );

        $context = new HttpCallContext();
        $context->setHeaders($headers);
        $context->setMethod($method);
        $context->setPath($path);
        $context->setQueryParameters($queryParams);
        $context->setStatusCodes(array(Resources::STATUS_OK));
        $context->setServiceOptions($options);

        $odataSerializer = $this->odataSerializer;

        return $this->sendContextAsync($context)->then(
            function ($response) use ($odataSerializer) {
                return GetEntityResult::create(
                    $response->getBody(),
                    $odataSerializer
                );
            },
            null
        );
    }

    /**
     * Does batch of operations on the table service.
     *
     * @param BatchOperations     $batchOperations The operations to apply.
     * @param TableServiceOptions $options         The optional parameters.
     *
     * @return BatchResult
     */
    public function batch(
        Models\BatchOperations $batchOperations,
        Models\TableServiceOptions $options = null
    ) {
        return $this->batchAsync($batchOperations, $options)->wait();
    }

    /**
     * Creates promise that does batch of operations on the table service.
     *
     * @param BatchOperations     $batchOperations The operations to apply.
     * @param TableServiceOptions $options         The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function batchAsync(
        Models\BatchOperations $batchOperations,
        Models\TableServiceOptions $options = null
    ) {
        Validate::notNullOrEmpty($batchOperations, 'batchOperations');

        $method      = Resources::HTTP_POST;
        $operations  = $batchOperations->getOperations();
        $contexts    = $this->createOperationsContexts($operations);
        $mime        = $this->createBatchRequestBody($operations, $contexts);
        $body        = $mime['body'];
        $headers     = $mime['headers'];
        $postParams  = array();
        $queryParams = array();
        $path        = '$batch';

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $odataSerializer = $this->odataSerializer;
        $mimeSerializer = $this->mimeSerializer;

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            Resources::JSON_FULL_METADATA_CONTENT_TYPE
        );

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_ACCEPTED,
            $body,
            $options
        )->then(function ($response) use (
            $operations,
            $contexts,
            $odataSerializer,
            $mimeSerializer
        ) {
            return BatchResult::create(
                $response->getBody(),
                $operations,
                $contexts,
                $odataSerializer,
                $mimeSerializer
            );
        }, null);
    }

    /**
     * Gets the access control list (ACL)
     *
     * @param string              $table   The table name.
     * @param TableServiceOptions $options The optional parameters.
     *
     * @return TableACL
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-table-acl
     */
    public function getTableAcl(
        $table,
        Models\TableServiceOptions $options = null
    ) {
        return $this->getTableAclAsync($table, $options)->wait();
    }

    /**
     * Creates the promise to gets the access control list (ACL)
     *
     * @param string              $table   The table name.
     * @param TableServiceOptions $options The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-table-acl
     */
    public function getTableAclAsync(
        $table,
        Models\TableServiceOptions $options = null
    ) {
        Validate::canCastAsString($table, 'table');

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_OK;
        $path        = $table;

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'acl'
        );

        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            Resources::XML_CONTENT_TYPE
        );

        $dataSerializer = $this->dataSerializer;

        $promise = $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_OK,
            Resources::EMPTY_STRING,
            $options
        );

        return $promise->then(function ($response) use ($dataSerializer) {
            $parsed       = $dataSerializer->unserialize($response->getBody());
            return TableACL::create($parsed);
        }, null);
    }

    /**
     * Sets the ACL.
     *
     * @param string              $table   name
     * @param TableACL            $acl     access control list for Table
     * @param TableServiceOptions $options optional parameters
     *
     * @return void
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-table-acl
     */
    public function setTableAcl(
        $table,
        TableACL $acl,
        TableServiceOptions $options = null
    ) {
        $this->setTableAclAsync($table, $acl, $options)->wait();
    }

    /**
     * Creates promise to set the ACL
     *
     * @param string              $table   name
     * @param TableACL            $acl     access control list for Table
     * @param TableServiceOptions $options optional parameters
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-table-acl
     */
    public function setTableAclAsync(
        $table,
        TableACL $acl,
        TableServiceOptions $options = null
    ) {
        Validate::canCastAsString($table, 'table');
        Validate::notNullOrEmpty($acl, 'acl');

        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $body        = $acl->toXml($this->dataSerializer);
        $path        = $table;

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'acl'
        );

        $this->addOptionalHeader(
            $headers,
            Resources::ACCEPT_HEADER,
            Resources::XML_CONTENT_TYPE
        );

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_NO_CONTENT,
            $body,
            $options
        );
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\File\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2017 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\File\Models;

use MicrosoftAzure\Storage\Common\Internal\MetadataTrait;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\File\Internal\FileResources as Resources;

/**
 * Holds result of getShareProperties and getShareMetadata
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\File\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2017 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class GetSharePropertiesResult
{
    use MetadataTrait;

    private $quota;

    /**
     * Gets file quota.
     *
     * @return int
     */
    public function getQuota()
    {
        return $this->quota;
    }

    /**
     * Sets file quota.
     *
     * @param int $quota value.
     *
     * @return void
     */
    protected function setQuota($quota)
    {
        $this->quota = $quota;
    }

    /**
     * Create an instance using the response headers from the API call.
     *
     * @param  array  $responseHeaders The array contains all the response headers
     *
     * @internal
     *
     * @return GetSharePropertiesResult
     */
    public static function create(array $responseHeaders)
    {
        $result   = static::createMetadataResult($responseHeaders);

        $result->setQuota(\intval(Utilities::tryGetValueInsensitive(
            Resources::X_MS_SHARE_QUOTA,
            $responseHeaders
        )));

        return $result;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common;

use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Logger class for debugging purpose.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class Logger
{
    /**
     * @var string
     */
    private static $_filePath;

    /**
     * Logs $var to file.
     *
     * @param mixed  $var The data to log.
     * @param string $tip The help message.
     *
     * @return void
     */
    public static function log($var, $tip = Resources::EMPTY_STRING)
    {
        if (!empty($tip)) {
            error_log($tip . "\n", 3, self::$_filePath);
        }

        if (is_array($var) || is_object($var)) {
            error_log(print_r($var, true), 3, self::$_filePath);
        } else {
            error_log($var . "\n", 3, self::$_filePath);
        }
    }

    /**
     * Sets file path to use.
     *
     * @param string $filePath The log file path.
     * @return void
     */
    public static function setLogFile($filePath)
    {
        self::$_filePath = $filePath;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\File\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\File\Models;

use MicrosoftAzure\Storage\File\Internal\FileResources as Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Models\MarkerContinuationToken;
use MicrosoftAzure\Storage\Common\MarkerContinuationTokenTrait;

/**
 * Share to hold list directories and files response object.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\File\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class ListDirectoriesAndFilesResult
{
    use MarkerContinuationTokenTrait;

    private $directories;
    private $files;
    private $maxResults;
    private $accountName;
    private $marker;

    /**
     * Creates ListDirectoriesAndFilesResult object from parsed XML response.
     *
     * @param array  $parsedResponse XML response parsed into array.
     * @param string $location       Contains the location for the previous
     *                               request.
     *
     * @internal
     *
     * @return ListDirectoriesAndFilesResult
     */
    public static function create(array $parsedResponse, $location = '')
    {
        $result               = new ListDirectoriesAndFilesResult();
        $serviceEndpoint      = Utilities::tryGetKeysChainValue(
            $parsedResponse,
            Resources::XTAG_ATTRIBUTES,
            Resources::XTAG_SERVICE_ENDPOINT
        );
        $result->setAccountName(Utilities::tryParseAccountNameFromUrl(
            $serviceEndpoint
        ));

        $nextMarker = Utilities::tryGetValue(
            $parsedResponse,
            Resources::QP_NEXT_MARKER
        );

        if ($nextMarker != null) {
            $result->setContinuationToken(
                new MarkerContinuationToken(
                    $nextMarker,
                    $location
                )
            );
        }

        $result->setMaxResults(Utilities::tryGetValue(
            $parsedResponse,
            Resources::QP_MAX_RESULTS
        ));

        $result->setMarker(Utilities::tryGetValue(
            $parsedResponse,
            Resources::QP_MARKER
        ));

        $entries = Utilities::tryGetValue(
            $parsedResponse,
            Resources::QP_ENTRIES
        );

        if (empty($entries)) {
            $result->setDirectories(array());
            $result->setFiles(array());
        } else {
            $directoriesArray = Utilities::tryGetValue(
                $entries,
                Resources::QP_DIRECTORY
            );
            $filesArray = Utilities::tryGetValue(
                $entries,
                Resources::QP_FILE
            );

            $directories = array();
            $files = array();

            if ($directoriesArray != null) {
                if (array_key_exists(Resources::QP_NAME, $directoriesArray)) {
                    $directoriesArray = [$directoriesArray];
                }
                foreach ($directoriesArray as $directoryArray) {
                    $directories[] = Directory::create($directoryArray);
                }
            }

            if ($filesArray != null) {
                if (array_key_exists(Resources::QP_NAME, $filesArray)) {
                    $filesArray = [$filesArray];
                }
                foreach ($filesArray as $fileArray) {
                    $files[] = File::create($fileArray);
                }
            }

            $result->setDirectories($directories);
            $result->setFiles($files);
        }

        return $result;
    }

    /**
     * Sets Directories.
     *
     * @param array $directories list of directories.
     *
     * @return void
     */
    protected function setDirectories(array $directories)
    {
        $this->directories = array();
        foreach ($directories as $directory) {
            $this->directories[] = clone $directory;
        }
    }

    /**
     * Gets directories.
     *
     * @return Directory[]
     */
    public function getDirectories()
    {
        return $this->directories;
    }

    /**
     * Sets files.
     *
     * @param array $files list of files.
     *
     * @return void
     */
    protected function setFiles(array $files)
    {
        $this->files = array();
        foreach ($files as $file) {
            $this->files[] = clone $file;
        }
    }

    /**
     * Gets files.
     *
     * @return File[]
     */
    public function getFiles()
    {
        return $this->files;
    }

    /**
     * Gets max results.
     *
     * @return string
     */
    public function getMaxResults()
    {
        return $this->maxResults;
    }

    /**
     * Sets max results.
     *
     * @param string $maxResults value.
     *
     * @return void
     */
    protected function setMaxResults($maxResults)
    {
        $this->maxResults = $maxResults;
    }

    /**
     * Gets marker.
     *
     * @return string
     */
    public function getMarker()
    {
        return $this->marker;
    }

    /**
     * Sets marker.
     *
     * @param string $marker value.
     *
     * @return void
     */
    protected function setMarker($marker)
    {
        $this->marker = $marker;
    }

    /**
     * Gets account name.
     *
     * @return string
     */
    public function getAccountName()
    {
        return $this->accountName;
    }

    /**
     * Sets account name.
     *
     * @param string $accountName value.
     *
     * @return void
     */
    protected function setAccountName($accountName)
    {
        $this->accountName = $accountName;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\File\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2017 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\File\Models;

use MicrosoftAzure\Storage\File\Internal\FileResources as Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Represents windows azure directory object
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\File\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2017 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class Directory
{
    private $name;

    /**
     * Gets directory name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Sets directory name.
     *
     * @param string $name value.
     *
     * @return void
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Creates a Directory object using the parsed array.
     *
     * @param  array $parsed The parsed array that contains the object information.
     *
     * @return Directory
     */
    public static function create(array $parsed)
    {
        $result = new Directory();
        $name = Utilities::tryGetValue($parsed, Resources::QP_NAME);
        $result->setName($name);

        return $result;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Table\Models;

use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Table\Internal\TableResources as Resources;

/**
 * Holds result of updateEntity and mergeEntity APIs
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class UpdateEntityResult
{
    private $_etag;

    /**
     * Creates UpdateEntityResult from HTTP response headers.
     *
     * @param array $headers The HTTP response headers.
     *
     * @internal
     *
     * @return UpdateEntityResult
     */
    public static function create(array $headers)
    {
        $result = new UpdateEntityResult();
        $result->setETag(
            Utilities::tryGetValueInsensitive(Resources::ETAG, $headers)
        );

        return $result;
    }

    /**
     * Gets entity etag.
     *
     * @return string
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets entity etag.
     *
     * @param string $etag The entity ETag.
     *
     * @return void
     */
    protected function setETag($etag)
    {
        $this->_etag = $etag;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Http
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Internal\Http;

use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Models\ServiceOptions;

/**
 * Holds basic elements for making HTTP call.
 *
 * @ignore
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Http
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class HttpCallContext
{
    private $_method;
    private $_headers;
    private $_queryParams;
    private $_postParameters;
    private $_uri;
    private $_path;
    private $_statusCodes;
    private $_body;
    private $_serviceOptions;

    /**
     * Default constructor.
     */
    public function __construct()
    {
        $this->_method         = null;
        $this->_body           = null;
        $this->_path           = null;
        $this->_uri            = null;
        $this->_queryParams    = array();
        $this->_postParameters = array();
        $this->_statusCodes    = array();
        $this->_headers        = array();
        $this->_serviceOptions = new ServiceOptions();
    }

    /**
     * Gets method.
     *
     * @return string
     */
    public function getMethod()
    {
        return $this->_method;
    }

    /**
     * Sets method.
     *
     * @param string $method The method value.
     *
     * @return void
     */
    public function setMethod($method)
    {
        Validate::canCastAsString($method, 'method');

        $this->_method = $method;
    }

    /**
     * Gets headers.
     *
     * @return array
     */
    public function getHeaders()
    {
        return $this->_headers;
    }

    /**
     * Sets headers.
     *
     * Ignores the header if its value is empty.
     *
     * @param array $headers The headers value.
     *
     * @return void
     */
    public function setHeaders(array $headers)
    {
        $this->_headers = array();
        foreach ($headers as $key => $value) {
            $this->addHeader($key, $value);
        }
    }

    /**
     * Gets queryParams.
     *
     * @return array
     */
    public function getQueryParameters()
    {
        return $this->_queryParams;
    }

    /**
     * Sets queryParams.
     *
     * Ignores the query variable if its value is empty.
     *
     * @param array $queryParams The queryParams value.
     *
     * @return void
     */
    public function setQueryParameters(array $queryParams)
    {
        $this->_queryParams = array();
        foreach ($queryParams as $key => $value) {
            $this->addQueryParameter($key, $value);
        }
    }

    /**
     * Gets uri.
     *
     * @return string
     */
    public function getUri()
    {
        return $this->_uri;
    }

    /**
     * Sets uri.
     *
     * @param string $uri The uri value.
     *
     * @return void
     */
    public function setUri($uri)
    {
        Validate::canCastAsString($uri, 'uri');

        $this->_uri = $uri;
    }

    /**
     * Gets path.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->_path;
    }

    /**
     * Sets path.
     *
     * @param string $path The path value.
     *
     * @return void
     */
    public function setPath($path)
    {
        Validate::canCastAsString($path, 'path');

        $this->_path = $path;
    }

    /**
     * Gets statusCodes.
     *
     * @return array
     */
    public function getStatusCodes()
    {
        return $this->_statusCodes;
    }

    /**
     * Sets statusCodes.
     *
     * @param array $statusCodes The statusCodes value.
     *
     * @return void
     */
    public function setStatusCodes(array $statusCodes)
    {
        $this->_statusCodes = array();
        foreach ($statusCodes as $value) {
            $this->addStatusCode($value);
        }
    }

    /**
     * Gets body.
     *
     * @return string
     */
    public function getBody()
    {
        return $this->_body;
    }

    /**
     * Sets body.
     *
     * @param string $body The body value.
     *
     * @return void
     */
    public function setBody($body)
    {
        Validate::canCastAsString($body, 'body');

        $this->_body = $body;
    }

    /**
     * Adds or sets header pair.
     *
     * @param string $name  The HTTP header name.
     * @param string $value The HTTP header value.
     *
     * @return void
     */
    public function addHeader($name, $value)
    {
        Validate::canCastAsString($name, 'name');
        Validate::canCastAsString($value, 'value');

        $this->_headers[$name] = $value;
    }

    /**
     * Adds or sets header pair.
     *
     * Ignores header if it's value satisfies empty().
     *
     * @param string $name  The HTTP header name.
     * @param string $value The HTTP header value.
     *
     * @return void
     */
    public function addOptionalHeader($name, $value)
    {
        Validate::canCastAsString($name, 'name');
        Validate::canCastAsString($value, 'value');

        if (!empty($value)) {
            $this->_headers[$name] = $value;
        }
    }

    /**
     * Removes header from the HTTP request headers.
     *
     * @param string $name The HTTP header name.
     *
     * @return void
     */
    public function removeHeader($name)
    {
        Validate::canCastAsString($name, 'name');
        Validate::notNullOrEmpty($name, 'name');

        unset($this->_headers[$name]);
    }

    /**
     * Adds or sets query parameter pair.
     *
     * @param string $name  The URI query parameter name.
     * @param string $value The URI query parameter value.
     *
     * @return void
     */
    public function addQueryParameter($name, $value)
    {
        Validate::canCastAsString($name, 'name');
        Validate::canCastAsString($value, 'value');

        $this->_queryParams[$name] = $value;
    }

    /**
     * Gets HTTP POST parameters.
     *
     * @return array
     */
    public function getPostParameters()
    {
        return $this->_postParameters;
    }

    /**
     * Sets HTTP POST parameters.
     *
     * @param array $postParameters The HTTP POST parameters.
     *
     * @return void
     */
    public function setPostParameters(array $postParameters)
    {
        Validate::isArray($postParameters, 'postParameters');
        $this->_postParameters = $postParameters;
    }

    /**
     * Adds or sets query parameter pair.
     *
     * Ignores query parameter if it's value satisfies empty().
     *
     * @param string $name  The URI query parameter name.
     * @param string $value The URI query parameter value.
     *
     * @return void
     */
    public function addOptionalQueryParameter($name, $value)
    {
        Validate::canCastAsString($name, 'name');
        Validate::canCastAsString($value, 'value');

        if (!empty($value)) {
            $this->_queryParams[$name] = $value;
        }
    }

    /**
     * Adds status code to the expected status codes.
     *
     * @param integer $statusCode The expected status code.
     *
     * @return void
     */
    public function addStatusCode($statusCode)
    {
        Validate::isInteger($statusCode, 'statusCode');

        $this->_statusCodes[] = $statusCode;
    }

    /**
     * Gets header value.
     *
     * @param string $name The header name.
     *
     * @return mixed
     */
    public function getHeader($name)
    {
        return Utilities::tryGetValue($this->_headers, $name);
    }

    /**
     * Gets the saved service options
     *
     * @return ServiceOptions
     */
    public function getServiceOptions()
    {
        if ($this->_serviceOptions == null) {
            $this->_serviceOptions = new ServiceOptions();
        }
        return $this->_serviceOptions;
    }

    /**
     * Sets the service options
     *
     * @param ServiceOptions $serviceOptions the service options to be set.
     *
     * @return void
     */
    public function setServiceOptions(ServiceOptions $serviceOptions)
    {
        $this->_serviceOptions = $serviceOptions;
    }

    /**
     * Converts the context object to string.
     *
     * @return string
     */
    public function __toString()
    {
        $headers = Resources::EMPTY_STRING;
        $uri     = $this->_uri;

        if ($uri[strlen($uri)-1] != '/') {
            $uri = $uri.'/';
        }

        foreach ($this->_headers as $key => $value) {
            $headers .= "$key: $value\n";
        }

        $str  = "$this->_method $uri$this->_path HTTP/1.1\n$headers\n";
        $str .= "$this->_body";

        return $str;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Middlewares
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2017 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Middlewares;

use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Serialization\MessageSerializer;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Promise\RejectedPromise;

/**
 * This class provides the functionality to log the requests/options/responses.
 * Logging large number of entries without providing a file path may exhaust
 * the memory.
 *
 * The middleware should be pushed into client options if the logging is
 * intended to persist between different API calls.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Middlewares
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2017 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class HistoryMiddleware extends MiddlewareBase
{
    private $history;
    private $path;
    private $count;

    const TITLE_LENGTH = 120;

    /**
     * Gets the saved paried history.
     *
     * @return array
     */
    public function getHistory()
    {
        return $this->history;
    }

    /**
     * Constructor
     *
     * @param string $path the path to save the history. If path is provided,
     *                     no data is going to be saved to memory and the
     *                     entries are going to be serialized and saved to given
     *                     path.
     *
     */
    public function __construct($path = '')
    {
        $this->history = array();
        $this->path = $path;
        $this->count = 0;
    }

    /**
     * Add an entry to history
     *
     * @param array $entry the entry to be added.
     */
    public function addHistory(array $entry)
    {
        if ($this->path !== '') {
            $this->appendNewEntryToPath($entry);
        } else {
            Validate::isTrue(
                array_key_exists('request', $entry) &&
                array_key_exists('options', $entry) &&
                (array_key_exists('response', $entry) ||
                array_key_exists('reason', $entry)),
                'Given history entry not in correct format'
            );
            $this->history[] = $entry;
        }
        ++$this->count;
    }

    /**
     * Clear the history
     *
     * @return void
     */
    public function clearHistory()
    {
        $this->history = array();
        $this->count = 0;
    }

    /**
     * This function will be invoked after the request is sent, if
     * the promise is fulfilled.
     *
     * @param  RequestInterface $request the request sent.
     * @param  array            $options the options that the request sent with.
     *
     * @return callable
     */
    protected function onFulfilled(RequestInterface $request, array $options)
    {
        $reflection = $this;
        return function (ResponseInterface $response) use (
            $reflection,
            $request,
            $options
        ) {
            $reflection->addHistory([
                'request'  => $request,
                'response' => $response,
                'options'  => $options
            ]);
            return $response;
        };
    }

    /**
     * This function will be executed after the request is sent, if
     * the promise is rejected.
     *
     * @param  RequestInterface $request the request sent.
     * @param  array            $options the options that the request sent with.
     *
     * @return callable
     */
    protected function onRejected(RequestInterface $request, array $options)
    {
        $reflection = $this;
        return function ($reason) use (
            $reflection,
            $request,
            $options
        ) {
            $reflection->addHistory([
                'request' => $request,
                'reason'  => $reason,
                'options' => $options
            ]);
            return new RejectedPromise($reason);
        };
    }

    /**
     * Append the new entry to saved file path.
     *
     * @param array $entry the entry to be added.
     *
     * @return void
     */
    private function appendNewEntryToPath(array $entry)
    {
        $entryNoString = "Entry " . $this->count;
        $delimiter = str_pad(
            $entryNoString,
            self::TITLE_LENGTH,
            '-',
            STR_PAD_BOTH
        ) . PHP_EOL;
        $entryString = $delimiter;
        $entryString .= sprintf(
            "Time: %s\n",
            (new \DateTime("now", new \DateTimeZone('UTC')))->format('Y-m-d H:i:s')
        );
        $entryString .= MessageSerializer::objectSerialize($entry['request']);
        if (array_key_exists('reason', $entry)) {
            $entryString .= MessageSerializer::objectSerialize($entry['reason']);
        } elseif (array_key_exists('response', $entry)) {
            $entryString .= MessageSerializer::objectSerialize($entry['response']);
        }

        $entryString .= $delimiter;

        Utilities::appendToFile($this->path, $entryString);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Table\Models;

use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Table\Internal\TableResources as Resources;

/**
 * QueryTablesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryTablesResult
{
    use TableContinuationTokenTrait;

    private $_tables;

    /**
     * Creates new QueryTablesResult object
     *
     * @param array $headers The HTTP response headers
     * @param array $entries The table entriess
     *
     * @internal
     *
     * @return QueryTablesResult
     */
    public static function create(array $headers, array $entries)
    {
        $result  = new QueryTablesResult();
        $headers = array_change_key_case($headers);

        $result->setTables($entries);

        $nextTableName = Utilities::tryGetValue(
            $headers,
            Resources::X_MS_CONTINUATION_NEXTTABLENAME
        );

        if ($nextTableName != null) {
            $result->setContinuationToken(
                new TableContinuationToken(
                    $nextTableName,
                    '',
                    '',
                    Utilities::getLocationFromHeaders($headers)
                )
            );
        }

        return $result;
    }

    /**
     * Gets tables
     *
     * @return array
     */
    public function getTables()
    {
        return $this->_tables;
    }

    /**
     * Sets tables
     *
     * @param array $tables value
     *
     * @return void
     */
    protected function setTables(array $tables)
    {
        $this->_tables = $tables;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Queue\Models;

use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds results of listMessages wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class ListMessagesResult
{
    private $_queueMessages;

    /**
     * Creates ListMessagesResult object from parsed XML response.
     *
     * @param array $parsedResponse XML response parsed into array.
     *
     * @internal
     *
     * @return ListMessagesResult
     */
    public static function create(array $parsedResponse = null)
    {
        $result        = new ListMessagesResult();
        $queueMessages = array();

        if (!empty($parsedResponse)) {
            $rawMessages = Utilities::getArray($parsedResponse['QueueMessage']);
            foreach ($rawMessages as $value) {
                $message = QueueMessage::createFromListMessages($value);

                $queueMessages[] = $message;
            }
        }
        $result->setQueueMessages($queueMessages);

        return $result;
    }

    /**
     * Gets queueMessages field.
     *
     * @return array
     */
    public function getQueueMessages()
    {
        return $this->_queueMessages;
    }

    /**
     * Sets queueMessages field.
     *
     * @param array $queueMessages value to use.
     *
     * @internal
     *
     * @return void
     */
    protected function setQueueMessages(array $queueMessages)
    {
        $this->_queueMessages = array();

        foreach ($queueMessages as $value) {
            $this->_queueMessages[] = clone $value;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\File\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2017 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\File\Models;

use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\File\Internal\FileResources as Resources;

/**
 * WindowsAzure share object.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\File\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2017 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class Share
{
    private $name;
    private $metadata;
    private $properties;

    /**
     * Creates an instance with given response array.
     *
     * @param  array  $parsedResponse The response array.
     *
     * @return Share
     */
    public static function create(array $parsedResponse)
    {
        $result = new Share();
        $result->setName($parsedResponse[Resources::QP_NAME]);
        $result->setMetadata(
            Utilities::tryGetValue($parsedResponse, Resources::QP_METADATA, array())
        );
        $result->setProperties(ShareProperties::create(
            $parsedResponse[Resources::QP_PROPERTIES]
        ));
        return $result;
    }

    /**
     * Gets share name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Sets share name.
     *
     * @param string $name value.
     *
     * @return void
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Gets share metadata.
     *
     * @return array
     */
    public function getMetadata()
    {
        return $this->metadata;
    }

    /**
     * Sets share metadata.
     *
     * @param array $metadata value.
     *
     * @return void
     */
    public function setMetadata(array $metadata = null)
    {
        $this->metadata = $metadata;
    }

    /**
     * Gets share properties
     *
     * @return ShareProperties
     */
    public function getProperties()
    {
        return $this->properties;
    }

    /**
     * Sets share properties
     *
     * @param ShareProperties $properties share properties
     *
     * @return void
     */
    public function setProperties(ShareProperties $properties)
    {
        $this->properties = $properties;
    }
}