protected function executeQuery(
        $uri,
        $type = self::HTTP_GET,
        $variables = [],
        $contentType = self::CONTENT_TYPE_FORM
    ) : BookboonResponse {
        $http = curl_init();
        $headers = $this->getHeaders()->getAll();


        if ($type === self::HTTP_POST) {
            $encodedVariables = $this->encodeByContentType($variables, $contentType);

            $headers[] = "Content-Type: $contentType";
            $headers[] = 'Content-Length: ' . strlen($encodedVariables);

            curl_setopt($http, CURLOPT_POSTFIELDS, $encodedVariables);
            curl_setopt($http, CURLOPT_CUSTOMREQUEST, $type);
        }

        curl_setopt($http, CURLOPT_USERAGENT, $this->getUserAgentString());
        curl_setopt($http, CURLOPT_URL, $uri);
        curl_setopt($http, CURLOPT_USERPWD, $this->getApiId() . ':' . $this->getApiSecret());
        curl_setopt($http, CURLOPT_HTTPHEADER, $headers);

        foreach (self::$CURL_OPTS as $key => $val) {
            curl_setopt($http, $key, $val);
        }

        $response = curl_exec($http);
        $curlInfo = curl_getinfo($http);
        $headersSize = $curlInfo['header_size'];
        $httpStatus = $curlInfo['http_code'];

        $errorNumber = curl_errno($http);
        if ($errorNumber > 0) {
            if ($this->_logger) {
                $this->_logger->error("Api request: No response received with error {$errorNumber}");
            }

            if ($errorNumber === 28) {
                throw new ApiTimeoutException();
            }

            throw new ApiGeneralException('Curl error number ' . curl_errno($http));
        }

        if ($this->_logger) {
            $totalTime = $curlInfo['total_time'] ?? 0;
            $size = $curlInfo['size_download'] ?? 0;
            $this->_logger->info("Api request \"{$type} {$uri} HTTP/1.1\" {$httpStatus} - {$size} - {$totalTime}");
        }

        curl_close($http);

        if (is_bool($response)) {
            throw new ApiGeneralException('Empty response from API');
        }

        $decodedHeaders = $this->decodeHeaders(substr($response, 0, $headersSize));
        $body = substr($response, $headersSize);

        if ($httpStatus >= 400) {
            $this->handleErrorResponse(
                $body,
                $decodedHeaders,
                $httpStatus,
                $uri
            );
        }

        return new BookboonResponse(
            $body,
            $httpStatus,
            $decodedHeaders
        );
    }