You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by is...@apache.org on 2018/10/09 14:47:31 UTC

[16/21] ignite git commit: IGNITE-7783: PHP thin client

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Internal/Binary/TypeInfo.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Binary/TypeInfo.php b/modules/platforms/php/src/Apache/Ignite/Internal/Binary/TypeInfo.php
new file mode 100644
index 0000000..9ab4057
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Internal/Binary/TypeInfo.php
@@ -0,0 +1,312 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Internal\Binary;
+
+use Apache\Ignite\Type\ObjectType;
+
+class TypeInfo
+{
+    const NAME = 'name';
+    const SIZE = 'size';
+    const MIN_VALUE = 'min';
+    const MAX_VALUE = 'max';
+    const MAX_UNSIGNED_VALUE = 'max_unsigned';
+    const NULLABLE = 'nullable';
+    const ELEMENT_TYPE_CODE = 'element_type';
+    const KEEP_ELEMENT_TYPE = 'keep_element_type';
+    const MAX_INT_VALUE = 2147483647;
+
+    private $properties;
+    
+    private static $info;
+    private static $primitiveTypes;
+    
+    public static function init(): void
+    {
+        TypeInfo::$info = array(
+            ObjectType::BYTE => new TypeInfo([
+                TypeInfo::NAME => 'byte',
+                TypeInfo::SIZE => 1,
+                TypeInfo::MIN_VALUE => -128,
+                TypeInfo::MAX_VALUE => 127,
+                TypeInfo::MAX_UNSIGNED_VALUE => 0x100,
+            ]),
+            ObjectType::SHORT => new TypeInfo([
+                TypeInfo::NAME => 'short',
+                TypeInfo::SIZE => 2,
+                TypeInfo::MIN_VALUE => -32768,
+                TypeInfo::MAX_VALUE => 32767,
+                TypeInfo::MAX_UNSIGNED_VALUE => 0x10000,
+            ]),
+            ObjectType::INTEGER => new TypeInfo([
+                TypeInfo::NAME => 'integer',
+                TypeInfo::SIZE => 4,
+                TypeInfo::MIN_VALUE => -2147483648,
+                TypeInfo::MAX_VALUE => TypeInfo::MAX_INT_VALUE,
+            ]),
+            ObjectType::LONG => new TypeInfo([
+                TypeInfo::NAME => 'long',
+                TypeInfo::SIZE => 8,
+            ]),
+            ObjectType::FLOAT => new TypeInfo([
+                TypeInfo::NAME => 'float',
+                TypeInfo::SIZE => 4,
+            ]),
+            ObjectType::DOUBLE => new TypeInfo([
+                TypeInfo::NAME => 'double',
+                TypeInfo::SIZE => 8,
+            ]),
+            ObjectType::CHAR => new TypeInfo([
+                TypeInfo::NAME => 'char',
+                TypeInfo::SIZE => 2,
+            ]),
+            ObjectType::BOOLEAN => new TypeInfo([
+                TypeInfo::NAME => 'boolean',
+                TypeInfo::SIZE => 1,
+            ]),
+            ObjectType::STRING => new TypeInfo([
+                TypeInfo::NAME => 'string',
+                TypeInfo::NULLABLE => true,
+            ]),
+            ObjectType::UUID => new TypeInfo([
+                TypeInfo::NAME => 'UUID',
+                TypeInfo::SIZE => 16,
+                TypeInfo::NULLABLE => true,
+            ]),
+            ObjectType::DATE => new TypeInfo([
+                TypeInfo::NAME => 'date',
+                TypeInfo::SIZE => 8,
+                TypeInfo::NULLABLE => true,
+            ]),
+            ObjectType::BYTE_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'byte array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::BYTE
+            ]),
+            ObjectType::SHORT_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'short array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::SHORT
+            ]),
+            ObjectType::INTEGER_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'integer array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::INTEGER
+            ]),
+            ObjectType::LONG_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'long array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::LONG
+            ]),
+            ObjectType::FLOAT_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'float array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::FLOAT
+            ]),
+            ObjectType::DOUBLE_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'double array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::DOUBLE
+            ]),
+            ObjectType::CHAR_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'char array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::CHAR
+            ]),
+            ObjectType::BOOLEAN_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'boolean array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::BOOLEAN
+            ]),
+            ObjectType::STRING_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'string array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::STRING,
+                TypeInfo::KEEP_ELEMENT_TYPE => true
+            ]),
+            ObjectType::UUID_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'UUID array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::UUID,
+                TypeInfo::KEEP_ELEMENT_TYPE => true
+            ]),
+            ObjectType::DATE_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'date array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::DATE,
+                TypeInfo::KEEP_ELEMENT_TYPE => true
+            ]),
+            ObjectType::OBJECT_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'object array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::COMPLEX_OBJECT,
+                TypeInfo::KEEP_ELEMENT_TYPE => true
+            ]),
+            ObjectType::COLLECTION => new TypeInfo([
+                TypeInfo::NAME => 'collection',
+                TypeInfo::NULLABLE => true,
+            ]),
+            ObjectType::MAP => new TypeInfo([
+                TypeInfo::NAME => 'map',
+                TypeInfo::NULLABLE => true,
+            ]),
+            ObjectType::ENUM => new TypeInfo([
+                TypeInfo::NAME => 'enum',
+                TypeInfo::NULLABLE => true,
+            ]),
+            ObjectType::ENUM_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'enum array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::ENUM,
+                TypeInfo::KEEP_ELEMENT_TYPE => true
+            ]),
+            ObjectType::DECIMAL => new TypeInfo([
+                TypeInfo::NAME => 'decimal',
+                TypeInfo::NULLABLE => true,
+            ]),
+            ObjectType::DECIMAL_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'decimal array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::DECIMAL,
+                TypeInfo::KEEP_ELEMENT_TYPE => true
+            ]),
+            ObjectType::TIMESTAMP => new TypeInfo([
+                TypeInfo::NAME => 'timestamp',
+                TypeInfo::SIZE => 12,
+                TypeInfo::NULLABLE => true,
+            ]),
+            ObjectType::TIMESTAMP_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'timestamp array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::TIMESTAMP,
+                TypeInfo::KEEP_ELEMENT_TYPE => true
+            ]),
+            ObjectType::TIME => new TypeInfo([
+                TypeInfo::NAME => 'time',
+                TypeInfo::SIZE => 8,
+                TypeInfo::NULLABLE => true,
+            ]),
+            ObjectType::TIME_ARRAY => new TypeInfo([
+                TypeInfo::NAME => 'time array',
+                TypeInfo::NULLABLE => true,
+                TypeInfo::ELEMENT_TYPE_CODE => ObjectType::TIME,
+                TypeInfo::KEEP_ELEMENT_TYPE => true
+            ]),
+            ObjectType::NULL => new TypeInfo([
+                TypeInfo::NAME => 'null',
+                TypeInfo::NULLABLE => true,
+            ])
+        );
+        
+        TypeInfo::$primitiveTypes = [
+            ObjectType::BYTE,
+            ObjectType::SHORT,
+            ObjectType::INTEGER,
+            ObjectType::LONG,
+            ObjectType::FLOAT,
+            ObjectType::DOUBLE,
+            ObjectType::CHAR,
+            ObjectType::BOOLEAN,
+            ObjectType::STRING,
+            ObjectType::UUID,
+            ObjectType::DATE,
+            ObjectType::BYTE_ARRAY,
+            ObjectType::SHORT_ARRAY,
+            ObjectType::INTEGER_ARRAY,
+            ObjectType::LONG_ARRAY,
+            ObjectType::FLOAT_ARRAY,
+            ObjectType::DOUBLE_ARRAY,
+            ObjectType::CHAR_ARRAY,
+            ObjectType::BOOLEAN_ARRAY,
+            ObjectType::STRING_ARRAY,
+            ObjectType::UUID_ARRAY,
+            ObjectType::DATE_ARRAY,
+            ObjectType::ENUM,
+            ObjectType::ENUM_ARRAY,
+            ObjectType::DECIMAL,
+            ObjectType::DECIMAL_ARRAY,
+            ObjectType::TIMESTAMP,
+            ObjectType::TIMESTAMP_ARRAY,
+            ObjectType::TIME,
+            ObjectType::TIME_ARRAY
+        ];
+    }
+
+    public static function getTypeInfo(int $typeCode): ?TypeInfo
+    {
+        return array_key_exists($typeCode, TypeInfo::$info) ? TypeInfo::$info[$typeCode] : null;
+    }
+    
+    public static function getPrimitiveTypes(): array
+    {
+        return TypeInfo::$primitiveTypes;
+    }
+    
+    private function __construct(array $properties)
+    {
+        $this->properties = $properties;
+    }
+    
+    public function getName(): string
+    {
+        return $this->getProperty(TypeInfo::NAME, null);
+    }
+
+    public function getSize(): int
+    {
+        return $this->getProperty(TypeInfo::SIZE, 0);
+    }
+    
+    public function isNullable(): bool
+    {
+        return $this->getProperty(TypeInfo::NULLABLE, false);
+    }
+    
+    public function getElementTypeCode(): int
+    {
+        return $this->getProperty(TypeInfo::ELEMENT_TYPE_CODE, 0);
+    }
+    
+    public function keepElementType(): bool
+    {
+        return $this->getProperty(TypeInfo::KEEP_ELEMENT_TYPE, false);
+    }
+    
+    public function getMinValue()
+    {
+        return $this->getProperty(TypeInfo::MIN_VALUE, null);
+    }
+    
+    public function getMaxValue()
+    {
+        return $this->getProperty(TypeInfo::MAX_VALUE, null);
+    }
+
+    public function getMaxUnsignedValue()
+    {
+        return $this->getProperty(TypeInfo::MAX_UNSIGNED_VALUE, null);
+    }
+
+    private function getProperty(string $propName, $defaultValue)
+    {
+        return array_key_exists($propName, $this->properties) ? $this->properties[$propName] : $defaultValue;
+    }
+}
+
+TypeInfo::init();

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Internal/Cache.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Cache.php b/modules/platforms/php/src/Apache/Ignite/Internal/Cache.php
new file mode 100644
index 0000000..fe0965e
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Internal/Cache.php
@@ -0,0 +1,387 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Internal;
+
+use Apache\Ignite\Cache\CacheInterface;
+use Apache\Ignite\Cache\CacheEntry;
+use Apache\Ignite\Query\Query;
+use Apache\Ignite\Query\CursorInterface;
+use Apache\Ignite\Internal\Binary\ClientOperation;
+use Apache\Ignite\Internal\Binary\MessageBuffer;
+use Apache\Ignite\Internal\Utils\ArgumentChecker;
+use Apache\Ignite\Internal\Binary\BinaryUtils;
+use Apache\Ignite\Internal\Binary\BinaryCommunicator;
+
+class Cache implements CacheInterface
+{
+    private $name;
+    private $id;
+    private $keyType;
+    private $valueType;
+    private $communicator;
+    
+    public function __construct(string $name, BinaryCommunicator $communicator)
+    {
+        $this->name = $name;
+        $this->id = Cache::calculateId($this->name);
+        $this->communicator = $communicator;
+        $this->keyType = null;
+        $this->valueType = null;
+    }
+    
+    public static function calculateId(string $name)
+    {
+        return BinaryUtils::hashCode($name);
+    }
+
+    public function setKeyType($type): CacheInterface
+    {
+        BinaryUtils::checkObjectType($type, 'type');
+        $this->keyType = $type;
+        return $this;
+    }
+
+    public function setValueType($type): CacheInterface
+    {
+        BinaryUtils::checkObjectType($type, 'type');
+        $this->valueType = $type;
+        return $this;
+    }
+    
+    public function get($key)
+    {
+        return $this->writeKeyReadValueOp(ClientOperation::CACHE_GET, $key);
+    }
+    
+    public function getAll(array $keys): array
+    {
+        ArgumentChecker::notEmpty($keys, 'keys');
+        $result = [];
+        $this->communicator->send(
+            ClientOperation::CACHE_GET_ALL,
+            function (MessageBuffer $payload) use ($keys)
+            {
+                $this->writeCacheInfo($payload);
+                $this->writeKeys($payload, $keys);
+            },
+            function (MessageBuffer $payload) use (&$result)
+            {
+                $resultCount = $payload->readInteger();
+                for ($i = 0; $i < $resultCount; $i++) {
+                    array_push($result, new CacheEntry(
+                        $this->communicator->readObject($payload, $this->keyType),
+                        $this->communicator->readObject($payload, $this->valueType)));
+                }
+            });
+        return $result;
+    }
+    
+    public function put($key, $value): void
+    {
+        $this->writeKeyValueOp(ClientOperation::CACHE_PUT, $key, $value);        
+    }
+
+    public function putAll(array $entries): void
+    {
+        ArgumentChecker::notEmpty($entries, 'entries');
+        ArgumentChecker::hasType($entries, 'entries', true, CacheEntry::class);
+        $this->communicator->send(
+            ClientOperation::CACHE_PUT_ALL,
+            function (MessageBuffer $payload) use ($entries)
+            {
+                $this->writeCacheInfo($payload);
+                $payload->writeInteger(count($entries));
+                foreach ($entries as $entry) {
+                    $this->writeKeyValue($payload, $entry->getKey(), $entry->getValue());
+                }
+            });
+    }
+    
+    public function containsKey($key): bool
+    {
+        return $this->writeKeyReadBooleanOp(ClientOperation::CACHE_CONTAINS_KEY, $key);
+    }
+    
+    public function containsKeys(array $keys): bool
+    {
+        return $this->writeKeysReadBooleanOp(ClientOperation::CACHE_CONTAINS_KEYS, $keys);
+    }
+    
+    public function getAndPut($key, $value)
+    {
+        return $this->writeKeyValueReadValueOp(ClientOperation::CACHE_GET_AND_PUT, $key, $value);
+    }
+
+    public function getAndReplace($key, $value)
+    {
+        return $this->writeKeyValueReadValueOp(ClientOperation::CACHE_GET_AND_REPLACE, $key, $value);
+    }
+    
+    public function getAndRemove($key)
+    {
+        return $this->writeKeyReadValueOp(ClientOperation::CACHE_GET_AND_REMOVE, $key);
+    }
+
+    public function putIfAbsent($key, $value): bool
+    {
+        return $this->writeKeyValueReadBooleanOp(ClientOperation::CACHE_PUT_IF_ABSENT, $key, $value);
+    }
+    
+    public function getAndPutIfAbsent($key, $value)
+    {
+        return $this->writeKeyValueReadValueOp(ClientOperation::CACHE_GET_AND_PUT_IF_ABSENT, $key, $value);
+    }
+    
+    public function replace($key, $value): bool
+    {
+        return $this->writeKeyValueReadBooleanOp(ClientOperation::CACHE_REPLACE, $key, $value);
+    }
+
+    public function replaceIfEquals($key, $value, $newValue): bool
+    {
+        ArgumentChecker::notNull($key, 'key');
+        ArgumentChecker::notNull($value, 'value');
+        ArgumentChecker::notNull($newValue, 'newValue');
+        $result = false;
+        $this->communicator->send(
+            ClientOperation::CACHE_REPLACE_IF_EQUALS,
+            function (MessageBuffer $payload) use ($key, $value, $newValue)
+            {
+                $this->writeCacheInfo($payload);
+                $this->writeKeyValue($payload, $key, $value);
+                $this->communicator->writeObject($payload, $newValue, $this->valueType);
+            },
+            function (MessageBuffer $payload) use (&$result)
+            {
+                $result = $payload->readBoolean();
+            });
+        return $result;
+    }
+    
+    public function clear(): void
+    {
+        $this->communicator->send(
+            ClientOperation::CACHE_CLEAR,
+            function (MessageBuffer $payload)
+            {
+                $this->writeCacheInfo($payload);
+            });
+    }
+    
+    public function clearKey($key): void
+    {
+        $this->writeKeyOp(ClientOperation::CACHE_CLEAR_KEY, $key);
+    }
+    
+    public function clearKeys($keys): void
+    {
+        $this->writeKeysOp(ClientOperation::CACHE_CLEAR_KEYS, $keys);
+    }
+    
+    public function removeKey($key): bool
+    {
+        return $this->writeKeyReadBooleanOp(ClientOperation::CACHE_REMOVE_KEY, $key);
+    }
+    
+    public function removeIfEquals($key, $value): bool
+    {
+        return $this->writeKeyValueReadBooleanOp(ClientOperation::CACHE_REMOVE_IF_EQUALS, $key, $value);
+    }
+    
+    public function removeKeys($keys): void
+    {
+        $this->writeKeysOp(ClientOperation::CACHE_REMOVE_KEYS, $keys);
+    }
+            
+    public function removeAll(): void
+    {
+        $this->communicator->send(
+            ClientOperation::CACHE_REMOVE_ALL,
+            function (MessageBuffer $payload)
+            {
+                $this->writeCacheInfo($payload);
+            });
+    }
+    
+    public function getSize(int ...$peekModes): int
+    {
+        ArgumentChecker::hasValueFrom($peekModes, 'peekModes', true, [
+            CacheInterface::PEEK_MODE_ALL,
+            CacheInterface::PEEK_MODE_NEAR,
+            CacheInterface::PEEK_MODE_PRIMARY,
+            CacheInterface::PEEK_MODE_BACKUP
+        ]);
+        $result = 0;
+        $this->communicator->send(
+            ClientOperation::CACHE_GET_SIZE,
+            function (MessageBuffer $payload) use ($peekModes)
+            {
+                $this->writeCacheInfo($payload);
+                $payload->writeInteger(count($peekModes));
+                foreach ($peekModes as $mode) {
+                    $payload->writeByte($mode);
+                }
+            },
+            function (MessageBuffer $payload) use (&$result)
+            {
+                $result = $payload->readLong();
+            });
+        return $result;
+    }
+    
+    public function query(Query $query): CursorInterface
+    {
+        $value = null;
+        $this->communicator->send(
+            $query->getOperation(),
+            function (MessageBuffer $payload) use ($query)
+            {
+                $this->writeCacheInfo($payload);
+                $query->write($this->communicator, $payload);
+            },
+            function (MessageBuffer $payload) use ($query, &$value)
+            {
+                $value = $query->getCursor($this->communicator, $payload, $this->keyType, $this->valueType);
+            });
+        return $value;
+    }
+
+    private function writeCacheInfo(MessageBuffer $payload): void
+    {
+        $payload->writeInteger($this->id);
+        $payload->writeByte(0);
+    }
+
+    private function writeKeyValueOp(int $operation, $key, $value, callable $payloadReader = null): void
+    {
+        ArgumentChecker::notNull($key, 'key');
+        ArgumentChecker::notNull($value, 'value');
+        $this->communicator->send(
+            $operation,
+            function (MessageBuffer $payload) use ($key, $value)
+            {
+                $this->writeCacheInfo($payload);
+                $this->writeKeyValue($payload, $key, $value);
+            },
+            $payloadReader);
+    }
+
+    private function writeKeyValueReadValueOp(int $operation, $key, $value)
+    {
+        $result = null;
+        $this->writeKeyValueOp(
+            $operation, $key, $value,
+            function (MessageBuffer $payload) use (&$result)
+            {
+                $result = $this->communicator->readObject($payload, $this->valueType);
+            });
+        return $result;
+    }
+
+    private function writeKeyValueReadBooleanOp(int $operation, $key, $value): bool
+    {
+        $result = false;
+        $this->writeKeyValueOp(
+            $operation, $key, $value,
+            function (MessageBuffer $payload) use (&$result)
+            {
+                $result = $payload->readBoolean();
+            });
+        return $result;
+    }
+
+    private function writeKeyReadValueOp(int $operation, $key)
+    {
+        $value = null;
+        $this->writeKeyOp(
+            $operation, $key,
+            function (MessageBuffer $payload) use (&$value)
+            {
+                $value = $this->communicator->readObject($payload, $this->valueType);
+            });
+        return $value;
+    }
+
+    private function writeKeyOp(int $operation, $key, callable $payloadReader = null): void
+    {
+        ArgumentChecker::notNull($key, 'key');
+        $this->communicator->send(
+            $operation,
+            function (MessageBuffer $payload) use ($key)
+            {
+                $this->writeCacheInfo($payload);
+                $this->communicator->writeObject($payload, $key, $this->keyType);
+            },
+            $payloadReader);
+    }
+
+    private function writeKeyReadBooleanOp(int $operation, $key): bool
+    {
+        $result = false;
+        $this->writeKeyOp(
+            $operation,
+            $key,
+            function (MessageBuffer $payload) use (&$result)
+            {
+                $result = $payload->readBoolean();
+            });
+        return $result;
+        
+    }
+
+    private function writeKeys(MessageBuffer $payload, array $keys): void
+    {
+        $payload->writeInteger(count($keys));
+        foreach ($keys as $key) {
+            $this->communicator->writeObject($payload, $key, $this->keyType);
+        }
+    }
+
+    private function writeKeysReadBooleanOp(int $operation, array $keys): bool
+    {
+        $result = false;
+        $this->writeKeysOp(
+            $operation,
+            $keys,
+            function (MessageBuffer $payload) use (&$result)
+            {
+                $result = $payload->readBoolean();
+            });
+        return $result;
+    }
+
+    private function writeKeysOp(int $operation, array $keys, callable $payloadReader = null): void
+    {
+        ArgumentChecker::notEmpty($keys, 'keys');
+        $this->communicator->send(
+            $operation,
+            function (MessageBuffer $payload) use ($keys)
+            {
+                $this->writeCacheInfo($payload);
+                $this->writeKeys($payload, $keys);
+            },
+            $payloadReader);
+    }
+
+    private function writeKeyValue(MessageBuffer $payload, $key, $value): void
+    {
+        $this->communicator->writeObject($payload, $key, $this->keyType);
+        $this->communicator->writeObject($payload, $value, $this->valueType);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ClientFailoverSocket.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ClientFailoverSocket.php b/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ClientFailoverSocket.php
new file mode 100644
index 0000000..2600698
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ClientFailoverSocket.php
@@ -0,0 +1,134 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Internal\Connection;
+
+use Apache\Ignite\ClientConfiguration;
+use Apache\Ignite\Exception\NoConnectionException;
+use Apache\Ignite\Exception\OperationStatusUnknownException;
+use Apache\Ignite\Internal\Utils\Logger;
+
+class ClientFailoverSocket
+{
+    const STATE_DISCONNECTED = 0;
+    const STATE_CONNECTING = 1;
+    const STATE_CONNECTED = 2;
+
+    private $socket;
+    private $state;
+    private $config;
+    private $endpointsNumber;
+    private $endpointIndex;
+    private $reconnectRequired;
+            
+    public function __construct()
+    {
+        $this->socket = null;
+        $this->state = ClientFailoverSocket::STATE_DISCONNECTED;
+        $this->reconnectRequired = false;
+    }
+
+    public function connect(ClientConfiguration $config): void
+    {
+        if ($this->state !== ClientFailoverSocket::STATE_DISCONNECTED) {
+            $this->disconnect();
+        }
+        $this->config = $config;
+        $this->endpointsNumber = count($this->config->getEndpoints());
+        $this->endpointIndex = rand(0, $this->endpointsNumber - 1);
+        $this->failoverConnect();
+    }
+
+    public function send(int $opCode, ?callable $payloadWriter, callable $payloadReader = null): void
+    {
+        if ($this->reconnectRequired) {
+            $this->failoverConnect();
+            $this->reconnectRequired = false;
+        }
+        if ($this->state !== ClientFailoverSocket::STATE_CONNECTED) {
+            throw new NoConnectionException();
+        }
+        try {
+            $this->socket->sendRequest($opCode, $payloadWriter, $payloadReader);
+        } catch (OperationStatusUnknownException $e) {
+            $this->disconnect();
+            $this->endpointIndex++;
+            $this->reconnectRequired = true;
+            throw $e;
+        }
+    }
+
+    public function disconnect(): void
+    {
+        if ($this->state !== ClientFailoverSocket::STATE_DISCONNECTED) {
+            $this->changeState(ClientFailoverSocket::STATE_DISCONNECTED);
+            if ($this->socket) {
+                $this->socket->disconnect();
+                $this->socket = null;
+            }
+        }
+    }
+
+    private function failoverConnect(): void
+    {
+        $errors = [];
+        for ($i = 0; $i < $this->endpointsNumber; $i++) {
+            $index = ($this->endpointIndex + $i) % $this->endpointsNumber;
+            $endpoint = $this->config->getEndpoints()[$index];
+            try {
+                $this->changeState(ClientFailoverSocket::STATE_CONNECTING, $endpoint);
+                $this->socket = new ClientSocket($endpoint, $this->config);
+                $this->socket->connect();
+                $this->changeState(ClientFailoverSocket::STATE_CONNECTED, $endpoint);
+                $this->endpointIndex = $index;
+                return;
+            } catch (NoConnectionException $e) {
+                Logger::logError($e->getMessage());
+                array_push($errors, sprintf('[%s] %s', $endpoint, $e->getMessage()));
+                $this->changeState(ClientFailoverSocket::STATE_DISCONNECTED, $endpoint);
+            }
+        }
+        $this->socket = null;
+        throw new NoConnectionException(implode(';', $errors));
+    }
+
+    private function changeState(int $state, ?string $endpoint = null): void
+    {
+        if (Logger::isDebug()) {
+            Logger::logDebug(sprintf('Socket %s: %s -> %s',
+                $endpoint ? $endpoint : ($this->socket ? $this->socket->getEndpoint() : ''),
+                $this->getState($this->state),
+                $this->getState($state)));
+        }
+        $this->state = $state;
+    }
+
+    private function getState(int $state)
+    {
+        switch ($state) {
+            case ClientFailoverSocket::STATE_DISCONNECTED:
+                return 'DISCONNECTED';
+            case ClientFailoverSocket::STATE_CONNECTING:
+                return 'CONNECTING';
+            case ClientFailoverSocket::STATE_CONNECTED:
+                return 'CONNECTED';
+            default:
+                return 'UNKNOWN';
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ClientSocket.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ClientSocket.php b/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ClientSocket.php
new file mode 100644
index 0000000..a35bb15
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ClientSocket.php
@@ -0,0 +1,247 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Internal\Connection;
+
+use Apache\Ignite\ClientConfiguration;
+use Apache\Ignite\Type\ObjectType;
+use Apache\Ignite\Internal\Utils\Logger;
+use Apache\Ignite\Internal\Binary\BinaryUtils;
+use Apache\Ignite\Internal\Binary\BinaryCommunicator;
+use Apache\Ignite\Internal\Binary\MessageBuffer;
+use Apache\Ignite\Internal\Binary\Request;
+use Apache\Ignite\Exception\NoConnectionException;
+use Apache\Ignite\Exception\OperationException;
+use Apache\Ignite\Exception\OperationStatusUnknownException;
+
+class ClientSocket
+{
+    const HANDSHAKE_SUCCESS_STATUS_CODE = 1;
+    const REQUEST_SUCCESS_STATUS_CODE = 0;
+    const PORT_DEFAULT = 10800;
+    const SOCKET_CHUNK_SIZE_DEFAULT = 8192;
+    const HANDSHAKE_CODE = 1;
+    const CLIENT_CODE = 2;
+
+    private static $currentVersion;
+    private static $supportedVersions;
+
+    private $endpoint;
+    private $config;
+    private $socket;
+    private $sendChunkSize;
+    private $receiveChunkSize;
+    private $protocolVersion;
+
+    public function __construct(string $endpoint, ClientConfiguration $config)
+    {
+        $this->endpoint = $endpoint;
+        $this->config = $config;
+        $this->socket = null;
+        $this->sendChunkSize = $config->getSendChunkSize() > 0 ?
+            $config->getSendChunkSize() :
+            self::SOCKET_CHUNK_SIZE_DEFAULT;
+        $this->receiveChunkSize = $config->getReceiveChunkSize() > 0 ?
+            $config->getReceiveChunkSize() :
+            self::SOCKET_CHUNK_SIZE_DEFAULT;
+        $this->protocolVersion = null;
+    }
+
+    public function __destruct()
+    {
+        $this->disconnect();
+    }
+    
+    public static function init(): void
+    {
+        ClientSocket::$currentVersion = ProtocolVersion::$V_1_2_0;
+        ClientSocket::$supportedVersions = [
+            ProtocolVersion::$V_1_2_0
+        ];
+    }
+    
+    public function getEndpoint(): string
+    {
+        return $this->endpoint;
+    }
+
+    public function connect(): void
+    {
+        $tlsOptions = $this->config->getTLSOptions();
+        $options = ['socket' => ['tcp_nodelay' => $this->config->getTcpNoDelay()]];
+        if ($tlsOptions) {
+            $options['ssl'] = $tlsOptions;
+        }
+        $context = stream_context_create($options);
+        $errno = 0;
+        $errstr = null;
+        if (!($this->socket = stream_socket_client(
+                ($tlsOptions ? 'ssl://' : 'tcp://') . $this->endpoint,
+                $errno,
+                $errstr,
+                ini_get('default_socket_timeout'),
+                STREAM_CLIENT_CONNECT,
+                $context))) {
+            throw new NoConnectionException($errstr);
+        }
+        if ($this->config->getTimeout() > 0) {
+            $timeout = $this->config->getTimeout();
+            stream_set_timeout($this->socket, intdiv($timeout, 1000), $timeout % 1000);
+        }
+        // send handshake
+        $this->processRequest($this->getHandshakeRequest(ClientSocket::$currentVersion));
+    }
+
+    public function disconnect(): void
+    {
+        if ($this->socket !== false && $this->socket !== null) {
+            fclose($this->socket);
+            $this->socket = null;
+        }
+    }
+    
+    private function getHandshakeRequest($version): Request
+    {
+        $this->protocolVersion = $version;
+        return new Request(-1, array($this, 'handshakePayloadWriter'), null, true);
+    }
+
+    public function handshakePayloadWriter(MessageBuffer $buffer): void
+    {
+        // Handshake code
+        $buffer->writeByte(ClientSocket::HANDSHAKE_CODE);
+        // Protocol version
+        $this->protocolVersion->write($buffer);
+        // Client code
+        $buffer->writeByte(ClientSocket::CLIENT_CODE);
+        if ($this->config->getUserName()) {
+            BinaryCommunicator::writeString($buffer, $this->config->getUserName());
+            BinaryCommunicator::writeString($buffer, $this->config->getPassword());
+        }
+    }
+
+    public function sendRequest(int $opCode, ?callable $payloadWriter, callable $payloadReader = null): void
+    {
+        $request = new Request($opCode, $payloadWriter, $payloadReader);
+        $this->processRequest($request);
+    }
+
+    private function processRequest(Request $request): void
+    {
+        $buffer = $request->getMessage();
+        $this->logMessage($request->getId(), true, $buffer);
+        $data = $buffer->getBuffer();
+        while (($length = strlen($data)) > 0) {
+            $written = fwrite($this->socket, $data, $this->sendChunkSize);
+            if ($length === $written) {
+                break;
+            }
+            if ($written === false || $written === 0) {
+                throw new OperationStatusUnknownException('Error while writing data to the server');
+            }
+            $data = substr($data, $written);
+        }
+        $this->processResponse($request);
+    }
+
+    private function receive(MessageBuffer $buffer, int $minSize): void
+    {
+        while ($buffer->getLength() < $minSize)
+        {
+            $chunk = fread($this->socket, $this->receiveChunkSize);
+            if ($chunk === false || $chunk === '') {
+                throw new OperationStatusUnknownException('Error while reading data from the server');
+            } else {
+                $buffer->append($chunk);
+            }
+        }
+    }
+
+    private function processResponse(Request $request): void
+    {
+        $buffer = new MessageBuffer(0);
+        $this->receive($buffer, BinaryUtils::getSize(ObjectType::INTEGER));
+        // Response length
+        $length = $buffer->readInteger();
+        $this->receive($buffer, $length + BinaryUtils::getSize(ObjectType::INTEGER));
+        if ($request->isHandshake()) {
+            $this->processHandshake($buffer);
+        } else {
+            // Request id
+            $requestId = $buffer->readLong();
+            if (!BinaryUtils::floatEquals($requestId, $request->getId())) {
+                BinaryUtils::internalError('Invalid response id: ' . $requestId);
+            }
+            // Status code
+            $isSuccess = ($buffer->readInteger() === ClientSocket::REQUEST_SUCCESS_STATUS_CODE);
+            if (!$isSuccess) {
+                // Error message
+                $errMessage = BinaryCommunicator::readString($buffer);
+                throw new OperationException($errMessage);
+            } else {
+                $payloadReader = $request->getPayloadReader();
+                if ($payloadReader) {
+                    call_user_func($payloadReader, $buffer);
+                }
+            }
+        }
+        $this->logMessage($request->getId(), false, $buffer);
+    }
+
+    private function processHandshake(MessageBuffer $buffer): void
+    {
+        // Handshake status
+        if ($buffer->readByte() === ClientSocket::HANDSHAKE_SUCCESS_STATUS_CODE) {
+            return;
+        }
+        // Server protocol version
+        $serverVersion = new ProtocolVersion();
+        $serverVersion->read($buffer);
+        // Error message
+        $errMessage = BinaryCommunicator::readString($buffer);
+
+        if (!$this->isSupportedVersion($serverVersion)) {
+            throw new OperationException(
+                sprintf('Protocol version mismatch: client %s / server %s. Server details: %s',
+                    $this->protocolVersion->toString(), $serverVersion->toString(), $errMessage));
+        } else {
+            $this->disconnect();
+            throw new OperationException($errMessage);
+        }
+    }
+
+    private function isSupportedVersion(ProtocolVersion $version): bool
+    {
+        foreach (ClientSocket::$supportedVersions as $supportedVersion) {
+            if ($supportedVersion->equals($version)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    private function logMessage(int $requestId, bool $isRequest, MessageBuffer $buffer): void
+    {
+        if (Logger::isDebug()) {
+            Logger::logDebug(($isRequest ? 'Request: ' : 'Response: ') . $requestId);
+            Logger::logBuffer($buffer);
+        }
+    }
+}
+
+ClientSocket::init();

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ProtocolVersion.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ProtocolVersion.php b/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ProtocolVersion.php
new file mode 100644
index 0000000..3c3a64b
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Internal/Connection/ProtocolVersion.php
@@ -0,0 +1,82 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Internal\Connection;
+
+use Apache\Ignite\Internal\Binary\MessageBuffer;
+
+class ProtocolVersion
+{
+    public static $V_1_2_0;
+
+    private $major;
+    private $minor;
+    private $patch;
+
+    public static function init(): void
+    {
+        ProtocolVersion::$V_1_2_0 = new ProtocolVersion(1, 2, 0);
+    }
+
+    public function __construct(int $major = 0, int $minor = 0, int $patch = 0)
+    {
+        $this->major = $major;
+        $this->minor = $minor;
+        $this->patch = $patch;
+    }
+
+    public function compareTo(ProtocolVersion $other): int
+    {
+        $diff = $this->major - $other->major;
+        if ($diff !== 0) {
+            return $diff;
+        }
+        $diff = $this->minor - $other->minor;
+        if ($diff !== 0) {
+            return $diff;
+        }
+        return $this->patch - $other->patch;
+    }
+
+    public function equals(ProtocolVersion $other): bool
+    {
+        return $this->compareTo($other) === 0;
+    }
+
+    public function toString(): string
+    {
+        return sprintf('%d.%d.%d', $this->major, $this->minor, $this->patch);
+    }
+
+    public function read(MessageBuffer $buffer): void
+    {
+        $this->major = $buffer->readShort();
+        $this->minor = $buffer->readShort();
+        $this->patch = $buffer->readShort();
+    }
+
+    public function write(MessageBuffer $buffer): void
+    {
+        $buffer->writeShort($this->major);
+        $buffer->writeShort($this->minor);
+        $buffer->writeShort($this->patch);
+    }
+}
+
+ProtocolVersion::init();
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Internal/Query/Cursor.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Query/Cursor.php b/modules/platforms/php/src/Apache/Ignite/Internal/Query/Cursor.php
new file mode 100644
index 0000000..a228dd4
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Internal/Query/Cursor.php
@@ -0,0 +1,166 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Internal\Query;
+
+use Apache\Ignite\Cache\CacheEntry;
+use Apache\Ignite\Query\CursorInterface;
+use Apache\Ignite\Internal\Binary\ClientOperation;
+use Apache\Ignite\Internal\Binary\MessageBuffer;
+use Apache\Ignite\Internal\Binary\BinaryCommunicator;
+
+class Cursor implements CursorInterface
+{
+    protected $communicator;
+    private $operation;
+    private $buffer;
+    private $keyType;
+    private $valueType;
+    protected $id;
+    private $hasNext;
+    private $values;
+    private $valueIndex;
+    private $rewinds;
+    private $index;
+    
+    public function __construct(BinaryCommunicator $communicator, int $operation, MessageBuffer $buffer, $keyType = null, $valueType = null)
+    {
+        $this->communicator = $communicator;
+        $this->operation = $operation;
+        $this->buffer = $buffer;
+        $this->keyType = $keyType;
+        $this->valueType = $valueType;
+        $this->id = null;
+        $this->hasNext = false;
+        $this->values = null;
+        $this->valueIndex = 0;
+        $this->rewinds = 0;
+        $this->index = 0;
+    }
+
+    public function current()
+    {
+        return $this->values[$this->valueIndex];
+    }
+    
+    public function key()
+    {
+        return $this->index;
+    }
+    
+    public function next() 
+    {
+        $this->valueIndex++;
+        $this->index++;
+    }
+    
+    public function rewind(): void
+    {
+        $this->rewinds++;
+    }
+
+    public function valid(): bool
+    {
+        if ($this->rewinds > 1) {
+            return false;
+        }
+        if (!$this->values || $this->valueIndex >= count($this->values)) {
+            $this->obtainValues();
+            $this->valueIndex = 0;
+        }
+        return $this->values && $this->valueIndex < count($this->values);
+    }
+
+    public function getAll(): array
+    {
+        $result = [];
+        foreach ($this as $value) {
+            array_push($result, $value);
+        }
+        return $result;
+    }
+
+    public function close(): void
+    {
+        // Close cursor only if the server has more pages: the server closes cursor automatically on last page
+        if ($this->id && $this->hasNext) {
+            $this->communicator->send(
+                ClientOperation::RESOURCE_CLOSE,
+                function (MessageBuffer $payload)
+                {
+                    $this->write($payload);
+                });
+        }
+    }
+
+    private function getNext(): void
+    {
+        $this->hasNext = false;
+        $this->values = null;
+        $this->buffer = null;
+        $this->communicator->send(
+            $this->operation,
+            function (MessageBuffer $payload)
+            {
+                $this->write($payload);
+            },
+            function (MessageBuffer $payload)
+            {
+                $this->buffer = $payload;
+            });
+    }
+
+    private function obtainValues(): void
+    {
+        if (!$this->buffer && $this->hasNext) {
+            $this->getNext();
+        }
+        $this->values = null;
+        if ($this->buffer) {
+            $this->read($this->buffer);
+            $this->buffer = null;
+        }
+    }
+
+    private function write(MessageBuffer $buffer): void
+    {
+        $buffer->writeLong($this->id);
+    }
+
+    public function readId(MessageBuffer $buffer): void
+    {
+        $this->id = $buffer->readLong();
+    }
+
+    protected function readRow(MessageBuffer $buffer)
+    {
+        return new CacheEntry(
+            $this->communicator->readObject($buffer, $this->keyType),
+            $this->communicator->readObject($buffer, $this->valueType));
+    }
+
+    private function read(MessageBuffer $buffer): void
+    {
+        $rowCount = $buffer->readInteger();
+        $this->values = [];
+        for ($i = 0; $i < $rowCount; $i++) {
+            array_push($this->values, $this->readRow($buffer));
+        }
+        $this->hasNext = $buffer->readBoolean();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Internal/Query/SqlFieldsCursor.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Query/SqlFieldsCursor.php b/modules/platforms/php/src/Apache/Ignite/Internal/Query/SqlFieldsCursor.php
new file mode 100644
index 0000000..e876baf
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Internal/Query/SqlFieldsCursor.php
@@ -0,0 +1,75 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Internal\Query;
+
+use Apache\Ignite\Query\SqlFieldsCursorInterface;
+use Apache\Ignite\Internal\Binary\ClientOperation;
+use Apache\Ignite\Internal\Binary\MessageBuffer;
+use Apache\Ignite\Internal\Binary\BinaryCommunicator;
+use Apache\Ignite\Internal\Binary\BinaryUtils;
+
+class SqlFieldsCursor extends Cursor implements SqlFieldsCursorInterface
+{
+    private $fieldCount;
+    private $fieldNames;
+    private $fieldTypes;
+    
+    public function __construct(BinaryCommunicator $communicator, MessageBuffer $buffer)
+    {
+        parent::__construct($communicator, ClientOperation::QUERY_SQL_FIELDS_CURSOR_GET_PAGE, $buffer);
+        $this->fieldCount = 0;
+        $this->fieldNames = [];
+        $this->fieldTypes = null;
+    }
+
+    public function getFieldNames(): array
+    {
+        return $this->fieldNames;
+    }
+
+    public function setFieldTypes(...$fieldTypes): SqlFieldsCursorInterface
+    {
+        foreach ($fieldTypes as $fieldType) {
+            BinaryUtils::checkObjectType($fieldType, 'fieldTypes');
+        }
+        $this->fieldTypes = $fieldTypes;
+        return $this;
+    }
+
+    public function readFieldNames(MessageBuffer $buffer, bool $includeFieldNames): void
+    {
+        $this->id = $buffer->readLong();
+        $this->fieldCount = $buffer->readInteger();
+        if ($includeFieldNames) {
+            for ($i = 0; $i < $this->fieldCount; $i++) {
+                array_push($this->fieldNames, $this->communicator->readObject($buffer));
+            }
+        }
+    }
+
+    protected function readRow(MessageBuffer $buffer)
+    {
+        $values = [];
+        for ($i = 0; $i < $this->fieldCount; $i++) {
+            $fieldType = $this->fieldTypes && $i < count($this->fieldTypes) ? $this->fieldTypes[$i] : null;
+            array_push($values, $this->communicator->readObject($buffer, $fieldType));
+        }
+        return $values;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Internal/Utils/ArgumentChecker.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Utils/ArgumentChecker.php b/modules/platforms/php/src/Apache/Ignite/Internal/Utils/ArgumentChecker.php
new file mode 100644
index 0000000..bd04f10
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Internal/Utils/ArgumentChecker.php
@@ -0,0 +1,87 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Internal\Utils;
+
+use Apache\Ignite\Exception\ClientException;
+
+class ArgumentChecker
+{
+    public static function notEmpty($arg, string $argName): void
+    {
+        if (empty($arg)) {
+            ArgumentChecker::illegalArgument(sprintf('"%s" argument should not be empty', $argName));
+        }
+    }
+
+    public static function notNull($arg, string $argName): void
+    {
+        if (is_null($arg)) {
+            ArgumentChecker::illegalArgument(sprintf('"%s" argument should not be null', $argName));
+        }
+    }
+
+    public static function hasType($arg, string $argName, bool $isArray, ...$types): void
+    {
+        if ($arg === null) {
+            return;
+        }
+        if ($isArray && is_array($arg)) {
+            foreach ($arg as $a) {
+                ArgumentChecker::hasType($a, $argName, false, ...$types);
+            }
+        } else {
+            foreach ($types as $type) {
+                if ($arg instanceof $type) {
+                    return;
+                }
+            }
+            ArgumentChecker::illegalArgument(sprintf('"%s" argument has incorrect type', $argName));
+        }
+    }
+
+    public static function hasValueFrom($arg, string $argName, bool $isArray, array $values): void
+    {
+        if ($isArray && is_array($arg)) {
+            foreach ($arg as $a) {
+                ArgumentChecker::hasValueFrom($a, $argName, false, $values);
+            }
+        } else {
+            if (!in_array($arg, $values)) {
+                ArgumentChecker::invalidValue($argName);
+            }
+        }
+    }
+
+    public static function invalidValue(string $argName): void
+    {
+        ArgumentChecker::illegalArgument(sprintf('"%s" argument has incorrect value', $argName));
+    }
+
+    public static function invalidArgument($arg, string $argName, string $typeName): void
+    {
+        if ($arg !== null) {
+            ArgumentChecker::illegalArgument(sprintf('"%s" argument is invalid for %s', $argName, $typeName));
+        }
+    }
+
+    public static function illegalArgument($message): void
+    {
+        throw new ClientException($message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Internal/Utils/Logger.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Internal/Utils/Logger.php b/modules/platforms/php/src/Apache/Ignite/Internal/Utils/Logger.php
new file mode 100644
index 0000000..1faf9ca
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Internal/Utils/Logger.php
@@ -0,0 +1,62 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Internal\Utils;
+
+use Apache\Ignite\Internal\Binary\MessageBuffer;
+
+/** Utility class for logging errors and debug messages. */
+class Logger
+{
+    private static $debug = false;
+    
+    public static function isDebug(): bool
+    {
+        return Logger::$debug;
+    }
+    
+    public static function setDebug(bool $debug): void
+    {
+        Logger::$debug = $debug;
+    }
+    
+    public static function logDebug($data, ...$args): void
+    {
+        if (Logger::$debug) {
+            echo(sprintf($data, ...$args) . PHP_EOL);
+        }
+    }
+    
+    public static function logError($data, ...$args): void
+    {
+        if (Logger::$debug) {
+            echo(sprintf("ERROR: $data", ...$args) . PHP_EOL);
+        }
+    }
+    
+    public static function logBuffer(MessageBuffer $buffer, int $startPos = 0, int $length = -1): void
+    {
+        if (Logger::$debug) {
+            if ($length < 0) {
+                $length = $buffer->getLength();
+            }
+            $message = $buffer->getSlice($startPos, $length);
+            Logger::logDebug('[' . implode(',', array_map('ord', str_split($message))) . ']');
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Query/CursorInterface.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Query/CursorInterface.php b/modules/platforms/php/src/Apache/Ignite/Query/CursorInterface.php
new file mode 100644
index 0000000..1525e28
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Query/CursorInterface.php
@@ -0,0 +1,56 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Query;
+
+/**
+ * Interface representing a cursor to obtain results of SQL and Scan query operations.
+ *
+ * An instance of the class with this interface should be obtained via query() method
+ * of an object with CacheInterface.
+ * One instance of the class with CursorInterface returns results of one SQL or Scan query operation.
+ *
+ * CursorInterface extends the PHP Iterator interface.
+ * The PHP Iterator's methods may be used to obtain the results of the query (cache entries, i.e. key-value pairs)
+ * one by one.
+ * Also, the cursor can be placed into the "foreach" PHP loop to easily iterate over all the results.
+ *
+ * Additionally, CursorInterface includes getAll() method to get all the results at once
+ * and close() method to prematurely close the cursor.
+ *
+ */
+interface CursorInterface extends \Iterator
+{
+    /**
+     * Returns all elements (cache entries, i.e. key-value pairs) from the query results at once.
+     *
+     * May be used instead of the PHP Iterator's methods if the number of returned entries
+     * is relatively small and will not cause memory utilization issues.
+     *
+     * @return array all cache entries (key-value pairs) returned by SQL or Scan query.
+     */
+    public function getAll(): array;
+
+    /**
+     * Closes the cursor. Obtaining elements from the results is not possible after this.
+     *
+     * This method should be called if no more elements are needed.
+     * It is not necessary to call it if all elements have been already obtained.
+     */
+    public function close(): void;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Query/Query.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Query/Query.php b/modules/platforms/php/src/Apache/Ignite/Query/Query.php
new file mode 100644
index 0000000..2cd3aec
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Query/Query.php
@@ -0,0 +1,70 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Query;
+
+/**
+ * Base class representing an Ignite SQL or Scan query.
+ *
+ * The class is abstract, only subclasses may be instantiated.
+ */
+abstract class Query
+{
+    const PAGE_SIZE_DEFAULT = 1024;
+    
+    protected $local;
+    protected $operation;
+    protected $pageSize;
+    
+    protected function __construct(int $operation)
+    {
+        $this->operation = $operation;
+        $this->local = false;
+        $this->pageSize = Query::PAGE_SIZE_DEFAULT;
+    }
+
+    /**
+     * Set local query flag.
+     * 
+     * @param bool $local local query flag: true or false.
+     * @return Query the same instance of the Query.
+     */
+    public function setLocal(bool $local): Query
+    {
+        $this->local = $local;
+        return $this;
+    }
+
+    /**
+     * Set cursor page size.
+     * 
+     * @param int $pageSize cursor page size.
+     * @return Query the same instance of the Query.
+     */
+    public function setPageSize(int $pageSize): Query
+    {
+        $this->pageSize = $pageSize;
+        return $this;
+    }
+
+    // This is not the public API method, is not intended for usage by an application.
+    public function getOperation(): int
+    {
+        return $this->operation;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Query/ScanQuery.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Query/ScanQuery.php b/modules/platforms/php/src/Apache/Ignite/Query/ScanQuery.php
new file mode 100644
index 0000000..85391b6
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Query/ScanQuery.php
@@ -0,0 +1,88 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Query;
+
+use Apache\Ignite\Exception\ClientException;
+use Apache\Ignite\Internal\Binary\ClientOperation;
+use Apache\Ignite\Internal\Binary\MessageBuffer;
+use Apache\Ignite\Internal\Binary\BinaryCommunicator;
+use Apache\Ignite\Internal\Query\Cursor;
+
+/**
+ * Class representing a Scan query which returns the whole cache entries (key-value pairs).
+ *
+ * This version of the class does not support a possibility to specify a Filter object for the query.
+ * The query returns all entries from the entire cache or from the specified partition.
+ */
+class ScanQuery extends Query
+{
+    private $partitionNumber;
+    
+    /**
+     * Public constructor.
+     *
+     * Scan query settings have the following defaults:
+     * <pre>
+     *     Scan Query setting        :    Default value
+     *     Local query flag          :    false
+     *     Cursor page size          :    1024
+     *     Partition number          :    -1 (entire cache)
+     *     Filter object             :    null (not supported)
+     * </pre>
+     * Every setting (except Filter object) may be changed using set methods.
+     */
+    public function __construct()
+    {
+        parent::__construct(ClientOperation::QUERY_SCAN);
+        $this->partitionNumber = -1;
+    }
+
+    /**
+     * Sets a partition number over which this query should iterate.
+     *
+     * If negative, the query will iterate over all partitions in the cache. 
+     * 
+     * @param int $partitionNumber partition number over which this query should iterate.
+     * 
+     * @return ScanQuery the same instance of the ScanQuery.
+     */
+    public function setPartitionNumber(int $partitionNumber): ScanQuery
+    {
+        $this->partitionNumber = $partitionNumber;
+        return $this;
+    }
+
+    // This is not the public API method, is not intended for usage by an application.
+    public function write(BinaryCommunicator $communicator, MessageBuffer $buffer): void
+    {
+        // filter
+        $communicator->writeObject($buffer, null);
+        $buffer->writeInteger($this->pageSize);
+        $buffer->writeInteger($this->partitionNumber);
+        $buffer->writeBoolean($this->local);
+    }
+
+    // This is not the public API method, is not intended for usage by an application.
+    public function getCursor(BinaryCommunicator $communicator, MessageBuffer $payload, $keyType = null, $valueType = null): CursorInterface
+    {
+        $cursor = new Cursor($communicator, ClientOperation::QUERY_SCAN_CURSOR_GET_PAGE, $payload, $keyType, $valueType);
+        $cursor->readId($payload);
+        return $cursor;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Query/SqlFieldsCursorInterface.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Query/SqlFieldsCursorInterface.php b/modules/platforms/php/src/Apache/Ignite/Query/SqlFieldsCursorInterface.php
new file mode 100644
index 0000000..77086bb
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Query/SqlFieldsCursorInterface.php
@@ -0,0 +1,82 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Query;
+
+use Apache\Ignite\Type\ObjectType;
+
+/**
+ * Interface representing a cursor to obtain results of SQL Fields query operation.
+ *
+ * An instance of the class with this interface should be obtained via query() method
+ * of an object with CacheInterface.
+ * One instance of the class with SqlFieldsCursorInterface returns results of one SQL Fields query operation.
+ *
+ * SqlFieldsCursorInterface extends CursorInterface which extends the PHP Iterator interface.
+ * The PHP Iterator's methods may be used to obtain the results of the query (arrays with values of the fields)
+ * one by one.
+ * Also, the cursor can be placed into the "foreach" PHP loop to easily iterate over all the results.
+ *
+ * Additionally, SqlFieldsCursorInterface includes
+ * getAll() method to get all the results at once,
+ * getFieldNames() method to return names of the fields,
+ * setFieldTypes() method to specify Ignite types of the fields,
+ * and close() method (defined in CursorInterface) to prematurely close the cursor.
+ *
+ */
+interface SqlFieldsCursorInterface extends CursorInterface
+{
+    /**
+     * Returns all elements (arrays with values of the fields) from the query results at once.
+     *
+     * May be used instead of the PHP Iterator's methods if the number of returned elements
+     * is relatively small and will not cause memory utilization issues.
+     *
+     * @return array all results returned by SQL Fields query.
+     *   Every element of the array is an array with values of the fields requested by the query.
+     */
+    public function getAll(): array;
+    
+    /**
+     * Returns names of the fields which were requested in the SQL Fields query.
+     *
+     * Empty array is returned if "include field names" flag was false in the query.
+     *
+     * @return array field names.
+     *   The order of names corresponds to the order of field values returned in the results of the query.
+     */
+    public function getFieldNames(): array;
+
+    /**
+     * Specifies Ignite types of the fields returned by the SQL Fields query.
+     *
+     * By default, an Ignite type of every field is not specified that means during operations Ignite client
+     * tries to make automatic mapping between PHP types and Ignite object types -
+     * according to the mapping table defined in the description of the ObjectType class.
+     *
+     * @param int|ObjectType|null ...$fieldTypes Ignite types of the returned fields.
+     *   The order of types must correspond the order of field values returned in the results of the query.
+     *   A type of every field can be:
+     *   - either a type code of primitive (simple) type (@ref PrimitiveTypeCodes)
+     *   - or an instance of class representing non-primitive (composite) type
+     *   - or null (or not specified) that means the type is not specified
+     *
+     * @return SqlFieldsCursorInterface the same instance of the class with SqlFieldsCursorInterface.
+     */
+    public function setFieldTypes(...$fieldTypes): SqlFieldsCursorInterface;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Query/SqlFieldsQuery.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Query/SqlFieldsQuery.php b/modules/platforms/php/src/Apache/Ignite/Query/SqlFieldsQuery.php
new file mode 100644
index 0000000..6025c64
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Query/SqlFieldsQuery.php
@@ -0,0 +1,206 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Query;
+
+use Apache\Ignite\Exception\ClientException;
+use Apache\Ignite\Internal\Binary\ClientOperation;
+use Apache\Ignite\Internal\Binary\MessageBuffer;
+use Apache\Ignite\Internal\Binary\BinaryCommunicator;
+use Apache\Ignite\Internal\Query\SqlFieldsCursor;
+
+/**
+ * Class representing an SQL Fields query.
+ */
+class SqlFieldsQuery extends SqlQuery
+{
+    /** @name SqlFieldsQueryStatementType
+     *  @anchor SqlFieldsQueryStatementType
+     *  @{
+     */
+    const STATEMENT_TYPE_ANY = 0;
+    const STATEMENT_TYPE_SELECT = 1;
+    const STATEMENT_TYPE_UPDATE = 2;
+    /** @} */ // end of SqlFieldsQueryStatementType
+    
+    private $schema;
+    private $maxRows;
+    private $statementType;
+    private $enforceJoinOrder;
+    private $collocated;
+    private $lazy;
+    private $includeFieldNames;
+    
+    /**
+     * Public constructor.
+     *
+     * Requires SQL query string to be specified.
+     * Other SQL Fields query settings have the following defaults:
+     * <pre>
+     *     SQL Fields Query setting  :    Default value
+     *     Local query flag          :    false
+     *     Cursor page size          :    1024
+     *     Query arguments           :    not specified
+     *     Distributed joins flag    :    false
+     *     Replicated only flag      :    false
+     *     Timeout                   :    0 (disabled)
+     *     Schema for the query      :    not specified
+     *     Max rows                  :    -1
+     *     Statement type            :    STATEMENT_TYPE_ANY
+     *     Enforce join order flag   :    false
+     *     Collocated flag           :    false
+     *     Lazy query execution flag :    false
+     *     Include field names flag  :    false
+     * </pre>
+     * Every setting may be changed using set methods.
+     * 
+     * @param string $sql SQL query string.
+     *
+     * @throws ClientException if error.
+     */
+    public function __construct(string $sql)
+    {
+        parent::__construct(null, $sql);
+        $this->operation = ClientOperation::QUERY_SQL_FIELDS;
+        $this->schema = null;
+        $this->maxRows = -1;
+        $this->statementType = SqlFieldsQuery::STATEMENT_TYPE_ANY;
+        $this->enforceJoinOrder = false;
+        $this->collocated = false;
+        $this->lazy = false;
+        $this->includeFieldNames = false;
+    }
+
+    /**
+     * Set schema for the query.
+     * 
+     * @param string $schema schema for the query.
+     * 
+     * @return SqlFieldsQuery the same instance of the SqlFieldsQuery.
+     */
+    public function setSchema(string $schema): SqlFieldsQuery
+    {
+        $this->schema = $schema;
+        return $this;
+    }
+
+    /**
+     * Set max rows.
+     * 
+     * @param int $maxRows max rows.
+     * 
+     * @return SqlFieldsQuery the same instance of the SqlFieldsQuery.
+     */
+    public function setMaxRows(int $maxRows): SqlFieldsQuery
+    {
+        $this->maxRows = $maxRows;
+        return $this;
+    }
+
+    /**
+     * Set statement type.
+     * 
+     * @param int $type statement type, one of @ref SqlFieldsQueryStatementType constants.
+     * 
+     * @return SqlFieldsQuery the same instance of the SqlFieldsQuery.
+     */
+    public function setStatementType(int $type): SqlFieldsQuery
+    {
+        $this->statementType = $type;
+        return $this;
+    }
+
+    /**
+     * Set enforce join order flag.
+     * 
+     * @param bool $enforceJoinOrder enforce join order flag: true or false.
+     * 
+     * @return SqlFieldsQuery the same instance of the SqlFieldsQuery.
+     */
+    public function setEnforceJoinOrder(bool $enforceJoinOrder): SqlFieldsQuery
+    {
+        $this->enforceJoinOrder = $enforceJoinOrder;
+        return $this;
+    }
+
+    /**
+     * Set collocated flag.
+     * 
+     * @param bool $collocated collocated flag: true or false.
+     * 
+     * @return SqlFieldsQuery the same instance of the SqlFieldsQuery.
+     */
+    public function setCollocated(bool $collocated): SqlFieldsQuery
+    {
+        $this->collocated = $collocated;
+        return $this;
+    }
+
+    /**
+     * Set lazy query execution flag.
+     * 
+     * @param bool $lazy lazy query execution flag: true or false.
+     * 
+     * @return SqlFieldsQuery the same instance of the SqlFieldsQuery.
+     */
+    public function setLazy(bool $lazy): SqlFieldsQuery
+    {
+        $this->lazy = $lazy;
+        return $this;
+    }
+
+    /**
+     * Set include field names flag.
+     * 
+     * @param bool $includeFieldNames include field names flag: true or false.
+     * 
+     * @return SqlFieldsQuery the same instance of the SqlFieldsQuery.
+     */
+    public function setIncludeFieldNames(bool $includeFieldNames): SqlFieldsQuery
+    {
+        $this->includeFieldNames = $includeFieldNames;
+        return $this;
+    }
+
+    // This is not the public API method, is not intended for usage by an application.
+    public function write(BinaryCommunicator $communicator, MessageBuffer $buffer): void
+    {
+        BinaryCommunicator::writeString($buffer, $this->schema);
+        $buffer->writeInteger($this->pageSize);
+        $buffer->writeInteger($this->maxRows);
+        BinaryCommunicator::writeString($buffer, $this->sql);
+        $this->writeArgs($communicator, $buffer);
+        $buffer->writeByte($this->statementType);
+        $buffer->writeBoolean($this->distributedJoins);
+        $buffer->writeBoolean($this->local);
+        $buffer->writeBoolean($this->replicatedOnly);
+        $buffer->writeBoolean($this->enforceJoinOrder);
+        $buffer->writeBoolean($this->collocated);
+        $buffer->writeBoolean($this->lazy);
+        $buffer->writeLong($this->timeout);
+        $buffer->writeBoolean($this->includeFieldNames);
+    }
+
+    // This is not the public API method, is not intended for usage by an application.
+    public function getCursor(BinaryCommunicator $communicator, MessageBuffer $payload, $keyType = null, $valueType = null): CursorInterface
+    {
+        $cursor = new SqlFieldsCursor($communicator, $payload);
+        $cursor->readFieldNames($payload, $this->includeFieldNames);
+        return $cursor;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f28e4f3f/modules/platforms/php/src/Apache/Ignite/Query/SqlQuery.php
----------------------------------------------------------------------
diff --git a/modules/platforms/php/src/Apache/Ignite/Query/SqlQuery.php b/modules/platforms/php/src/Apache/Ignite/Query/SqlQuery.php
new file mode 100644
index 0000000..50cc08b
--- /dev/null
+++ b/modules/platforms/php/src/Apache/Ignite/Query/SqlQuery.php
@@ -0,0 +1,225 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+namespace Apache\Ignite\Query;
+
+use Apache\Ignite\Type\ObjectType;
+use Apache\Ignite\Exception\ClientException;
+use Apache\Ignite\Internal\Binary\ClientOperation;
+use Apache\Ignite\Internal\Binary\MessageBuffer;
+use Apache\Ignite\Internal\Binary\BinaryCommunicator;
+use Apache\Ignite\Internal\Binary\BinaryUtils;
+use Apache\Ignite\Internal\Query\Cursor;
+use Apache\Ignite\Internal\Utils\ArgumentChecker;
+
+/**
+ * Class representing an SQL query which returns the whole cache entries (key-value pairs).
+ */
+class SqlQuery extends Query
+{
+    private $args;
+    private $argTypes;
+    protected $sql;
+    protected $type;
+    protected $distributedJoins;
+    protected $replicatedOnly;
+    protected $timeout;
+
+    /**
+     * Public constructor.
+     *
+     * Requires name of a type (or SQL table) and SQL query string to be specified.
+     * Other SQL query settings have the following defaults:
+     * <pre>
+     *     SQL Query setting         :    Default value
+     *     Local query flag          :    false
+     *     Cursor page size          :    1024
+     *     Query arguments           :    not specified
+     *     Distributed joins flag    :    false
+     *     Replicated only flag      :    false
+     *     Timeout                   :    0 (disabled)
+     * </pre>
+     * Every setting may be changed using set methods.
+     *
+     * @param string $type name of a type or SQL table.
+     * @param string $sql SQL query string.
+     * 
+     * @throws ClientException if error.
+     */
+    public function __construct(?string $type, string $sql)
+    {
+        parent::__construct(ClientOperation::QUERY_SQL);
+        $this->setType($type);
+        $this->setSql($sql);
+        $this->args = null;
+        $this->argTypes = null;
+        $this->distributedJoins = false;
+        $this->replicatedOnly = false;
+        $this->timeout = 0;
+    }
+
+    /**
+     * Set name of a type or SQL table.
+     *
+     * @param string $type name of a type or SQL table.
+     *
+     * @return SqlQuery the same instance of the SqlQuery.
+     *
+     * @throws ClientException if error.
+     */
+    public function setType(?string $type): SqlQuery
+    {
+        if ($this instanceof SqlFieldsQuery) {
+            ArgumentChecker::invalidArgument($type, 'type', SqlFieldsQuery::class);
+        }
+        $this->type = $type;
+        return $this;
+    }
+
+    /**
+     * Set SQL query string.
+     * 
+     * @param string $sql SQL query string.
+     * 
+     * @return SqlQuery the same instance of the SqlQuery.
+     */
+    public function setSql(string $sql): SqlQuery
+    {
+        $this->sql = $sql;
+        return $this;
+    }
+
+    /**
+     * Set query arguments.
+     *
+     * Type of any argument may be specified using setArgTypes() method.
+     * If type of an argument is not specified then during operations the Ignite client
+     * will try to make automatic mapping between PHP types and Ignite object types -
+     * according to the mapping table defined in the description of the ObjectType class.
+     * 
+     * @param mixed ...$args Query arguments.
+     * 
+     * @return SqlQuery the same instance of the SqlQuery.
+     */
+    public function setArgs(...$args): SqlQuery
+    {
+        $this->args = $args;
+        return $this;
+    }
+
+    /**
+     * Specifies types of query arguments.
+     *
+     * Query arguments itself are set using setArgs() method.
+     * By default, a type of every argument is not specified that means during operations the Ignite client
+     * will try to make automatic mapping between PHP types and Ignite object types -
+     * according to the mapping table defined in the description of the ObjectType class.
+     * 
+     * @param int|ObjectType|null ...$argTypes types of Query arguments.
+     *   The order of types must follow the order of arguments in the setArgs() method.
+     *   A type of every argument can be:
+     *   - either a type code of primitive (simple) type (@ref PrimitiveTypeCodes)
+     *   - or an instance of class representing non-primitive (composite) type
+     *   - or null (or not specified) that means the type is not specified
+     * 
+     * @return SqlQuery the same instance of the SqlQuery.
+     *
+     * @throws ClientException if error.
+     */
+    public function setArgTypes(...$argTypes): SqlQuery
+    {
+        foreach ($argTypes as $argType) {
+            BinaryUtils::checkObjectType($argType, 'argTypes');
+        }
+        $this->argTypes = $argTypes;
+        return $this;
+    }
+
+    /**
+     * Set distributed joins flag.
+     * 
+     * @param bool $distributedJoins distributed joins flag: true or false.
+     * 
+     * @return SqlQuery the same instance of the SqlQuery.
+     */
+    public function setDistributedJoins(bool $distributedJoins): SqlQuery
+    {
+        $this->distributedJoins = $distributedJoins;
+        return $this;
+    }
+
+    /**
+     * Set replicated only flag.
+     * 
+     * @param bool $replicatedOnly replicated only flag: true or false.
+     * 
+     * @return SqlQuery the same instance of the SqlQuery.
+     */
+    public function setReplicatedOnly(bool $replicatedOnly): SqlQuery
+    {
+        $this->replicatedOnly = $replicatedOnly;
+        return $this;
+    }
+
+    /**
+     * Set timeout.
+     * 
+     * @param float $timeout timeout value in milliseconds.
+     *   Must be non-negative. Zero value disables timeout.
+     * 
+     * @return SqlQuery the same instance of the SqlQuery.
+     */
+    public function setTimeout(float $timeout): SqlQuery
+    {
+        $this->timeout = $timeout;
+        return $this;
+    }
+
+    protected function writeArgs(BinaryCommunicator $communicator, MessageBuffer $buffer): void
+    {
+        $argsLength = $this->args ? count($this->args) : 0;
+        $buffer->writeInteger($argsLength);
+        if ($argsLength > 0) {
+            for ($i = 0; $i < $argsLength; $i++) {
+                $argType = $this->argTypes && $i < count($this->argTypes) ? $this->argTypes[$i] : null;
+                $communicator->writeObject($buffer, $this->args[$i], $argType);
+            }
+        }
+    }
+
+    // This is not the public API method, is not intended for usage by an application.
+    public function write(BinaryCommunicator $communicator, MessageBuffer $buffer): void
+    {
+        BinaryCommunicator::writeString($buffer, $this->type);
+        BinaryCommunicator::writeString($buffer, $this->sql);
+        $this->writeArgs($communicator, $buffer);
+        $buffer->writeBoolean($this->distributedJoins);
+        $buffer->writeBoolean($this->local);
+        $buffer->writeBoolean($this->replicatedOnly);
+        $buffer->writeInteger($this->pageSize);
+        $buffer->writeLong($this->timeout);
+    }
+
+    // This is not the public API method, is not intended for usage by an application.
+    public function getCursor(BinaryCommunicator $communicator, MessageBuffer $payload, $keyType = null, $valueType = null): CursorInterface
+    {
+        $cursor = new Cursor($communicator, ClientOperation::QUERY_SQL_CURSOR_GET_PAGE, $payload, $keyType, $valueType);
+        $cursor->readId($payload);
+        return $cursor;
+    }
+}