├── .gitignore ├── .travis.yml ├── tests ├── bootstrap.php ├── base │ └── Stringy.php ├── MapperTest.php ├── TokenizerTest.php ├── SchemaTest.php ├── DocumentTest.php ├── QueryTest.php ├── GatewayTest.php └── IndexTest.php ├── phpunit.xml ├── composer.json ├── examples ├── schemas │ ├── PersonSchema.php │ ├── PubSchema.php │ └── BookSchema.php ├── put_person_with_schema.php ├── put_with_schema.php ├── put_one.php └── put_geo_with_schema.php ├── src └── Search │ ├── Tokenizer.php │ ├── Index.php │ ├── Document.php │ ├── Schema.php │ ├── Mapper.php │ ├── Query.php │ └── Gateway.php ├── LICENSE ├── README.md └── libs └── google └── document_pb.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | build/ 3 | app.yaml 4 | composer.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.6 6 | - 7.0 7 | 8 | before_script: 9 | - composer self-update 10 | - composer install --prefer-source 11 | 12 | script: 13 | - mkdir -p build/logs 14 | - php vendor/bin/phpunit 15 | 16 | after_script: 17 | - php vendor/bin/coveralls -v 18 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | // Time zone 9 | date_default_timezone_set('UTC'); 10 | 11 | // Autoloader 12 | require_once(dirname(__FILE__) . '/../vendor/autoload.php'); 13 | 14 | // Base Test Files 15 | require_once(dirname(__FILE__) . '/base/Stringy.php'); -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests 5 | 6 | 7 | 8 | vendor/google/appengine-php-sdk 9 | 10 | 11 | 12 | 13 | 14 | 15 | libs 16 | vendor 17 | tests 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tomwalder/php-appengine-search", 3 | "type": "library", 4 | "description": "Google App Engine Search Library for PHP", 5 | "keywords": ["google", "appengine", "gae", "search", "full text"], 6 | "homepage": "https://github.com/tomwalder/php-appengine-search", 7 | "license": "Apache-2.0", 8 | "authors": [ 9 | { 10 | "name": "Tom Walder", 11 | "email": "tom@docnet.nu" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.5.0" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "~4.0", 19 | "satooshi/php-coveralls": "dev-master", 20 | "google/appengine-php-sdk": "dev-master" 21 | }, 22 | "autoload": { 23 | "classmap": [ 24 | "src/", 25 | "libs/" 26 | ], 27 | "psr-4": {"Search\\": "src/Search/"} 28 | }, 29 | "include-path": ["src/"] 30 | } 31 | -------------------------------------------------------------------------------- /tests/base/Stringy.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class Stringy 24 | { 25 | /** 26 | * Return a string 27 | * 28 | * @return string 29 | */ 30 | public function __toString() 31 | { 32 | return 'success!'; 33 | } 34 | } -------------------------------------------------------------------------------- /tests/MapperTest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class MapperTest extends \PHPUnit_Framework_TestCase 24 | { 25 | 26 | /** 27 | * Can we create a Mapper? 28 | */ 29 | public function testExists() 30 | { 31 | $obj_mapper = new \Search\Mapper(); 32 | $this->assertInstanceOf('\\Search\\Mapper', $obj_mapper); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /examples/schemas/PersonSchema.php: -------------------------------------------------------------------------------- 1 | addText('name') 32 | ->addNumber('age') 33 | ->addDate('dob'); 34 | } 35 | } -------------------------------------------------------------------------------- /examples/schemas/PubSchema.php: -------------------------------------------------------------------------------- 1 | addText('name') 32 | ->addGeopoint('location') 33 | ->addNumber('rating'); 34 | } 35 | } -------------------------------------------------------------------------------- /examples/schemas/BookSchema.php: -------------------------------------------------------------------------------- 1 | addAtom('isbn') 32 | ->addText('title') 33 | ->addText('author') 34 | ->addNumber('price'); 35 | } 36 | } -------------------------------------------------------------------------------- /examples/put_person_with_schema.php: -------------------------------------------------------------------------------- 1 | put($obj_person_schema->createDocument([ 30 | 'name' => 'Marty McFly Jnr', 31 | 'age' => 0, 32 | 'dob' => new DateTime() 33 | ])); 34 | 35 | echo "OK"; 36 | 37 | } catch (\Exception $obj_ex) { 38 | echo $obj_ex->getMessage(); 39 | syslog(LOG_CRIT, $obj_ex->getMessage()); 40 | } -------------------------------------------------------------------------------- /examples/put_with_schema.php: -------------------------------------------------------------------------------- 1 | createDocument([ 25 | 'title' => 'Romeo and Juliet', 26 | 'author' => 'William Shakespeare', 27 | 'isbn' => '1840224339', 28 | 'price' => 9.99 29 | ]); 30 | 31 | // Insert - we'll need an index 32 | $obj_index = new \Search\Index('library'); 33 | $obj_index->put($obj_book); 34 | 35 | echo "OK"; 36 | 37 | } catch (\Exception $obj_ex) { 38 | echo $obj_ex->getMessage(); 39 | syslog(LOG_CRIT, $obj_ex->getMessage()); 40 | } -------------------------------------------------------------------------------- /examples/put_one.php: -------------------------------------------------------------------------------- 1 | addAtom('isbn') 27 | ->addText('title') 28 | ->addText('author') 29 | ->addNumber('price'); 30 | 31 | // Create Book (FROM SCHEMA) 32 | $obj_book1 = $obj_book_schema->createDocument(); 33 | $obj_book1->title = 'Romeo and Juliet'; 34 | $obj_book1->author = 'William Shakespeare'; 35 | $obj_book1->isbn = '1840224339'; 36 | $obj_book1->price = 9.99; 37 | 38 | // Insert 39 | $obj_index->put($obj_book1); 40 | 41 | echo "OK"; 42 | 43 | } catch (\Exception $obj_ex) { 44 | echo $obj_ex->getMessage(); 45 | syslog(LOG_CRIT, $obj_ex->getMessage()); 46 | } -------------------------------------------------------------------------------- /examples/put_geo_with_schema.php: -------------------------------------------------------------------------------- 1 | createDocument([ 30 | 'name' => 'Euston Tap', 31 | 'location' => [51.5269059, -0.1325679], 32 | 'rating' => 5 33 | ]); 34 | 35 | // Second pub 36 | $obj_pub2 = $obj_pub_schema->createDocument([ 37 | 'name' => 'Kim by the Sea', 38 | 'location' => [53.4653381, -2.2483717], 39 | 'rating' => 3 40 | ]); 41 | 42 | // Insert 43 | $obj_index->put([$obj_pub1, $obj_pub2]); 44 | 45 | echo "OK"; 46 | 47 | } catch (\Exception $obj_ex) { 48 | echo $obj_ex->getMessage(); 49 | syslog(LOG_CRIT, $obj_ex->getMessage()); 50 | } -------------------------------------------------------------------------------- /tests/TokenizerTest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class TokenizerTest extends \PHPUnit_Framework_TestCase 24 | { 25 | 26 | /** 27 | * Can we create a Tokenizer? 28 | */ 29 | public function testExists() 30 | { 31 | $obj_tkzr = new \Search\Tokenizer(); 32 | $this->assertInstanceOf('\\Search\\Tokenizer', $obj_tkzr); 33 | } 34 | 35 | public function testBasicOneWord() 36 | { 37 | $obj_tkzr = new \Search\Tokenizer(); 38 | $this->assertEquals('test t te tes', $obj_tkzr->edgeNGram('test')); 39 | } 40 | 41 | public function testBasicTwoWord() 42 | { 43 | $obj_tkzr = new \Search\Tokenizer(); 44 | $this->assertEquals('two words t tw w wo wor word', $obj_tkzr->edgeNGram('two words')); 45 | } 46 | 47 | public function testTwoWordMinLen() 48 | { 49 | $obj_tkzr = new \Search\Tokenizer(); 50 | $this->assertEquals('two words tw wo wor word', $obj_tkzr->edgeNGram('two words', 2)); 51 | $this->assertEquals('two words wor word', $obj_tkzr->edgeNGram('two words', 3)); 52 | } 53 | 54 | public function testAlpha() 55 | { 56 | $obj_tkzr = new \Search\Tokenizer(); 57 | $this->assertEquals('test 123 t te tes 1 12', $obj_tkzr->edgeNGram('test 123')); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/Search/Tokenizer.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class Tokenizer 25 | { 26 | /** 27 | * Change an input string into a list of edge N-Gram tokens for autocomplete usage 28 | * 29 | * @param $str_phrase 30 | * @param $int_min_length 31 | * @return string 32 | */ 33 | public function edgeNGram($str_phrase, $int_min_length = 1) 34 | { 35 | $arr_tokens = []; 36 | 37 | // Clean-up unwanted characters (assume english for now) 38 | $str_phrase = preg_replace('#[^a-z0-9 ]#i', '', $str_phrase); 39 | 40 | // @todo move this to non-edge ngram function when we have one. 41 | // Do we need individual characters? 42 | // if($int_min_length < 2) { 43 | // $arr_chars = str_split($str_phrase); 44 | // $arr_tokens = array_merge($arr_tokens, $arr_chars); 45 | // } 46 | 47 | // Add the words 48 | $arr_words = explode(' ', $str_phrase); 49 | $arr_tokens = array_merge($arr_tokens, $arr_words); 50 | 51 | // OK, now split the words 52 | foreach($arr_words as $str_word) { 53 | $int_letters = strlen($str_word); 54 | $arr_ngrams = []; 55 | for($int_subs = $int_min_length; $int_subs <= ($int_letters - 1); $int_subs++) { 56 | $arr_ngrams[] = substr($str_word, 0, $int_subs); 57 | } 58 | $arr_tokens = array_merge($arr_tokens, $arr_ngrams); 59 | } 60 | 61 | // Clean up and return 62 | return str_replace(' ', ' ', implode(' ', $arr_tokens)); 63 | } 64 | } -------------------------------------------------------------------------------- /tests/SchemaTest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class SchemaTest extends \PHPUnit_Framework_TestCase { 24 | 25 | /** 26 | * Set up a schema with all data types 27 | */ 28 | public function testSchema() 29 | { 30 | $obj_schema = (new \Search\Schema()) 31 | ->addAtom('atom') 32 | ->addHtml('html') 33 | ->addText('text') 34 | ->addNumber('number') 35 | ->addDate('date') 36 | ->addGeopoint('geopoint') 37 | ; 38 | $this->assertEquals($obj_schema->getFields(), [ 39 | 'atom' => \Search\Schema::FIELD_ATOM, 40 | 'html' => \Search\Schema::FIELD_HTML, 41 | 'text' => \Search\Schema::FIELD_TEXT, 42 | 'number' => \Search\Schema::FIELD_NUMBER, 43 | 'date' => \Search\Schema::FIELD_DATE, 44 | 'geopoint' => \Search\Schema::FIELD_GEOPOINT 45 | ]); 46 | } 47 | 48 | /** 49 | * Test some automated field detection 50 | */ 51 | public function testAutoField() 52 | { 53 | $obj_schema = (new \Search\Schema()) 54 | ->addAutoField('string', 'some string') 55 | ->addAutoField('number1', 99) 56 | ->addAutoField('number2', 1.21) 57 | ->addAutoField('date', new DateTime()) 58 | ->addAutoField('stringable', new Stringy()) 59 | ; 60 | $this->assertEquals($obj_schema->getFields(), [ 61 | 'string' => \Search\Schema::FIELD_TEXT, 62 | 'number1' => \Search\Schema::FIELD_NUMBER, 63 | 'number2' => \Search\Schema::FIELD_NUMBER, 64 | 'date' => \Search\Schema::FIELD_DATE, 65 | 'stringable' => \Search\Schema::FIELD_TEXT 66 | ]); 67 | } 68 | 69 | /** 70 | * Check that fields are testable 71 | */ 72 | public function testHasField() 73 | { 74 | $obj_schema = (new \Search\Schema()) 75 | ->addAtom('atom') 76 | ->addHtml('html') 77 | ->addAutoField('number', 99) 78 | ; 79 | $this->assertTrue($obj_schema->hasField('atom')); 80 | $this->assertTrue($obj_schema->hasField('html')); 81 | $this->assertTrue($obj_schema->hasField('number')); 82 | $this->assertFalse($obj_schema->hasField('missing')); 83 | } 84 | 85 | /** 86 | * Basic create doc tests 87 | */ 88 | public function testCreateDoc() 89 | { 90 | $obj_schema = (new \Search\Schema()) 91 | ->addAtom('isbn') 92 | ->addText('title') 93 | ; 94 | $obj_doc = $obj_schema->createDocument([ 95 | 'isbn' => '123456789', 96 | 'title' => 'Booky Wooky' 97 | ]); 98 | $this->assertInstanceOf('\\Search\\Document', $obj_doc); 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /src/Search/Index.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class Index 25 | { 26 | 27 | /** 28 | * @var Gateway 29 | */ 30 | private $obj_gateway = null; 31 | 32 | /** 33 | * @param $str_index_name 34 | * @param null $str_namespace 35 | */ 36 | public function __construct($str_index_name, $str_namespace = null) 37 | { 38 | $this->obj_gateway = new Gateway($str_index_name, $str_namespace); 39 | } 40 | 41 | /** 42 | * Put a document into the index 43 | * 44 | * @param Document|Document[] $docs 45 | */ 46 | public function put($docs) 47 | { 48 | if($docs instanceof Document) { 49 | $this->obj_gateway->put([$docs]); 50 | } elseif (is_array($docs)) { 51 | $this->obj_gateway->put($docs); 52 | } else { 53 | throw new \InvalidArgumentException('Parameter must be one or more \Search\Document objects'); 54 | } 55 | } 56 | 57 | /** 58 | * Run a basic search query 59 | * 60 | * Support simple query strings OR Query objects 61 | * 62 | * @param $query 63 | * @return object 64 | */ 65 | public function search($query) 66 | { 67 | if($query instanceof Query) { 68 | return $this->obj_gateway->search($query); 69 | } 70 | return $this->obj_gateway->search(new Query((string)$query)); 71 | } 72 | 73 | /** 74 | * Get a single document by ID 75 | * 76 | * @param $str_id 77 | * @return array 78 | */ 79 | public function get($str_id) 80 | { 81 | return $this->obj_gateway->getDocById($str_id); 82 | } 83 | 84 | /** 85 | * Delete one or more documents 86 | * 87 | * @param $docs 88 | */ 89 | public function delete($docs) 90 | { 91 | if($docs instanceof Document) { 92 | $this->obj_gateway->delete([$docs->getId()]); 93 | } elseif (is_string($docs)) { 94 | $this->obj_gateway->delete([$docs]); 95 | } elseif (is_array($docs)) { 96 | $arr_doc_ids = []; 97 | foreach($docs as $doc){ 98 | if($doc instanceof Document) { 99 | $arr_doc_ids[] = $doc->getId(); 100 | } elseif (is_string($doc)) { 101 | $arr_doc_ids[] = $doc; 102 | } else { 103 | throw new \InvalidArgumentException('Parameter must be one or more \Search\Document objects or ID strings'); 104 | } 105 | } 106 | $this->obj_gateway->delete($arr_doc_ids); 107 | } else { 108 | throw new \InvalidArgumentException('Parameter must be one or more \Search\Document objects or ID strings'); 109 | } 110 | } 111 | 112 | /** 113 | * Return some debug info 114 | * 115 | * @return array 116 | */ 117 | public function debug() 118 | { 119 | return [$this->obj_gateway->getLastRequest(), $this->obj_gateway->getLastResponse()]; 120 | } 121 | 122 | // @todo FacetRequestParam 123 | // @todo FacetRequest 124 | 125 | } -------------------------------------------------------------------------------- /tests/DocumentTest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class DocumentTest extends \PHPUnit_Framework_TestCase 24 | { 25 | 26 | /** 27 | * Does the Document create it's own Schema? 28 | */ 29 | public function testAutoSchema() 30 | { 31 | $obj_doc = new \Search\Document(); 32 | $this->assertInstanceOf('\\Search\\Schema', $obj_doc->getSchema()); 33 | } 34 | 35 | /** 36 | * Does the Document retain the Schema we give it? 37 | */ 38 | public function testManualSchema() 39 | { 40 | $obj_schema = (new \Search\Schema())->addText('testing123'); 41 | $obj_doc = new \Search\Document($obj_schema); 42 | $this->assertInstanceOf('\\Search\\Schema', $obj_doc->getSchema()); 43 | $this->assertSame($obj_schema, $obj_doc->getSchema()); 44 | } 45 | 46 | /** 47 | * Can we set and get IDs. Is the interface fluent? 48 | */ 49 | public function testId() 50 | { 51 | $obj_doc = new \Search\Document(); 52 | $obj_doc2 = $obj_doc->setId('121gw'); 53 | $this->assertEquals('121gw', $obj_doc->getId()); 54 | $this->assertSame($obj_doc, $obj_doc2); 55 | } 56 | 57 | /** 58 | * Set and Get some data 59 | */ 60 | public function testMagicSetterGetter() 61 | { 62 | $obj_doc = new \Search\Document(); 63 | $obj_doc->power = '121gw'; 64 | $this->assertTrue(isset($obj_doc->power)); 65 | $this->assertFalse(isset($obj_doc->missing)); 66 | $this->assertEquals('121gw', $obj_doc->power); 67 | $this->assertNull($obj_doc->missing); 68 | } 69 | 70 | /** 71 | * Can we get all the data? 72 | */ 73 | public function testGetData() 74 | { 75 | $obj_doc = new \Search\Document(); 76 | $obj_doc->power = '121gw'; 77 | $this->assertEquals(['power' => '121gw'], $obj_doc->getData()); 78 | } 79 | 80 | /** 81 | * Can we set and return Expressions? 82 | */ 83 | public function testExpressionGetSet() 84 | { 85 | $obj_doc = new \Search\Document(); 86 | $obj_doc->setExpression('a', 'b'); 87 | $obj_doc->setExpression('c', 'd'); 88 | 89 | $this->assertEquals('b', $obj_doc->getExpression('a')); 90 | $this->assertNull($obj_doc->getExpression('missing')); 91 | $this->assertEquals(['a' => 'b', 'c' => 'd'], $obj_doc->getExpressions()); 92 | } 93 | 94 | /** 95 | * Test Facets 96 | */ 97 | public function testFacets() 98 | { 99 | $obj_doc = new \Search\Document(); 100 | $obj_doc->atomFacet('ref', '121gw'); 101 | $obj_doc->numberFacet('gw', 1.21); 102 | $obj_doc->numberFacet('gw', 2.42); 103 | $obj_doc->atomFacet('gw', 'lots'); 104 | 105 | $this->assertEquals([ 106 | [ 107 | 'name' => 'ref', 108 | 'atom' => '121gw' 109 | ],[ 110 | 'name' => 'gw', 111 | 'number' => 1.21 112 | ],[ 113 | 'name' => 'gw', 114 | 'number' => 2.42 115 | ],[ 116 | 'name' => 'gw', 117 | 'atom' => 'lots' 118 | ] 119 | ], $obj_doc->getFacets()); 120 | } 121 | 122 | } -------------------------------------------------------------------------------- /tests/QueryTest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class QueryTest extends \PHPUnit_Framework_TestCase 24 | { 25 | 26 | /** 27 | * Set setting and getting the query string 28 | */ 29 | public function testQueryString() 30 | { 31 | $obj_query = new \Search\Query('test string here'); 32 | $this->assertEquals('test string here', $obj_query->getQuery()); 33 | } 34 | 35 | /** 36 | * Test getting and setting the limit 37 | */ 38 | public function testLimit() 39 | { 40 | $obj_query = new \Search\Query(); 41 | $this->assertEquals(20, $obj_query->getLimit()); 42 | $obj_query->limit(121); 43 | $this->assertEquals(121, $obj_query->getLimit()); 44 | $obj_query->limit(-10); 45 | $this->assertEquals(1, $obj_query->getLimit()); 46 | $obj_query2 = $obj_query->limit(2001); 47 | $this->assertEquals(1000, $obj_query->getLimit()); 48 | $this->assertSame($obj_query, $obj_query2); 49 | } 50 | 51 | /** 52 | * Test getting and setting the offset 53 | */ 54 | public function testOffset() 55 | { 56 | $obj_query = new \Search\Query(); 57 | $this->assertEquals(0, $obj_query->getOffset()); 58 | $obj_query->offset(121); 59 | $this->assertEquals(121, $obj_query->getOffset()); 60 | $obj_query->offset(-10); 61 | $this->assertEquals(0, $obj_query->getOffset()); 62 | $obj_query2 = $obj_query->offset(2001); 63 | $this->assertEquals(1000, $obj_query->getOffset()); 64 | $this->assertSame($obj_query, $obj_query2); 65 | } 66 | 67 | /** 68 | * Test sort setter & getter 69 | */ 70 | public function testSort() 71 | { 72 | $obj_query = new \Search\Query(); 73 | $this->assertEmpty($obj_query->getSorts()); 74 | $obj_query->sort('a', 'b'); 75 | $this->assertEquals([['a', 'b']], $obj_query->getSorts()); 76 | $obj_query2 = $obj_query->sort('c', 'd'); 77 | $this->assertEquals([['a', 'b'],['c', 'd']], $obj_query->getSorts()); 78 | $this->assertSame($obj_query, $obj_query2); 79 | } 80 | 81 | /** 82 | * Test return field config 83 | */ 84 | public function testFields() 85 | { 86 | $obj_query = new \Search\Query(); 87 | $this->assertEmpty($obj_query->getReturnFields()); 88 | $obj_query->fields(['a', 'b']); 89 | $this->assertEquals(['a', 'b'], $obj_query->getReturnFields()); 90 | $obj_query2 = $obj_query->fields(['c', 'd']); 91 | $this->assertEquals(['c', 'd'], $obj_query->getReturnFields()); 92 | $this->assertSame($obj_query, $obj_query2); 93 | } 94 | 95 | /** 96 | * Test expressions 97 | */ 98 | public function testExpression() 99 | { 100 | $obj_query = new \Search\Query(); 101 | $this->assertEmpty($obj_query->getReturnExpressions()); 102 | $obj_query->expression('a', 'b'); 103 | $this->assertEquals([['a', 'b']], $obj_query->getReturnExpressions()); 104 | $obj_query2 = $obj_query->expression('c', 'd'); 105 | $this->assertEquals([['a', 'b'],['c', 'd']], $obj_query->getReturnExpressions()); 106 | $this->assertSame($obj_query, $obj_query2); 107 | } 108 | 109 | /** 110 | * Test the distance helper 111 | */ 112 | public function testDistance() 113 | { 114 | $obj_query = new \Search\Query(); 115 | $this->assertEmpty($obj_query->getSorts()); 116 | $this->assertEmpty($obj_query->getReturnExpressions()); 117 | $obj_query2 = $obj_query->sortByDistance('where', [1.21, 3.14159]); 118 | $this->assertEquals([['distance(where, geopoint(1.21,3.14159))', \Search\Query::ASC]], $obj_query->getSorts()); 119 | $this->assertEquals([['distance_from_where', 'distance(where, geopoint(1.21,3.14159))']], $obj_query->getReturnExpressions()); 120 | $this->assertSame($obj_query, $obj_query2); 121 | } 122 | 123 | /** 124 | * Validate scorer get/set 125 | */ 126 | public function testScorer() 127 | { 128 | $obj_query = new \Search\Query(); 129 | $this->assertEquals(\Search\Query::SCORE_NONE, $obj_query->getScorer()); 130 | $obj_query->score(); 131 | $this->assertEquals(\Search\Query::SCORE_REGULAR, $obj_query->getScorer()); 132 | } 133 | 134 | } -------------------------------------------------------------------------------- /src/Search/Document.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class Document 25 | { 26 | 27 | /** 28 | * Document schema 29 | * 30 | * @var Schema 31 | */ 32 | private $obj_schema = null; 33 | 34 | /** 35 | * Document ID 36 | * 37 | * @var string 38 | */ 39 | private $str_id = null; 40 | 41 | /** 42 | * Field Data 43 | * 44 | * @var array 45 | */ 46 | private $arr_data = []; 47 | 48 | /** 49 | * Expression data 50 | * 51 | * @var array 52 | */ 53 | private $arr_expressions = []; 54 | 55 | /** 56 | * Facets 57 | * 58 | * @var array 59 | */ 60 | private $arr_facets = []; 61 | 62 | /** 63 | * Optionally set the Schema on construction 64 | * 65 | * @param Schema $obj_schema 66 | */ 67 | public function __construct(Schema $obj_schema = null) 68 | { 69 | if(null === $obj_schema) { 70 | $this->obj_schema = new Schema(); 71 | } else { 72 | $this->obj_schema = $obj_schema; 73 | } 74 | } 75 | 76 | /** 77 | * Set the document ID 78 | * 79 | * @param $str_id 80 | * @return $this 81 | */ 82 | public function setId($str_id) 83 | { 84 | $this->str_id = $str_id; 85 | return $this; 86 | } 87 | 88 | /** 89 | * Get the document ID 90 | * 91 | * @return string 92 | */ 93 | public function getId() 94 | { 95 | return $this->str_id; 96 | } 97 | 98 | /** 99 | * The Schema for the Entity, if known. 100 | * 101 | * @return Schema|null 102 | */ 103 | public function getSchema() 104 | { 105 | return $this->obj_schema; 106 | } 107 | 108 | /** 109 | * Magic setter.. sorry 110 | * 111 | * Dynamically update the Schema as required 112 | * 113 | * @param $str_field 114 | * @param $mix_value 115 | */ 116 | public function __set($str_field, $mix_value) 117 | { 118 | if(!$this->obj_schema->hasField($str_field)) { 119 | $this->obj_schema->addAutoField($str_field, $mix_value); 120 | } 121 | $this->arr_data[$str_field] = $mix_value; 122 | } 123 | 124 | /** 125 | * Magic getter.. sorry 126 | * 127 | * @param $str_key 128 | * @return null 129 | */ 130 | public function __get($str_key) 131 | { 132 | if(isset($this->arr_data[$str_key])) { 133 | return $this->arr_data[$str_key]; 134 | } 135 | return null; 136 | } 137 | 138 | /** 139 | * Is a data value set? 140 | * 141 | * @param $str_key 142 | * @return bool 143 | */ 144 | public function __isset($str_key) 145 | { 146 | return isset($this->arr_data[$str_key]); 147 | } 148 | 149 | /** 150 | * Get all the document data 151 | * 152 | * @return array 153 | */ 154 | public function getData() 155 | { 156 | return $this->arr_data; 157 | } 158 | 159 | /** 160 | * Set an expression 161 | * 162 | * @param $str_name 163 | * @param $mix_value 164 | */ 165 | public function setExpression($str_name, $mix_value) 166 | { 167 | $this->arr_expressions[$str_name] = $mix_value; 168 | } 169 | 170 | /** 171 | * Get all the returned expressions for this document 172 | * 173 | * @return array 174 | */ 175 | public function getExpressions() 176 | { 177 | return $this->arr_expressions; 178 | } 179 | 180 | /** 181 | * Get a specific returned expression 182 | * 183 | * @param $str_name 184 | * @return null 185 | */ 186 | public function getExpression($str_name) 187 | { 188 | if(isset($this->arr_expressions[$str_name])) { 189 | return $this->arr_expressions[$str_name]; 190 | } 191 | return null; 192 | } 193 | 194 | /** 195 | * Add an ATOM facet to the Document 196 | * 197 | * @param $str_name 198 | * @param $str_value 199 | * @return $this 200 | */ 201 | public function atomFacet($str_name, $str_value) 202 | { 203 | $this->arr_facets[] = ['name' => $str_name, 'atom' => $str_value]; 204 | return $this; 205 | } 206 | 207 | /** 208 | * Add a NUMBER facet to the Document 209 | * 210 | * @param $str_name 211 | * @param $flt_value 212 | * @return $this 213 | */ 214 | public function numberFacet($str_name, $flt_value) 215 | { 216 | $this->arr_facets[] = ['name' => $str_name, 'number' => $flt_value]; 217 | return $this; 218 | } 219 | 220 | /** 221 | * Return the facets 222 | * 223 | * @return array 224 | */ 225 | public function getFacets() 226 | { 227 | return $this->arr_facets; 228 | } 229 | 230 | } -------------------------------------------------------------------------------- /tests/GatewayTest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class GatewayTest extends \google\appengine\testing\ApiProxyTestBase 24 | { 25 | 26 | /** 27 | * Can we create a Gateway? 28 | */ 29 | public function testExists() 30 | { 31 | $obj_gateway = new \Search\Gateway('some-index'); 32 | $this->assertInstanceOf('\\Search\\Gateway', $obj_gateway); 33 | } 34 | 35 | /** 36 | * Basic search test 37 | * 38 | * @todo Add assertions for response 39 | */ 40 | public function testBasicSearch() 41 | { 42 | $str_index = 'test-index'; 43 | $str_query = 'phrase'; 44 | 45 | $obj_request = new \google\appengine\SearchRequest(); 46 | $obj_request->mutableParams() 47 | ->setQuery($str_query) 48 | ->setLimit(20) 49 | ->setOffset(0) 50 | ->mutableIndexSpec()->setName($str_index); 51 | 52 | $this->apiProxyMock->expectCall('search', 'Search', $obj_request, new \google\appengine\SearchResponse()); 53 | $obj_gateway = new \Search\Gateway($str_index); 54 | $obj_gateway->search(new \Search\Query($str_query)); 55 | $this->apiProxyMock->verify(); 56 | } 57 | 58 | /** 59 | * Basic namespace 60 | */ 61 | public function testNamespaceSearch() 62 | { 63 | $str_index = 'test-index'; 64 | $str_namespace = 'testns1'; 65 | $str_query = 'phrase'; 66 | 67 | $obj_request = new \google\appengine\SearchRequest(); 68 | $obj_request->mutableParams() 69 | ->setQuery($str_query) 70 | ->setLimit(20) 71 | ->setOffset(0) 72 | ->mutableIndexSpec()->setName($str_index)->setNamespace($str_namespace); 73 | 74 | $this->apiProxyMock->expectCall('search', 'Search', $obj_request, new \google\appengine\SearchResponse()); 75 | $obj_gateway = new \Search\Gateway($str_index, $str_namespace); 76 | $obj_gateway->search(new \Search\Query($str_query)); 77 | $this->apiProxyMock->verify(); 78 | } 79 | 80 | /** 81 | * Test the delete document function 82 | * 83 | * @todo Add assertions for response 84 | */ 85 | public function testDelete() 86 | { 87 | $str_index = 'test-index'; 88 | $arr_ids = ['123456789', 'abc123']; 89 | 90 | $obj_request = new \google\appengine\DeleteDocumentRequest(); 91 | $obj_params = $obj_request->mutableParams(); 92 | $obj_params->mutableIndexSpec()->setName($str_index); 93 | foreach($arr_ids as $str_id) { 94 | $obj_params->addDocId($str_id); 95 | } 96 | 97 | $this->apiProxyMock->expectCall('search', 'DeleteDocument', $obj_request, new \google\appengine\DeleteDocumentResponse()); 98 | $obj_gateway = new \Search\Gateway($str_index); 99 | $obj_gateway->delete($arr_ids); 100 | $this->apiProxyMock->verify(); 101 | } 102 | 103 | /** 104 | * Test get by ID. Also ensure we can access the last request and response objects. 105 | * 106 | * @todo Add assertions for response 107 | */ 108 | public function testGetById() 109 | { 110 | $str_index = 'test-index'; 111 | $str_id = 'abc123def456'; 112 | 113 | $obj_request = new \google\appengine\ListDocumentsRequest(); 114 | $obj_params = $obj_request->mutableParams(); 115 | $obj_params->mutableIndexSpec()->setName($str_index); 116 | $obj_params->setStartDocId($str_id)->setLimit(1); 117 | $obj_response = new \google\appengine\ListDocumentsResponse(); 118 | 119 | $this->apiProxyMock->expectCall('search', 'ListDocuments', $obj_request, $obj_response); 120 | $obj_gateway = new \Search\Gateway($str_index); 121 | $obj_gateway->getDocById($str_id); 122 | $this->apiProxyMock->verify(); 123 | 124 | $this->assertInstanceOf('\\google\\appengine\\ListDocumentsRequest', $obj_gateway->getLastRequest()); 125 | $this->assertInstanceOf('\\google\\appengine\\ListDocumentsResponse', $obj_gateway->getLastResponse()); 126 | } 127 | 128 | /** 129 | * Test a basic put call 130 | * 131 | * @todo Expand range of field types 132 | */ 133 | public function testPut() 134 | { 135 | 136 | $str_index = 'library'; 137 | 138 | // Schema describing a book 139 | $obj_schema = (new \Search\Schema()) 140 | ->addText('title') 141 | ; 142 | 143 | // Create and populate a document 144 | $obj_book = $obj_schema->createDocument([ 145 | 'title' => 'The Merchant of Venice', 146 | ]); 147 | 148 | // Prepare the proxy mock 149 | $obj_request = new \google\appengine\IndexDocumentRequest(); 150 | $obj_params = $obj_request->mutableParams(); 151 | $obj_params->mutableIndexSpec()->setName($str_index); 152 | $obj_doc = $obj_params->addDocument(); 153 | $obj_doc->addField() 154 | ->setName('title') 155 | ->mutableValue() 156 | ->setType(storage_onestore_v3\FieldValue\ContentType::TEXT) 157 | ->setStringValue('The Merchant of Venice'); 158 | 159 | $this->apiProxyMock->expectCall('search', 'IndexDocument', $obj_request, new \google\appengine\IndexDocumentResponse()); 160 | 161 | // Write it to the Index 162 | $obj_index = new \Search\Index($str_index); 163 | $obj_index->put($obj_book); 164 | 165 | $this->apiProxyMock->verify(); 166 | 167 | } 168 | 169 | } -------------------------------------------------------------------------------- /src/Search/Schema.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class Schema 25 | { 26 | 27 | /** 28 | * Field data types 29 | * 30 | * Atom Field - an indivisible character string 31 | * Text Field - a plain text string that can be searched word by word 32 | * HTML Field - a string that contains HTML markup tags, only the text outside the markup tags can be searched 33 | * Number Field - a floating point number 34 | * Date Field - a date object with year/month/day and optional time 35 | * Geopoint Field - a data object with latitude and longitude coordinates 36 | */ 37 | const FIELD_ATOM = 1; 38 | const FIELD_TEXT = 2; 39 | const FIELD_HTML = 3; 40 | const FIELD_NUMBER = 4; 41 | const FIELD_DATE = 5; 42 | const FIELD_GEOPOINT = 20; 43 | const FIELD_DETECT = 99; // used for auto-detection 44 | 45 | /** 46 | * Known fields 47 | * 48 | * @var array 49 | */ 50 | private $arr_defined_fields = []; 51 | 52 | /** 53 | * Add a field to the known field array 54 | * 55 | * @param $str_name 56 | * @param $int_type 57 | * @return $this 58 | */ 59 | public function addField($str_name, $int_type) 60 | { 61 | $this->arr_defined_fields[$str_name] = $int_type; 62 | return $this; 63 | } 64 | 65 | /** 66 | * Add an ATOM field to the schema 67 | * 68 | * Atom Field - an indivisible character string 69 | * 70 | * @param $str_name 71 | * @return Schema 72 | */ 73 | public function addAtom($str_name) 74 | { 75 | return $this->addField($str_name, self::FIELD_ATOM); 76 | } 77 | 78 | /** 79 | * Add a TEXT field to the schema 80 | * 81 | * Text Field - a plain text string that can be searched word by word 82 | * 83 | * @param $str_name 84 | * @return Schema 85 | */ 86 | public function addText($str_name) 87 | { 88 | return $this->addField($str_name, self::FIELD_TEXT); 89 | } 90 | 91 | /** 92 | * Add an HTML field to the schema 93 | * 94 | * HTML Field - a string that contains HTML markup tags, only the text outside the markup tags can be searched 95 | * 96 | * @param $str_name 97 | * @return Schema 98 | */ 99 | public function addHtml($str_name) 100 | { 101 | return $this->addField($str_name, self::FIELD_HTML); 102 | } 103 | 104 | /** 105 | * Add a NUMBER field to the schema 106 | * 107 | * Number Field - a floating point number 108 | * 109 | * @param $str_name 110 | * @return Schema 111 | */ 112 | public function addNumber($str_name) 113 | { 114 | return $this->addField($str_name, self::FIELD_NUMBER); 115 | } 116 | 117 | /** 118 | * Add a DATE field to the schema 119 | * 120 | * Date Field - a date object with year/month/day 121 | * 122 | * @param $str_name 123 | * @return Schema 124 | */ 125 | public function addDate($str_name) 126 | { 127 | return $this->addField($str_name, self::FIELD_DATE); 128 | } 129 | 130 | /** 131 | * Add a GEOPOINT field to the schema 132 | * 133 | * Geopoint Field - a data object with latitude and longitude coordinates 134 | * 135 | * @param $str_name 136 | * @return Schema 137 | */ 138 | public function addGeopoint($str_name) 139 | { 140 | return $this->addField($str_name, self::FIELD_GEOPOINT); 141 | } 142 | 143 | /** 144 | * Get the configured fields 145 | * 146 | * @return array 147 | */ 148 | public function getFields() 149 | { 150 | return $this->arr_defined_fields; 151 | } 152 | 153 | /** 154 | * Create and return a Document with this Schema 155 | * 156 | * Optionally populate with the supplied data 157 | * 158 | * @param array|null $arr_data 159 | * @return Document 160 | */ 161 | public function createDocument(array $arr_data = null) 162 | { 163 | $obj_doc = new Document($this); 164 | if(null !== $arr_data) { 165 | foreach ($arr_data as $str_field => $mix_value) { 166 | $obj_doc->__set($str_field, $mix_value); 167 | } 168 | } 169 | return $obj_doc; 170 | } 171 | 172 | /** 173 | * Check if we have a field defined 174 | * 175 | * @param $str_name 176 | * @return bool 177 | */ 178 | public function hasField($str_name) 179 | { 180 | return isset($this->arr_defined_fields[$str_name]); 181 | } 182 | 183 | /** 184 | * Determine field type automatically 185 | * 186 | * @param $str_name 187 | * @param $mix_value 188 | * @return $this 189 | */ 190 | public function addAutoField($str_name, $mix_value) 191 | { 192 | switch(gettype($mix_value)) { 193 | case 'integer': 194 | case 'double': 195 | $this->addNumber($str_name); 196 | break; 197 | 198 | case 'object': 199 | if($mix_value instanceof \DateTime) { 200 | $this->addDate($str_name); 201 | } elseif (method_exists($mix_value, '__toString')) { 202 | $this->addText($str_name); 203 | } else { 204 | // @todo consider exception 205 | } 206 | break; 207 | 208 | case 'array': // @todo consider exception 209 | case 'string': 210 | case 'boolean': // @todo consider exception / numeric 211 | case 'resource': // @todo consider exception 212 | case 'null': 213 | case 'unknown type': // @todo consider exception 214 | default: 215 | $this->addText($str_name); 216 | } 217 | return $this; 218 | } 219 | 220 | } -------------------------------------------------------------------------------- /src/Search/Mapper.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | class Mapper 29 | { 30 | 31 | /** 32 | * Type map (to Google) 33 | * 34 | * @var array 35 | */ 36 | private static $arr_types = [ 37 | Schema::FIELD_ATOM => ContentType::ATOM, 38 | Schema::FIELD_TEXT => ContentType::TEXT, 39 | Schema::FIELD_HTML => ContentType::HTML, 40 | Schema::FIELD_NUMBER => ContentType::NUMBER, 41 | Schema::FIELD_DATE => ContentType::DATE, 42 | Schema::FIELD_GEOPOINT => ContentType::GEO 43 | ]; 44 | 45 | /** 46 | * Type map (from Google) 47 | * 48 | * @var array 49 | */ 50 | private static $arr_types_rev = []; 51 | 52 | /** 53 | * Create the reverse type map 54 | */ 55 | public function __construct() 56 | { 57 | self::$arr_types_rev = array_flip(self::$arr_types); 58 | } 59 | 60 | /** 61 | * Map from a Google document to a Search Document 62 | * 63 | * @param GoogleDocument $obj_source 64 | * @param Field[] $arr_expressions 65 | * @return Document 66 | */ 67 | public function fromGoogle(GoogleDocument $obj_source, array $arr_expressions = null) 68 | { 69 | $obj_schema = new Schema(); 70 | $obj_doc = new Document($obj_schema); 71 | $obj_doc->setId($obj_source->getId()); 72 | foreach($obj_source->getFieldList() as $obj_field) { 73 | /** @var Field $obj_field */ 74 | $str_field_name = $obj_field->getName(); 75 | $obj_value = $obj_field->getValue(); 76 | if(ContentType::GEO === $obj_value->getType()) { 77 | $obj_schema->addGeopoint($str_field_name); 78 | $obj_geo = $obj_value->getGeo(); 79 | $obj_doc->{$str_field_name} = [$obj_geo->getLat(), $obj_geo->getLng()]; 80 | } else { 81 | if(isset(self::$arr_types_rev[$obj_value->getType()])) { 82 | $obj_schema->addField($str_field_name, self::$arr_types_rev[$obj_value->getType()]); 83 | $obj_doc->{$str_field_name} = $obj_value->getStringValue(); 84 | } else { 85 | throw new \InvalidArgumentException('Unknown type mapping from Google Document'); 86 | } 87 | } 88 | } 89 | if(null !== $arr_expressions) { 90 | $this->mapExpressions($arr_expressions, $obj_doc); 91 | } 92 | return $obj_doc; 93 | } 94 | 95 | /** 96 | * Map expressions into a document 97 | * 98 | * @param Field[] $arr_fields 99 | * @param Document $obj_doc 100 | */ 101 | private function mapExpressions(array $arr_fields, Document $obj_doc) 102 | { 103 | foreach($arr_fields as $obj_field) { 104 | $str_field_name = $obj_field->getName(); 105 | $obj_value = $obj_field->getValue(); 106 | if(ContentType::GEO === $obj_value->getType()) { 107 | $obj_geo = $obj_value->getGeo(); 108 | $obj_doc->setExpression($str_field_name, [$obj_geo->getLat(), $obj_geo->getLng()]); 109 | } else { 110 | if(isset(self::$arr_types_rev[$obj_value->getType()])) { 111 | $obj_doc->setExpression($str_field_name, $obj_value->getStringValue()); 112 | } else { 113 | throw new \InvalidArgumentException('Unknown type mapping from Expressions'); 114 | } 115 | } 116 | } 117 | } 118 | 119 | /** 120 | * Map from a Google document to a Search Document 121 | * 122 | * @param Document $obj_source 123 | * @param GoogleDocument $obj_target 124 | * @return GoogleDocument 125 | */ 126 | public function toGoogle(Document $obj_source, GoogleDocument $obj_target) 127 | { 128 | $obj_target->setId($obj_source->getId()); 129 | 130 | // Field data 131 | foreach($obj_source->getSchema()->getFields() as $str_name => $int_type) { 132 | $obj_value = $obj_target->addField()->setName($str_name)->mutableValue(); 133 | if(Schema::FIELD_GEOPOINT === $int_type) { 134 | $obj_value->setType(ContentType::GEO); 135 | if(isset($obj_source->{$str_name}) && is_array($obj_source->{$str_name})) { 136 | $arr_geo = $obj_source->{$str_name}; 137 | $obj_value->mutableGeo()->setLat($arr_geo[0])->setLng($arr_geo[1]); 138 | } else { 139 | throw new \InvalidArgumentException('Geopoint data is required'); 140 | } 141 | } else { 142 | if(isset(self::$arr_types[$int_type])) { 143 | $obj_value->setType(self::$arr_types[$int_type]); 144 | } else { 145 | throw new \InvalidArgumentException('Unknown type mapping to Google Document'); 146 | } 147 | // @todo deal with specific field types requiring null / blanks / values 148 | if(isset($obj_source->{$str_name})) { 149 | $mix_val = $obj_source->{$str_name}; 150 | if($mix_val instanceof \DateTime) { 151 | $obj_value->setStringValue($mix_val->format('Y-m-d')); 152 | } else { 153 | $obj_value->setStringValue((string)$mix_val); 154 | } 155 | } else { 156 | $obj_value->setStringValue(null); 157 | } 158 | } 159 | } 160 | 161 | // Facets 162 | foreach($obj_source->getFacets() as $arr_facet) { 163 | $obj_facet = $obj_target->addFacet()->setName($arr_facet['name']); 164 | if(isset($arr_facet['atom'])) { 165 | $obj_facet->mutableValue()->setType(\storage_onestore_v3\FacetValue\ContentType::ATOM)->setStringValue($arr_facet['atom']); 166 | } elseif (isset($arr_facet['number'])) { 167 | $obj_facet->mutableValue()->setType(\storage_onestore_v3\FacetValue\ContentType::NUMBER)->setStringValue($arr_facet['number']); 168 | } 169 | } 170 | 171 | return $obj_target; 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /tests/IndexTest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class IndexTest extends \google\appengine\testing\ApiProxyTestBase 24 | { 25 | 26 | /** 27 | * Can we create an Index? 28 | */ 29 | public function testExists() 30 | { 31 | $obj_index = new \Search\Index('some-index'); 32 | $this->assertInstanceOf('\\Search\\Index', $obj_index); 33 | } 34 | 35 | /** 36 | * Test put failure 37 | * 38 | * @expectedException InvalidArgumentException 39 | * @expectedExceptionMessage Parameter must be one or more \Search\Document objects 40 | */ 41 | public function testPutTypeFailure() 42 | { 43 | $obj_index = new \Search\Index('test'); 44 | $obj_index->put('error-string'); 45 | } 46 | 47 | /** 48 | * Basic search test 49 | */ 50 | public function testBasicSearch() 51 | { 52 | $str_index = 'test-index'; 53 | $str_query = 'phrase'; 54 | 55 | $obj_request = new \google\appengine\SearchRequest(); 56 | $obj_request->mutableParams() 57 | ->setQuery($str_query) 58 | ->setLimit(20) 59 | ->setOffset(0) 60 | ->mutableIndexSpec()->setName($str_index); 61 | 62 | $this->apiProxyMock->expectCall('search', 'Search', $obj_request, new \google\appengine\SearchResponse()); 63 | $obj_index = new \Search\Index($str_index); 64 | $obj_index->search($str_query); 65 | $this->apiProxyMock->verify(); 66 | } 67 | 68 | /** 69 | * Basic search test 70 | */ 71 | public function testQuerySearch() 72 | { 73 | $str_index = 'test-index'; 74 | $str_query = 'phrase'; 75 | 76 | $obj_request = new \google\appengine\SearchRequest(); 77 | $obj_request->mutableParams() 78 | ->setQuery($str_query) 79 | ->setLimit(20) 80 | ->setOffset(0) 81 | ->mutableIndexSpec()->setName($str_index); 82 | 83 | $this->apiProxyMock->expectCall('search', 'Search', $obj_request, new \google\appengine\SearchResponse()); 84 | $obj_index = new \Search\Index($str_index); 85 | $obj_index->search(new \Search\Query($str_query)); 86 | $this->apiProxyMock->verify(); 87 | } 88 | 89 | /** 90 | * Test get by ID. Also ensure we can access the last request and response objects. 91 | */ 92 | public function testGetById() 93 | { 94 | $str_index = 'test-index'; 95 | $str_id = 'abc123def456'; 96 | 97 | $obj_request = new \google\appengine\ListDocumentsRequest(); 98 | $obj_params = $obj_request->mutableParams(); 99 | $obj_params->mutableIndexSpec()->setName($str_index); 100 | $obj_params->setStartDocId($str_id)->setLimit(1); 101 | $obj_response = new \google\appengine\ListDocumentsResponse(); 102 | 103 | $this->apiProxyMock->expectCall('search', 'ListDocuments', $obj_request, $obj_response); 104 | $obj_index = new \Search\Index($str_index); 105 | $obj_index->get($str_id); 106 | $this->apiProxyMock->verify(); 107 | 108 | $arr_debug = $obj_index->debug(); 109 | $this->assertInstanceOf('\\google\\appengine\\ListDocumentsRequest', $arr_debug[0]); 110 | $this->assertInstanceOf('\\google\\appengine\\ListDocumentsResponse', $arr_debug[1]); 111 | } 112 | 113 | /** 114 | * Test the delete document function 115 | */ 116 | public function testDeleteArrayIdStrings() 117 | { 118 | $str_index = 'test-index'; 119 | $arr_ids = ['123456789', 'abc123']; 120 | 121 | $obj_request = new \google\appengine\DeleteDocumentRequest(); 122 | $obj_params = $obj_request->mutableParams(); 123 | $obj_params->mutableIndexSpec()->setName($str_index); 124 | foreach($arr_ids as $str_id) { 125 | $obj_params->addDocId($str_id); 126 | } 127 | 128 | $this->apiProxyMock->expectCall('search', 'DeleteDocument', $obj_request, new \google\appengine\DeleteDocumentResponse()); 129 | $obj_index = new \Search\Index($str_index); 130 | $obj_index->delete($arr_ids); 131 | $this->apiProxyMock->verify(); 132 | } 133 | 134 | /** 135 | * Test the delete document function with a variety of inputs 136 | */ 137 | public function testDeleteMulti() 138 | { 139 | $str_index = 'test-index'; 140 | $str_id = '123456789'; 141 | 142 | $obj_request = new \google\appengine\DeleteDocumentRequest(); 143 | $obj_params = $obj_request->mutableParams(); 144 | $obj_params->mutableIndexSpec()->setName($str_index); 145 | $obj_params->addDocId($str_id); 146 | 147 | $this->apiProxyMock->expectCall('search', 'DeleteDocument', $obj_request, new \google\appengine\DeleteDocumentResponse()); 148 | $obj_index = new \Search\Index($str_index); 149 | $obj_index->delete($str_id); 150 | $this->apiProxyMock->verify(); 151 | 152 | $this->apiProxyMock->expectCall('search', 'DeleteDocument', $obj_request, new \google\appengine\DeleteDocumentResponse()); 153 | $obj_doc = new \Search\Document(); 154 | $obj_doc->setId($str_id); 155 | $obj_index->delete($obj_doc); 156 | $this->apiProxyMock->verify(); 157 | 158 | $this->apiProxyMock->expectCall('search', 'DeleteDocument', $obj_request, new \google\appengine\DeleteDocumentResponse()); 159 | $obj_doc = new \Search\Document(); 160 | $obj_doc->setId($str_id); 161 | $obj_index->delete([$obj_doc]); 162 | $this->apiProxyMock->verify(); 163 | } 164 | 165 | /** 166 | * Test the delete document function failure 167 | * 168 | * @expectedException InvalidArgumentException 169 | * @expectedExceptionMessage Parameter must be one or more \Search\Document objects 170 | */ 171 | public function testDeleteFailure() 172 | { 173 | $obj_index = new \Search\Index('test'); 174 | $obj_index->delete(123); 175 | } 176 | 177 | /** 178 | * Test the delete document function failure 179 | * 180 | * @expectedException InvalidArgumentException 181 | * @expectedExceptionMessage Parameter must be one or more \Search\Document objects 182 | */ 183 | public function testDeleteFailureArray() 184 | { 185 | $obj_index = new \Search\Index('test'); 186 | $obj_index->delete(['abc', 123]); 187 | } 188 | 189 | } -------------------------------------------------------------------------------- /src/Search/Query.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class Query 25 | { 26 | 27 | /** 28 | * Sort directions 29 | */ 30 | const DESC = 'DESCENDING'; 31 | const ASC = 'ASCENDING'; 32 | 33 | /** 34 | * Match scorers 35 | */ 36 | const SCORE_NONE = 'NONE'; 37 | const SCORE_REGULAR = 'REGULAR'; 38 | const SCORE_RESCORING = 'RESCORING'; 39 | 40 | /** 41 | * Query string 42 | * 43 | * @var string 44 | */ 45 | private $str_query = ''; 46 | 47 | /** 48 | * Max results 49 | * 50 | * @var int 51 | */ 52 | private $int_limit = 20; 53 | 54 | /** 55 | *Result offset 56 | * 57 | * @var int 58 | */ 59 | private $int_offset = 0; 60 | 61 | /** 62 | * A list of the required return fields 63 | * 64 | * @var null 65 | */ 66 | private $arr_return_fields = null; 67 | 68 | /** 69 | * A list of the required return expressions 70 | * 71 | * @var null 72 | */ 73 | private $arr_return_expressions = []; 74 | 75 | /** 76 | * Applied sorts 77 | * 78 | * @var array 79 | */ 80 | private $arr_sorts = []; 81 | 82 | /** 83 | * Scorer type 84 | * 85 | * @var string 86 | */ 87 | private $str_scorer = self::SCORE_NONE; 88 | 89 | /** 90 | * Which facets to include in the response 91 | * 92 | * @var null 93 | */ 94 | private $arr_facets = null; 95 | 96 | /** 97 | * Set the query string on construction 98 | * 99 | * @param $str_query 100 | */ 101 | public function __construct($str_query = '') 102 | { 103 | $this->str_query = $str_query; 104 | } 105 | 106 | /** 107 | * Get the query string 108 | * 109 | * @return string 110 | */ 111 | public function getQuery() 112 | { 113 | return $this->str_query; 114 | } 115 | 116 | /** 117 | * Set the number of results, between 1-100 118 | * 119 | * @param $int_limit 120 | * @return $this 121 | */ 122 | public function limit($int_limit) 123 | { 124 | $this->int_limit = min(max(1, $int_limit), 1000); 125 | return $this; 126 | } 127 | 128 | /** 129 | * Get the limit 130 | * 131 | * @return int 132 | */ 133 | public function getLimit() 134 | { 135 | return $this->int_limit; 136 | } 137 | 138 | /** 139 | * Set the result offset, 0-n 140 | * 141 | * @param $int_offset 142 | * @return $this 143 | */ 144 | public function offset($int_offset) 145 | { 146 | $this->int_offset = min(max(0, $int_offset), 1000); 147 | return $this; 148 | } 149 | 150 | /** 151 | * Get the offset 152 | * 153 | * @return int 154 | */ 155 | public function getOffset() 156 | { 157 | return $this->int_offset; 158 | } 159 | 160 | /** 161 | * Sort results by a field in ASCending order 162 | * 163 | * @param $str_field 164 | * @param string $str_direction 165 | * @return $this 166 | */ 167 | public function sort($str_field, $str_direction = self::DESC) 168 | { 169 | $this->arr_sorts[] = [$str_field, $str_direction]; 170 | return $this; 171 | } 172 | 173 | /** 174 | * Sort results by distance from a supplied Lat/Lon. Defaults to nearest first. 175 | * 176 | * Also includes the distance (in meters) in the results as an expression 177 | * 178 | * @param string $str_field 179 | * @param array $arr_loc 180 | * @param string $str_dir 181 | * @return $this 182 | */ 183 | public function sortByDistance($str_field, array $arr_loc, $str_dir = self::ASC) 184 | { 185 | $str_distance_expression = "distance({$str_field}, geopoint({$arr_loc[0]},{$arr_loc[1]}))"; 186 | $this->sort($str_distance_expression, $str_dir); 187 | $this->expression('distance_from_' . $str_field, $str_distance_expression); 188 | return $this; 189 | } 190 | 191 | /** 192 | * Get applied sorts 193 | * 194 | * @return array 195 | */ 196 | public function getSorts() 197 | { 198 | return $this->arr_sorts; 199 | } 200 | 201 | /** 202 | * Should we use a MatchScorer? 203 | * 204 | * @param $str_type 205 | * @return $this 206 | */ 207 | public function score($str_type = self::SCORE_REGULAR) 208 | { 209 | $this->str_scorer = $str_type; 210 | return $this; 211 | } 212 | 213 | /** 214 | * Get the defined scorer 215 | * 216 | * @return string 217 | */ 218 | public function getScorer() 219 | { 220 | return $this->str_scorer; 221 | } 222 | 223 | /** 224 | * Set the required return fields 225 | * 226 | * @param array $arr_fields 227 | * @return $this 228 | */ 229 | public function fields(array $arr_fields) 230 | { 231 | $this->arr_return_fields = $arr_fields; 232 | return $this; 233 | } 234 | 235 | /** 236 | * Get the fields to return 237 | * 238 | * @return null 239 | */ 240 | public function getReturnFields() 241 | { 242 | return $this->arr_return_fields; 243 | } 244 | 245 | /** 246 | * Add an expression to return 247 | * 248 | * @param string $str_name 249 | * @param string $str_expression 250 | * @return $this 251 | */ 252 | public function expression($str_name, $str_expression) 253 | { 254 | $this->arr_return_expressions[] = [$str_name, $str_expression]; 255 | return $this; 256 | } 257 | 258 | /** 259 | * Get the expressions to return 260 | * 261 | * @return null 262 | */ 263 | public function getReturnExpressions() 264 | { 265 | return $this->arr_return_expressions; 266 | } 267 | 268 | /** 269 | * Attempt to return Facet data with the results 270 | * 271 | * @param array $arr_facets 272 | * @return $this 273 | */ 274 | public function facets(array $arr_facets = []) 275 | { 276 | $this->arr_facets = $arr_facets; 277 | return $this; 278 | } 279 | 280 | /** 281 | * Which facets to include in the response 282 | * 283 | * @return null 284 | */ 285 | public function getFacets() 286 | { 287 | return $this->arr_facets; 288 | } 289 | 290 | // @todo Snippets 291 | // @todo Cursors 292 | 293 | } 294 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://api.travis-ci.org/tomwalder/php-appengine-search.svg)](https://travis-ci.org/tomwalder/php-appengine-search) 2 | [![Coverage Status](https://coveralls.io/repos/tomwalder/php-appengine-search/badge.svg)](https://coveralls.io/r/tomwalder/php-appengine-search) 3 | 4 | # Full Text Search for PHP on Google App Engine # 5 | 6 | This library provides native PHP access to the Google App Engine Search API. 7 | 8 | At the time of writing there is no off-the-shelf way to access the Google App Engine full text search API from the PHP runtime. 9 | 10 | Generally this means developers cannot access the service without using [Python/Java/Go proxy modules](https://github.com/tomwalder/phpne14-text-search) - which adds complexity, another language, additional potential points of failure and performance impact. 11 | 12 | **ALPHA** This library is in the very early stages of development. Do not use it in production. It will change. 13 | 14 | ## Table of Contents ## 15 | 16 | - [Examples](#examples) 17 | - [Install with Composer](#install-with-composer) 18 | - [Queries](#queries) 19 | - [Geo Queries](#distance-from) 20 | - [Autocomplete](#autocomplete) 21 | - [Creating Documents](#creating-documents) - includes location (Geopoint) and Dates 22 | - [Facets](#facets) 23 | - [Deleting Documents](#deleting-documents) 24 | - [Local Development](#local-development-environment) 25 | - [Best Practice, Free Quotas, Costs](#best-practice-free-quotas-costs) 26 | - [Google Software](#google-software) 27 | 28 | ## Examples ## 29 | 30 | I find examples a great way to decide if I want to even try out a library, so here's a couple for you. 31 | 32 | ```php 33 | // Schema describing a book 34 | $obj_schema = (new \Search\Schema()) 35 | ->addText('title') 36 | ->addText('author') 37 | ->addAtom('isbn') 38 | ->addNumber('price'); 39 | 40 | // Create and populate a document 41 | $obj_book = $obj_schema->createDocument([ 42 | 'title' => 'The Merchant of Venice', 43 | 'author' => 'William Shakespeare', 44 | 'isbn' => '1840224312', 45 | 'price' => 11.99 46 | ]); 47 | 48 | // Write it to the Index 49 | $obj_index = new \Search\Index('library'); 50 | $obj_index->put($obj_book); 51 | ``` 52 | 53 | In this example, I've used the [Alternative Array Syntax](#alternative-array-syntax) for creating Documents - but you can also do it like this: 54 | 55 | ```php 56 | $obj_book = $obj_schema->createDocument(); 57 | $obj_book->title = 'Romeo and Juliet'; 58 | $obj_book->author = 'William Shakespeare'; 59 | $obj_book->isbn = '1840224339'; 60 | $obj_book->price = 9.99; 61 | ``` 62 | 63 | Now let's do a simple search and display the output 64 | 65 | ```php 66 | $obj_index = new \Search\Index('library'); 67 | $obj_response = $obj_index->search('romeo'); 68 | foreach($obj_response->results as $obj_result) { 69 | echo "Title: {$obj_result->doc->title}, ISBN: {$obj_result->doc->isbn}
", PHP_EOL; 70 | } 71 | ``` 72 | 73 | ### Demo Application ### 74 | 75 | Search pubs! 76 | 77 | Application: http://pub-search.appspot.com/ 78 | 79 | Code: https://github.com/tomwalder/pub-search 80 | 81 | ## Getting Started ## 82 | 83 | ### Install with Composer ### 84 | 85 | To install using Composer, use this require line in your `composer.json` for bleeding-edge features, dev-master 86 | 87 | `"tomwalder/php-appengine-search": "v0.0.4-alpha"` 88 | 89 | Or, if you're using the command line: 90 | 91 | `composer require tomwalder/php-appengine-search` 92 | 93 | You may need `minimum-stability: dev` 94 | 95 | # Queries # 96 | 97 | You can supply a simple query string to `Index::search` 98 | 99 | ```php 100 | $obj_index->search('romeo'); 101 | ``` 102 | 103 | For more control and options, you can supply a `Query` object 104 | 105 | ```php 106 | $obj_query = (new \Search\Query($str_query)) 107 | ->fields(['isbn', 'price']) 108 | ->limit(10) 109 | ->sort('price'); 110 | $obj_response = $obj_index->search($obj_query); 111 | ``` 112 | 113 | ## Query Strings ## 114 | 115 | Some simple, valid query strings: 116 | - `price:2.99` 117 | - `romeo` 118 | - `dob:2015-01-01` 119 | - `dob < 2000-01-01` 120 | - `tom AND age:36` 121 | 122 | For *much* more information, see the Python reference docs: https://cloud.google.com/appengine/docs/python/search/query_strings 123 | 124 | ## Sorting ## 125 | 126 | ```php 127 | $obj_query->sort('price'); 128 | ``` 129 | 130 | ```php 131 | $obj_query->sort('price', Query::ASC); 132 | ``` 133 | 134 | ## Limits & Offsets ## 135 | 136 | ```php 137 | $obj_query->limit(10); 138 | ``` 139 | 140 | ```php 141 | $obj_query->offset(5); 142 | ``` 143 | 144 | ## Return Fields ## 145 | 146 | ```php 147 | $obj_query->fields(['isbn', 'price']); 148 | ``` 149 | 150 | ## Expressions ## 151 | 152 | The library supports requesting arbitrary expressions in the results. 153 | 154 | ```php 155 | $obj_query->expression('euros', 'gbp * 1.45']); 156 | ``` 157 | 158 | These can be accessed from the `Document::getExpression()` method on the resulting documents, like this: 159 | 160 | ```php 161 | $obj_doc->getExpression('euros'); 162 | ``` 163 | 164 | ## Get Document by ID ## 165 | 166 | You can fetch a single document from an index directly, by it's unique Doc ID: 167 | 168 | ```php 169 | $obj_index->get('some-document-id-here'); 170 | ``` 171 | 172 | ## Scoring ## 173 | 174 | You can enable the MatchScorer by calling the `Query::score` method. 175 | 176 | If you do this, each document in the result set will be scored by the Search API "according to search term frequency" - Google. 177 | 178 | Without it, documents will all have a score of 0. 179 | 180 | ```php 181 | $obj_query->score(); 182 | ``` 183 | 184 | And the results... 185 | 186 | ```php 187 | foreach($obj_response->results as $obj_result) { 188 | echo $obj_result->score, '
'; // Score will be a float 189 | } 190 | ``` 191 | 192 | ### Multiple Sorts and Scoring ### 193 | 194 | If you apply `score()` and `sort()` you may be wasting cycles and costing money. Only score documents when you intend to sort by score. 195 | 196 | If you need to mix sorting of score and another field, you can use the magic field name `_score` like this - here we sort by price then score, so records with the same price are sorted by their score. 197 | 198 | ```php 199 | $obj_query->score()->sort('price')->sort('_score'); 200 | ``` 201 | 202 | # Helper Queries & Tools # 203 | 204 | ## Distance From ## 205 | 206 | A common use case is searching for documents that have a Geopoint field, based on their distance from a known Geopoint. e.g. "Find pubs near me" 207 | 208 | There is a helper method to do this for you, and it also returns the distance in meters in the response. 209 | 210 | ```php 211 | $obj_query->sortByDistance('location', [53.4653381,-2.2483717]); 212 | ``` 213 | 214 | This will return results, nearest first to the supplied Lat/Lon, and there will be an expression returned for the distance itself - prefixed with `distance_from_`: 215 | 216 | ```php 217 | $obj_result->doc->getExpression('distance_from_location'); 218 | ``` 219 | 220 | ## Autocomplete ## 221 | 222 | Autocomplete is one of the most desired and useful features of a search solution. 223 | 224 | This can be implemented fairly easily with the Google App Engine Search API, **with a little slight of hand!** 225 | 226 | The Search API does not natively support "edge n-gram" tokenisation (which is what we need for autocomplete!). 227 | 228 | So, you can do this with the library - when creating documents, set a second text field with the output from the included `Tokenizer::edgeNGram` function 229 | 230 | ```php 231 | $obj_tkzr = new \Search\Tokenizer(); 232 | $obj_schema->createDocument([ 233 | 'name' => $str_name, 234 | 'name_ngram' => $obj_tkzr->edgeNGram($str_name), 235 | ]); 236 | ``` 237 | 238 | Then you can run autocomplete queries easily like this: 239 | 240 | ```php 241 | $obj_response = $obj_index->search((new \Search\Query('name_ngram:' . $str_query))); 242 | ``` 243 | 244 | You can see a full demo application using this in my "pub search" demo app 245 | 246 | # Creating Documents # 247 | 248 | ## Schemas & Field Types ## 249 | 250 | As per the Python docs, the available field types are 251 | 252 | - **Atom** - an indivisible character string 253 | - **Text** - a plain text string that can be searched word by word 254 | - **HTML** - a string that contains HTML markup tags, only the text outside the markup tags can be searched 255 | - **Number** - a floating point number 256 | - **Date** - a date with year/month/day and optional time 257 | - **Geopoint** - latitude and longitude coordinates 258 | 259 | ### Dates ### 260 | 261 | We support `DateTime` objects or date strings in the format `YYYY-MM-DD` (PHP `date('Y-m-d')`) 262 | 263 | ```php 264 | $obj_person_schema = (new \Search\Schema()) 265 | ->addText('name') 266 | ->addDate('dob'); 267 | 268 | $obj_person = $obj_person_schema->createDocument([ 269 | 'name' => 'Marty McFly', 270 | 'dob' => new DateTime() 271 | ]); 272 | ``` 273 | 274 | ### Geopoints - Location Data ### 275 | 276 | Create an entry with a Geopoint field 277 | 278 | ```php 279 | $obj_pub_schema = (new \Search\Schema()) 280 | ->addText('name') 281 | ->addGeopoint('where') 282 | ->addNumber('rating'); 283 | 284 | $obj_pub = $obj_pub_schema->createDocument([ 285 | 'name' => 'Kim by the Sea', 286 | 'where' => [53.4653381, -2.2483717], 287 | 'rating' => 3 288 | ]); 289 | ``` 290 | 291 | ## Batch Inserts ## 292 | 293 | It's more efficient to insert in batches if you have multiple documents. Up to 200 documents can be inserted at once. 294 | 295 | Just pass an array of Document objects into the `Index::put()` method, like this: 296 | 297 | ```php 298 | $obj_index = new \Search\Index('library'); 299 | $obj_index->put([$obj_book1, $obj_book2, $obj_book3]); 300 | ``` 301 | 302 | ## Alternative Array Syntax ## 303 | 304 | There is an alternative to directly constructing a new `Search\Document` and setting it's member data, which is to use the `Search\Schema::createDocument` factory method as follows. 305 | 306 | ```php 307 | $obj_book = $obj_schema->createDocument([ 308 | 'title' => 'The Merchant of Venice', 309 | 'author' => 'William Shakespeare', 310 | 'isbn' => '1840224312', 311 | 'price' => 11.99 312 | ]); 313 | ``` 314 | 315 | ## Namespaces ## 316 | 317 | You can set a namespace when constructing an index. This will allow you to support multi-tenant applications. 318 | 319 | ```php 320 | $obj_index = new \Search\Index('library', 'client1'); 321 | ``` 322 | 323 | # Facets # 324 | 325 | The Search API supports 2 types of document facets for categorisation, ATOM and NUMBER. 326 | 327 | ATOM are probably the ones you are most familiar with, and result sets will include counts per unique facet, kind of like this: 328 | 329 | For shirt sizes 330 | * small (9) 331 | * medium (37) 332 | 333 | ## Adding Facets to a Document ## 334 | 335 | ```php 336 | $obj_doc->atomFacet('size', 'small'); 337 | $obj_doc->atomFacet('colour', 'blue'); 338 | ``` 339 | 340 | ## Getting Facets in Results ## 341 | 342 | ```php 343 | $obj_query->facets(); 344 | ``` 345 | 346 | # Deleting Documents # 347 | 348 | You can delete documents by calling the `Index::delete()` method. 349 | 350 | It support one or more `Document` objects - or one or more Document ID strings - or a mixture of objects and ID strings! 351 | 352 | ```php 353 | $obj_index = new \Search\Index('library'); 354 | $obj_index->delete('some-document-id'); 355 | $obj_index->delete([$obj_doc1, $obj_doc2]); 356 | $obj_index->delete([$obj_doc3, 'another-document-id']); 357 | ``` 358 | 359 | # Local Development Environment # 360 | 361 | The Search API is supported locally, because it's included to support the Python, Java and Go App Engine runtimes. 362 | 363 | # Best Practice, Free Quotas, Costs # 364 | 365 | Like most App Engine services, search is free... up to a point! 366 | 367 | - [Free quota information](https://cloud.google.com/appengine/docs/quotas?hl=en#search) 368 | - [Search API pricing](https://cloud.google.com/appengine/pricing#search_pricing) 369 | 370 | And some best practice that is most certainly worth a read 371 | 372 | - [Best practice for the Google App Engine Search API](https://cloud.google.com/appengine/docs/python/search/best_practices) 373 | 374 | 375 | # Google Software # 376 | 377 | I've had to include 2 files from Google to make this work - they are the Protocol Buffer implementations for the Search API. You will find them in the `/libs` folder. 378 | 379 | They are also available directly from the following repository: https://github.com/GoogleCloudPlatform/appengine-php-sdk 380 | 381 | These 2 files are Copyright 2007 Google Inc. 382 | 383 | As and when they make it into the actual live PHP runtime, I will remove them from here. 384 | 385 | Thank you to @sjlangley for the assist. 386 | 387 | # Other App Engine Software # 388 | 389 | If you've enjoyed this, you might be interested in my [Google Cloud Datastore Library for PHP, PHP-GDS](https://github.com/tomwalder/php-gds) 390 | -------------------------------------------------------------------------------- /src/Search/Gateway.php: -------------------------------------------------------------------------------- 1 | 38 | */ 39 | class Gateway 40 | { 41 | 42 | /** 43 | * The index name 44 | * 45 | * @var string 46 | */ 47 | protected $str_index_name = null; 48 | 49 | /** 50 | * The index namespace 51 | * 52 | * @var string 53 | */ 54 | protected $str_namespace = null; 55 | 56 | /** 57 | * The last request 58 | * 59 | * @var ProtocolMessage 60 | */ 61 | protected $obj_last_request = null; 62 | 63 | /** 64 | * The last response 65 | * 66 | * @var ProtocolMessage 67 | */ 68 | protected $obj_last_response = null; 69 | 70 | /** 71 | * Set the index name and optionally a namespace 72 | * 73 | * @param string $str_index_name 74 | * @param null|string $str_namespace 75 | */ 76 | public function __construct($str_index_name, $str_namespace = null) 77 | { 78 | $this->str_index_name = $str_index_name; 79 | $this->str_namespace = $str_namespace; 80 | } 81 | 82 | /** 83 | * Prepare the request parameters 84 | * 85 | * Index specs: consistency, mode, name, namespace, source, version 86 | * 87 | * @param $obj_request 88 | * @return object 89 | */ 90 | private function prepareRequestParams($obj_request) 91 | { 92 | $obj_params = $obj_request->mutableParams(); 93 | $obj_spec = $obj_params->mutableIndexSpec()->setName($this->str_index_name); 94 | if(null !== $this->str_namespace) { 95 | $obj_spec->setNamespace($this->str_namespace); 96 | } 97 | return $obj_params; 98 | } 99 | 100 | /** 101 | * Put one or more documents into the index 102 | * 103 | * @param Document[] $arr_docs 104 | * @throws ApplicationError 105 | * @throws \Exception 106 | */ 107 | public function put(array $arr_docs) 108 | { 109 | $obj_request = new IndexDocumentRequest(); 110 | $obj_params = $this->prepareRequestParams($obj_request); 111 | 112 | // Other index specs: consistency, mode, name, namespace, source, version 113 | $obj_mapper = new Mapper(); 114 | foreach($arr_docs as $obj_doc) { 115 | $obj_mapper->toGoogle($obj_doc, $obj_params->addDocument()); 116 | } 117 | $this->execute('IndexDocument', $obj_request, new IndexDocumentResponse()); 118 | } 119 | 120 | /** 121 | * Run a Search Query 122 | * 123 | * @param Query $obj_query 124 | * @return object 125 | * @throws ApplicationError 126 | * @throws \Exception 127 | */ 128 | public function search(Query $obj_query) 129 | { 130 | $obj_request = new SearchRequest(); 131 | $obj_params = $this->prepareRequestParams($obj_request); 132 | 133 | // Basics 134 | $obj_params 135 | ->setQuery($obj_query->getQuery()) 136 | ->setLimit($obj_query->getLimit()) 137 | ->setOffset($obj_query->getOffset()) 138 | ; 139 | 140 | // Sorting 141 | $arr_sorts = $obj_query->getSorts(); 142 | if(null !== $arr_sorts && count($arr_sorts) > 0) { 143 | foreach ($arr_sorts as $arr_sort) { 144 | $obj_sort = $obj_params->addSortSpec(); 145 | $obj_sort->setSortExpression($arr_sort[0]); 146 | $obj_sort->setSortDescending(Query::DESC === $arr_sort[1]); 147 | } 148 | } 149 | 150 | // Match Scoring 151 | if(Query::SCORE_REGULAR === $obj_query->getScorer()) { 152 | $obj_params->mutableScorerSpec()->setScorer(Scorer::MATCH_SCORER)->setLimit($obj_query->getLimit()); 153 | } elseif (Query::SCORE_RESCORING === $obj_query->getScorer()) { 154 | $obj_params->mutableScorerSpec()->setScorer(Scorer::RESCORING_MATCH_SCORER)->setLimit($obj_query->getLimit()); 155 | } 156 | 157 | // Return Fields 158 | $arr_return_fields = $obj_query->getReturnFields(); 159 | if(null !== $arr_return_fields && count($arr_return_fields) > 0) { 160 | $obj_fields = $obj_params->mutableFieldSpec(); 161 | foreach ($arr_return_fields as $str_field) { 162 | $obj_fields->addName($str_field); 163 | } 164 | } 165 | 166 | // Return Expressions 167 | $arr_return_exps = $obj_query->getReturnExpressions(); 168 | if(null !== $arr_return_exps && count($arr_return_exps) > 0) { 169 | $obj_fields = $obj_params->mutableFieldSpec(); 170 | foreach ($arr_return_exps as $arr_exp) { 171 | $obj_fields->addExpression()->setName($arr_exp[0])->setExpression($arr_exp[1]); 172 | } 173 | } 174 | 175 | // Facets 176 | $arr_facets = $obj_query->getFacets(); 177 | if(null !== $arr_facets) { 178 | if(count($arr_facets) > 0) { 179 | // We want a specific set of facets in the response 180 | foreach($arr_facets as $str_facet) { 181 | $obj_params->addIncludeFacet()->setName($str_facet); 182 | } 183 | } else { 184 | // We want all facets back... 185 | $obj_params->setAutoDiscoverFacetCount(100); 186 | } 187 | } 188 | 189 | $this->execute('Search', $obj_request, new SearchResponse()); 190 | return $this->processSearchResponse(); 191 | } 192 | 193 | /** 194 | * Return a single document by ID 195 | * 196 | * @param $str_id 197 | * @return array 198 | * @throws ApplicationError 199 | * @throws \Exception 200 | */ 201 | public function getDocById($str_id) 202 | { 203 | $obj_request = new ListDocumentsRequest(); 204 | $obj_params = $this->prepareRequestParams($obj_request); 205 | $obj_params->setStartDocId($str_id)->setLimit(1); 206 | $this->execute('ListDocuments', $obj_request, new ListDocumentsResponse()); 207 | $obj_response = $this->processListResponse(); 208 | 209 | // Verify that the document is the one we want and if not, empty the response 210 | // This works around the lack of a "get-by-id" method on the Google Protocol Buffer 211 | if($obj_response->count > 0) { 212 | if($str_id !== $obj_response->docs[0]->getId()) { 213 | $obj_response->count = 0; 214 | $obj_response->docs = []; 215 | } 216 | } 217 | return $obj_response; 218 | } 219 | 220 | /** 221 | * Delete one or more documents by ID 222 | * 223 | * @param array $arr_ids 224 | */ 225 | public function delete(array $arr_ids) 226 | { 227 | $obj_request = new DeleteDocumentRequest(); 228 | $obj_params = $this->prepareRequestParams($obj_request); 229 | foreach($arr_ids as $str_id) { 230 | $obj_params->addDocId($str_id); 231 | } 232 | $this->execute('DeleteDocument', $obj_request, new DeleteDocumentResponse()); 233 | } 234 | 235 | /** 236 | * Run a Request 237 | * 238 | * @param $str_method 239 | * @param ProtocolMessage $obj_request 240 | * @param ProtocolMessage $obj_response 241 | * @return ProtocolMessage|null|object 242 | * @throws ApplicationError 243 | * @throws \Exception 244 | */ 245 | private function execute($str_method, ProtocolMessage $obj_request, ProtocolMessage $obj_response) 246 | { 247 | try { 248 | $this->obj_last_request = $obj_request; 249 | $this->obj_last_response = null; 250 | ApiProxy::makeSyncCall('search', $str_method, $obj_request, $obj_response, 60); 251 | $this->obj_last_response = $obj_response; 252 | } catch (ApplicationError $obj_exception) { 253 | throw $obj_exception; 254 | } 255 | } 256 | 257 | /** 258 | * Process a search response 259 | * 260 | * @return object 261 | */ 262 | private function processSearchResponse() 263 | { 264 | /** @var SearchResponse $obj_search_response */ 265 | $obj_search_response = $this->obj_last_response; 266 | $obj_response = (object)[ 267 | 'status' => $this->describeStatusCode($obj_search_response->getStatus()->getCode()), 268 | 'hits' => $obj_search_response->getMatchedCount(), 269 | 'count' => $obj_search_response->getResultSize(), 270 | 'results' => [] 271 | ]; 272 | $obj_mapper = new Mapper(); 273 | foreach($obj_search_response->getResultList() as $obj_result) { 274 | /** @var SearchResult $obj_result */ 275 | $obj_doc = $obj_mapper->fromGoogle($obj_result->getDocument(), $obj_result->getExpressionList()); 276 | $obj_response->results[] = (object)[ 277 | 'score' => ($obj_result->getScoreSize() > 0 ? $obj_result->getScore(0) : 0), 278 | 'doc' => $obj_doc 279 | ]; 280 | } 281 | 282 | // Map facets from results 283 | // @todo Restructure/review response object and moving this code to Mapper 284 | if($obj_search_response->getFacetResultSize() > 0) { 285 | $obj_response->facets = []; // create the empty facets placeholder in the response 286 | /** @var \google\appengine\FacetResult $obj_facet */ 287 | foreach($obj_search_response->getFacetResultList() as $obj_facet) { 288 | $arr_facet_values = []; 289 | /** @var \google\appengine\FacetResultValue $obj_facet_value */ 290 | foreach($obj_facet->getValueList() as $obj_facet_value) { 291 | $arr_facet_values[] = [ 292 | 'agg' => $obj_facet_value->getName(), 293 | 'count' => $obj_facet_value->getCount() 294 | ]; 295 | } 296 | $obj_response->facets[$obj_facet->getName()] = $arr_facet_values; 297 | } 298 | } 299 | 300 | return $obj_response; 301 | } 302 | 303 | /** 304 | * Process a document list response 305 | * 306 | * @return object 307 | */ 308 | private function processListResponse() 309 | { 310 | /** @var ListDocumentsResponse $obj_list_response */ 311 | $obj_list_response = $this->obj_last_response; 312 | $obj_response = (object)[ 313 | 'status' => $this->describeStatusCode($obj_list_response->getStatus()->getCode()), 314 | 'count' => $obj_list_response->getDocumentSize(), 315 | 'docs' => [] 316 | ]; 317 | $obj_mapper = new Mapper(); 318 | foreach($obj_list_response->getDocumentList() as $obj_document) { 319 | $obj_doc = $obj_mapper->fromGoogle($obj_document); 320 | $obj_response->docs[] = $obj_doc; 321 | } 322 | return $obj_response; 323 | } 324 | 325 | /** 326 | * Describe a request/response status 327 | * 328 | * @param $int_code 329 | * @return string 330 | */ 331 | private function describeStatusCode($int_code) 332 | { 333 | $arr_codes = [ 334 | ErrorCode::OK => 'OK', 335 | ErrorCode::INVALID_REQUEST => 'INVALID_REQUEST', 336 | ErrorCode::TRANSIENT_ERROR => 'TRANSIENT_ERROR', 337 | ErrorCode::INTERNAL_ERROR => 'INTERNAL_ERROR', 338 | ErrorCode::PERMISSION_DENIED => 'PERMISSION_DENIED', 339 | ErrorCode::TIMEOUT => 'TIMEOUT', 340 | ErrorCode::CONCURRENT_TRANSACTION => 'CONCURRENT_TRANSACTION' 341 | ]; 342 | if(isset($arr_codes[$int_code])) { 343 | return $arr_codes[$int_code]; 344 | } 345 | return 'UNKNOWN'; 346 | } 347 | 348 | /** 349 | * Get the last response message 350 | * 351 | * @return ProtocolMessage 352 | */ 353 | public function getLastResponse() 354 | { 355 | return $this->obj_last_response; 356 | } 357 | 358 | /** 359 | * Get the last request message 360 | * 361 | * @return ProtocolMessage 362 | */ 363 | public function getLastRequest() 364 | { 365 | return $this->obj_last_request; 366 | } 367 | 368 | } -------------------------------------------------------------------------------- /libs/google/document_pb.php: -------------------------------------------------------------------------------- 1 | lat)) { 37 | return 0.0; 38 | } 39 | return $this->lat; 40 | } 41 | public function setLat($val) { 42 | $this->lat = $val; 43 | return $this; 44 | } 45 | public function clearLat() { 46 | unset($this->lat); 47 | return $this; 48 | } 49 | public function hasLat() { 50 | return isset($this->lat); 51 | } 52 | public function getLng() { 53 | if (!isset($this->lng)) { 54 | return 0.0; 55 | } 56 | return $this->lng; 57 | } 58 | public function setLng($val) { 59 | $this->lng = $val; 60 | return $this; 61 | } 62 | public function clearLng() { 63 | unset($this->lng); 64 | return $this; 65 | } 66 | public function hasLng() { 67 | return isset($this->lng); 68 | } 69 | public function clear() { 70 | $this->clearLat(); 71 | $this->clearLng(); 72 | } 73 | public function byteSizePartial() { 74 | $res = 0; 75 | if (isset($this->lat)) { 76 | $res += 9; 77 | } 78 | if (isset($this->lng)) { 79 | $res += 9; 80 | } 81 | return $res; 82 | } 83 | public function outputPartial($out) { 84 | if (isset($this->lat)) { 85 | $out->putVarInt32(41); 86 | $out->putDouble($this->lat); 87 | } 88 | if (isset($this->lng)) { 89 | $out->putVarInt32(49); 90 | $out->putDouble($this->lng); 91 | } 92 | } 93 | public function tryMerge($d) { 94 | while($d->avail() > 0) { 95 | $tt = $d->getVarInt32(); 96 | switch ($tt) { 97 | case 36: return; 98 | case 41: 99 | $this->setLat($d->getDouble()); 100 | break; 101 | case 49: 102 | $this->setLng($d->getDouble()); 103 | break; 104 | case 0: 105 | throw new \google\net\ProtocolBufferDecodeError(); 106 | break; 107 | default: 108 | $d->skipData($tt); 109 | } 110 | }; 111 | } 112 | public function checkInitialized() { 113 | if (!isset($this->lat)) return 'lat'; 114 | if (!isset($this->lng)) return 'lng'; 115 | return null; 116 | } 117 | public function mergeFrom($x) { 118 | if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); } 119 | if ($x->hasLat()) { 120 | $this->setLat($x->getLat()); 121 | } 122 | if ($x->hasLng()) { 123 | $this->setLng($x->getLng()); 124 | } 125 | } 126 | public function equals($x) { 127 | if ($x === $this) { return true; } 128 | if (isset($this->lat) !== isset($x->lat)) return false; 129 | if (isset($this->lat) && $this->lat !== $x->lat) return false; 130 | if (isset($this->lng) !== isset($x->lng)) return false; 131 | if (isset($this->lng) && $this->lng !== $x->lng) return false; 132 | return true; 133 | } 134 | public function shortDebugString($prefix = "") { 135 | $res = ''; 136 | if (isset($this->lat)) { 137 | $res .= $prefix . "lat: " . $this->debugFormatDouble($this->lat) . "\n"; 138 | } 139 | if (isset($this->lng)) { 140 | $res .= $prefix . "lng: " . $this->debugFormatDouble($this->lng) . "\n"; 141 | } 142 | return $res; 143 | } 144 | } 145 | } 146 | namespace storage_onestore_v3 { 147 | class FieldValue extends \google\net\ProtocolMessage { 148 | public function getType() { 149 | if (!isset($this->type)) { 150 | return 0; 151 | } 152 | return $this->type; 153 | } 154 | public function setType($val) { 155 | $this->type = $val; 156 | return $this; 157 | } 158 | public function clearType() { 159 | unset($this->type); 160 | return $this; 161 | } 162 | public function hasType() { 163 | return isset($this->type); 164 | } 165 | public function getLanguage() { 166 | if (!isset($this->language)) { 167 | return "en"; 168 | } 169 | return $this->language; 170 | } 171 | public function setLanguage($val) { 172 | $this->language = $val; 173 | return $this; 174 | } 175 | public function clearLanguage() { 176 | unset($this->language); 177 | return $this; 178 | } 179 | public function hasLanguage() { 180 | return isset($this->language); 181 | } 182 | public function getStringValue() { 183 | if (!isset($this->string_value)) { 184 | return ''; 185 | } 186 | return $this->string_value; 187 | } 188 | public function setStringValue($val) { 189 | $this->string_value = $val; 190 | return $this; 191 | } 192 | public function clearStringValue() { 193 | unset($this->string_value); 194 | return $this; 195 | } 196 | public function hasStringValue() { 197 | return isset($this->string_value); 198 | } 199 | public function getGeo() { 200 | if (!isset($this->geo)) { 201 | return new \storage_onestore_v3\FieldValue\Geo(); 202 | } 203 | return $this->geo; 204 | } 205 | public function mutableGeo() { 206 | if (!isset($this->geo)) { 207 | $res = new \storage_onestore_v3\FieldValue\Geo(); 208 | $this->geo = $res; 209 | return $res; 210 | } 211 | return $this->geo; 212 | } 213 | public function clearGeo() { 214 | if (isset($this->geo)) { 215 | unset($this->geo); 216 | } 217 | } 218 | public function hasGeo() { 219 | return isset($this->geo); 220 | } 221 | public function clear() { 222 | $this->clearType(); 223 | $this->clearLanguage(); 224 | $this->clearStringValue(); 225 | $this->clearGeo(); 226 | } 227 | public function byteSizePartial() { 228 | $res = 0; 229 | if (isset($this->type)) { 230 | $res += 1; 231 | $res += $this->lengthVarInt64($this->type); 232 | } 233 | if (isset($this->language)) { 234 | $res += 1; 235 | $res += $this->lengthString(strlen($this->language)); 236 | } 237 | if (isset($this->string_value)) { 238 | $res += 1; 239 | $res += $this->lengthString(strlen($this->string_value)); 240 | } 241 | if (isset($this->geo)) { 242 | $res += 2; 243 | $res += $this->geo->byteSizePartial(); 244 | } 245 | return $res; 246 | } 247 | public function outputPartial($out) { 248 | if (isset($this->type)) { 249 | $out->putVarInt32(8); 250 | $out->putVarInt32($this->type); 251 | } 252 | if (isset($this->language)) { 253 | $out->putVarInt32(18); 254 | $out->putPrefixedString($this->language); 255 | } 256 | if (isset($this->string_value)) { 257 | $out->putVarInt32(26); 258 | $out->putPrefixedString($this->string_value); 259 | } 260 | if (isset($this->geo)) { 261 | $out->putVarInt32(35); 262 | $this->geo->outputPartial($out); 263 | $out->putVarInt32(36); 264 | } 265 | } 266 | public function tryMerge($d) { 267 | while($d->avail() > 0) { 268 | $tt = $d->getVarInt32(); 269 | switch ($tt) { 270 | case 8: 271 | $this->setType($d->getVarInt32()); 272 | break; 273 | case 18: 274 | $length = $d->getVarInt32(); 275 | $this->setLanguage(substr($d->buffer(), $d->pos(), $length)); 276 | $d->skip($length); 277 | break; 278 | case 26: 279 | $length = $d->getVarInt32(); 280 | $this->setStringValue(substr($d->buffer(), $d->pos(), $length)); 281 | $d->skip($length); 282 | break; 283 | case 35: 284 | $this->mutableGeo()->tryMerge($d); 285 | break; 286 | case 0: 287 | throw new \google\net\ProtocolBufferDecodeError(); 288 | break; 289 | default: 290 | $d->skipData($tt); 291 | } 292 | }; 293 | } 294 | public function checkInitialized() { 295 | if (isset($this->geo) && (!$this->geo->isInitialized())) return 'geo'; 296 | return null; 297 | } 298 | public function mergeFrom($x) { 299 | if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); } 300 | if ($x->hasType()) { 301 | $this->setType($x->getType()); 302 | } 303 | if ($x->hasLanguage()) { 304 | $this->setLanguage($x->getLanguage()); 305 | } 306 | if ($x->hasStringValue()) { 307 | $this->setStringValue($x->getStringValue()); 308 | } 309 | if ($x->hasGeo()) { 310 | $this->mutableGeo()->mergeFrom($x->getGeo()); 311 | } 312 | } 313 | public function equals($x) { 314 | if ($x === $this) { return true; } 315 | if (isset($this->type) !== isset($x->type)) return false; 316 | if (isset($this->type) && $this->type !== $x->type) return false; 317 | if (isset($this->language) !== isset($x->language)) return false; 318 | if (isset($this->language) && $this->language !== $x->language) return false; 319 | if (isset($this->string_value) !== isset($x->string_value)) return false; 320 | if (isset($this->string_value) && $this->string_value !== $x->string_value) return false; 321 | if (isset($this->geo) !== isset($x->geo)) return false; 322 | if (isset($this->geo) && !$this->geo->equals($x->geo)) return false; 323 | return true; 324 | } 325 | public function shortDebugString($prefix = "") { 326 | $res = ''; 327 | if (isset($this->type)) { 328 | $res .= $prefix . "type: " . ($this->type) . "\n"; 329 | } 330 | if (isset($this->language)) { 331 | $res .= $prefix . "language: " . $this->debugFormatString($this->language) . "\n"; 332 | } 333 | if (isset($this->string_value)) { 334 | $res .= $prefix . "string_value: " . $this->debugFormatString($this->string_value) . "\n"; 335 | } 336 | if (isset($this->geo)) { 337 | $res .= $prefix . "Geo {\n" . $this->geo->shortDebugString($prefix . " ") . $prefix . "}\n"; 338 | } 339 | return $res; 340 | } 341 | } 342 | } 343 | namespace storage_onestore_v3 { 344 | class Field extends \google\net\ProtocolMessage { 345 | public function getName() { 346 | if (!isset($this->name)) { 347 | return ''; 348 | } 349 | return $this->name; 350 | } 351 | public function setName($val) { 352 | $this->name = $val; 353 | return $this; 354 | } 355 | public function clearName() { 356 | unset($this->name); 357 | return $this; 358 | } 359 | public function hasName() { 360 | return isset($this->name); 361 | } 362 | public function getValue() { 363 | if (!isset($this->value)) { 364 | return new \storage_onestore_v3\FieldValue(); 365 | } 366 | return $this->value; 367 | } 368 | public function mutableValue() { 369 | if (!isset($this->value)) { 370 | $res = new \storage_onestore_v3\FieldValue(); 371 | $this->value = $res; 372 | return $res; 373 | } 374 | return $this->value; 375 | } 376 | public function clearValue() { 377 | if (isset($this->value)) { 378 | unset($this->value); 379 | } 380 | } 381 | public function hasValue() { 382 | return isset($this->value); 383 | } 384 | public function clear() { 385 | $this->clearName(); 386 | $this->clearValue(); 387 | } 388 | public function byteSizePartial() { 389 | $res = 0; 390 | if (isset($this->name)) { 391 | $res += 1; 392 | $res += $this->lengthString(strlen($this->name)); 393 | } 394 | if (isset($this->value)) { 395 | $res += 1; 396 | $res += $this->lengthString($this->value->byteSizePartial()); 397 | } 398 | return $res; 399 | } 400 | public function outputPartial($out) { 401 | if (isset($this->name)) { 402 | $out->putVarInt32(10); 403 | $out->putPrefixedString($this->name); 404 | } 405 | if (isset($this->value)) { 406 | $out->putVarInt32(18); 407 | $out->putVarInt32($this->value->byteSizePartial()); 408 | $this->value->outputPartial($out); 409 | } 410 | } 411 | public function tryMerge($d) { 412 | while($d->avail() > 0) { 413 | $tt = $d->getVarInt32(); 414 | switch ($tt) { 415 | case 10: 416 | $length = $d->getVarInt32(); 417 | $this->setName(substr($d->buffer(), $d->pos(), $length)); 418 | $d->skip($length); 419 | break; 420 | case 18: 421 | $length = $d->getVarInt32(); 422 | $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length); 423 | $d->skip($length); 424 | $this->mutableValue()->tryMerge($tmp); 425 | break; 426 | case 0: 427 | throw new \google\net\ProtocolBufferDecodeError(); 428 | break; 429 | default: 430 | $d->skipData($tt); 431 | } 432 | }; 433 | } 434 | public function checkInitialized() { 435 | if (!isset($this->name)) return 'name'; 436 | if ((!isset($this->value)) || (!$this->value->isInitialized())) return 'value'; 437 | return null; 438 | } 439 | public function mergeFrom($x) { 440 | if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); } 441 | if ($x->hasName()) { 442 | $this->setName($x->getName()); 443 | } 444 | if ($x->hasValue()) { 445 | $this->mutableValue()->mergeFrom($x->getValue()); 446 | } 447 | } 448 | public function equals($x) { 449 | if ($x === $this) { return true; } 450 | if (isset($this->name) !== isset($x->name)) return false; 451 | if (isset($this->name) && $this->name !== $x->name) return false; 452 | if (isset($this->value) !== isset($x->value)) return false; 453 | if (isset($this->value) && !$this->value->equals($x->value)) return false; 454 | return true; 455 | } 456 | public function shortDebugString($prefix = "") { 457 | $res = ''; 458 | if (isset($this->name)) { 459 | $res .= $prefix . "name: " . $this->debugFormatString($this->name) . "\n"; 460 | } 461 | if (isset($this->value)) { 462 | $res .= $prefix . "value <\n" . $this->value->shortDebugString($prefix . " ") . $prefix . ">\n"; 463 | } 464 | return $res; 465 | } 466 | } 467 | } 468 | namespace storage_onestore_v3 { 469 | class FieldTypes extends \google\net\ProtocolMessage { 470 | private $type = array(); 471 | public function getName() { 472 | if (!isset($this->name)) { 473 | return ''; 474 | } 475 | return $this->name; 476 | } 477 | public function setName($val) { 478 | $this->name = $val; 479 | return $this; 480 | } 481 | public function clearName() { 482 | unset($this->name); 483 | return $this; 484 | } 485 | public function hasName() { 486 | return isset($this->name); 487 | } 488 | public function getTypeSize() { 489 | return sizeof($this->type); 490 | } 491 | public function getTypeList() { 492 | return $this->type; 493 | } 494 | public function getType($idx) { 495 | return $this->type[$idx]; 496 | } 497 | public function setType($idx, $val) { 498 | $this->type[$idx] = $val; 499 | return $this; 500 | } 501 | public function addType($val) { 502 | $this->type[] = $val; 503 | return $this; 504 | } 505 | public function clearType() { 506 | $this->type = array(); 507 | } 508 | public function clear() { 509 | $this->clearName(); 510 | $this->clearType(); 511 | } 512 | public function byteSizePartial() { 513 | $res = 0; 514 | if (isset($this->name)) { 515 | $res += 1; 516 | $res += $this->lengthString(strlen($this->name)); 517 | } 518 | $this->checkProtoArray($this->type); 519 | $res += 1 * sizeof($this->type); 520 | foreach ($this->type as $value) { 521 | $res += $this->lengthVarInt64($value); 522 | } 523 | return $res; 524 | } 525 | public function outputPartial($out) { 526 | if (isset($this->name)) { 527 | $out->putVarInt32(10); 528 | $out->putPrefixedString($this->name); 529 | } 530 | $this->checkProtoArray($this->type); 531 | foreach ($this->type as $value) { 532 | $out->putVarInt32(16); 533 | $out->putVarInt32($value); 534 | } 535 | } 536 | public function tryMerge($d) { 537 | while($d->avail() > 0) { 538 | $tt = $d->getVarInt32(); 539 | switch ($tt) { 540 | case 10: 541 | $length = $d->getVarInt32(); 542 | $this->setName(substr($d->buffer(), $d->pos(), $length)); 543 | $d->skip($length); 544 | break; 545 | case 16: 546 | $this->addType($d->getVarInt32()); 547 | break; 548 | case 0: 549 | throw new \google\net\ProtocolBufferDecodeError(); 550 | break; 551 | default: 552 | $d->skipData($tt); 553 | } 554 | }; 555 | } 556 | public function checkInitialized() { 557 | if (!isset($this->name)) return 'name'; 558 | return null; 559 | } 560 | public function mergeFrom($x) { 561 | if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); } 562 | if ($x->hasName()) { 563 | $this->setName($x->getName()); 564 | } 565 | foreach ($x->getTypeList() as $v) { 566 | $this->addType($v); 567 | } 568 | } 569 | public function equals($x) { 570 | if ($x === $this) { return true; } 571 | if (isset($this->name) !== isset($x->name)) return false; 572 | if (isset($this->name) && $this->name !== $x->name) return false; 573 | if (sizeof($this->type) !== sizeof($x->type)) return false; 574 | foreach (array_map(null, $this->type, $x->type) as $v) { 575 | if ($v[0] !== $v[1]) return false; 576 | } 577 | return true; 578 | } 579 | public function shortDebugString($prefix = "") { 580 | $res = ''; 581 | if (isset($this->name)) { 582 | $res .= $prefix . "name: " . $this->debugFormatString($this->name) . "\n"; 583 | } 584 | foreach ($this->type as $value) { 585 | $res .= $prefix . "type: " . ($value) . "\n"; 586 | } 587 | return $res; 588 | } 589 | } 590 | } 591 | namespace storage_onestore_v3 { 592 | class IndexShardSettings extends \google\net\ProtocolMessage { 593 | private $prev_num_shards = array(); 594 | private $prev_num_shards_search_false = array(); 595 | public function getPrevNumShardsSize() { 596 | return sizeof($this->prev_num_shards); 597 | } 598 | public function getPrevNumShardsList() { 599 | return $this->prev_num_shards; 600 | } 601 | public function getPrevNumShards($idx) { 602 | return $this->prev_num_shards[$idx]; 603 | } 604 | public function setPrevNumShards($idx, $val) { 605 | $this->prev_num_shards[$idx] = $val; 606 | return $this; 607 | } 608 | public function addPrevNumShards($val) { 609 | $this->prev_num_shards[] = $val; 610 | return $this; 611 | } 612 | public function clearPrevNumShards() { 613 | $this->prev_num_shards = array(); 614 | } 615 | public function getNumShards() { 616 | if (!isset($this->num_shards)) { 617 | return 1; 618 | } 619 | return $this->num_shards; 620 | } 621 | public function setNumShards($val) { 622 | $this->num_shards = $val; 623 | return $this; 624 | } 625 | public function clearNumShards() { 626 | unset($this->num_shards); 627 | return $this; 628 | } 629 | public function hasNumShards() { 630 | return isset($this->num_shards); 631 | } 632 | public function getPrevNumShardsSearchFalseSize() { 633 | return sizeof($this->prev_num_shards_search_false); 634 | } 635 | public function getPrevNumShardsSearchFalseList() { 636 | return $this->prev_num_shards_search_false; 637 | } 638 | public function getPrevNumShardsSearchFalse($idx) { 639 | return $this->prev_num_shards_search_false[$idx]; 640 | } 641 | public function setPrevNumShardsSearchFalse($idx, $val) { 642 | $this->prev_num_shards_search_false[$idx] = $val; 643 | return $this; 644 | } 645 | public function addPrevNumShardsSearchFalse($val) { 646 | $this->prev_num_shards_search_false[] = $val; 647 | return $this; 648 | } 649 | public function clearPrevNumShardsSearchFalse() { 650 | $this->prev_num_shards_search_false = array(); 651 | } 652 | public function getLocalReplica() { 653 | if (!isset($this->local_replica)) { 654 | return ''; 655 | } 656 | return $this->local_replica; 657 | } 658 | public function setLocalReplica($val) { 659 | $this->local_replica = $val; 660 | return $this; 661 | } 662 | public function clearLocalReplica() { 663 | unset($this->local_replica); 664 | return $this; 665 | } 666 | public function hasLocalReplica() { 667 | return isset($this->local_replica); 668 | } 669 | public function clear() { 670 | $this->clearPrevNumShards(); 671 | $this->clearNumShards(); 672 | $this->clearPrevNumShardsSearchFalse(); 673 | $this->clearLocalReplica(); 674 | } 675 | public function byteSizePartial() { 676 | $res = 0; 677 | $this->checkProtoArray($this->prev_num_shards); 678 | $res += 1 * sizeof($this->prev_num_shards); 679 | foreach ($this->prev_num_shards as $value) { 680 | $res += $this->lengthVarInt64($value); 681 | } 682 | if (isset($this->num_shards)) { 683 | $res += 1; 684 | $res += $this->lengthVarInt64($this->num_shards); 685 | } 686 | $this->checkProtoArray($this->prev_num_shards_search_false); 687 | $res += 1 * sizeof($this->prev_num_shards_search_false); 688 | foreach ($this->prev_num_shards_search_false as $value) { 689 | $res += $this->lengthVarInt64($value); 690 | } 691 | if (isset($this->local_replica)) { 692 | $res += 1; 693 | $res += $this->lengthString(strlen($this->local_replica)); 694 | } 695 | return $res; 696 | } 697 | public function outputPartial($out) { 698 | $this->checkProtoArray($this->prev_num_shards); 699 | foreach ($this->prev_num_shards as $value) { 700 | $out->putVarInt32(8); 701 | $out->putVarInt32($value); 702 | } 703 | if (isset($this->num_shards)) { 704 | $out->putVarInt32(16); 705 | $out->putVarInt32($this->num_shards); 706 | } 707 | $this->checkProtoArray($this->prev_num_shards_search_false); 708 | foreach ($this->prev_num_shards_search_false as $value) { 709 | $out->putVarInt32(24); 710 | $out->putVarInt32($value); 711 | } 712 | if (isset($this->local_replica)) { 713 | $out->putVarInt32(34); 714 | $out->putPrefixedString($this->local_replica); 715 | } 716 | } 717 | public function tryMerge($d) { 718 | while($d->avail() > 0) { 719 | $tt = $d->getVarInt32(); 720 | switch ($tt) { 721 | case 8: 722 | $this->addPrevNumShards($d->getVarInt32()); 723 | break; 724 | case 16: 725 | $this->setNumShards($d->getVarInt32()); 726 | break; 727 | case 24: 728 | $this->addPrevNumShardsSearchFalse($d->getVarInt32()); 729 | break; 730 | case 34: 731 | $length = $d->getVarInt32(); 732 | $this->setLocalReplica(substr($d->buffer(), $d->pos(), $length)); 733 | $d->skip($length); 734 | break; 735 | case 0: 736 | throw new \google\net\ProtocolBufferDecodeError(); 737 | break; 738 | default: 739 | $d->skipData($tt); 740 | } 741 | }; 742 | } 743 | public function checkInitialized() { 744 | if (!isset($this->num_shards)) return 'num_shards'; 745 | return null; 746 | } 747 | public function mergeFrom($x) { 748 | if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); } 749 | foreach ($x->getPrevNumShardsList() as $v) { 750 | $this->addPrevNumShards($v); 751 | } 752 | if ($x->hasNumShards()) { 753 | $this->setNumShards($x->getNumShards()); 754 | } 755 | foreach ($x->getPrevNumShardsSearchFalseList() as $v) { 756 | $this->addPrevNumShardsSearchFalse($v); 757 | } 758 | if ($x->hasLocalReplica()) { 759 | $this->setLocalReplica($x->getLocalReplica()); 760 | } 761 | } 762 | public function equals($x) { 763 | if ($x === $this) { return true; } 764 | if (sizeof($this->prev_num_shards) !== sizeof($x->prev_num_shards)) return false; 765 | foreach (array_map(null, $this->prev_num_shards, $x->prev_num_shards) as $v) { 766 | if (!$this->integerEquals($v[0], $v[1])) return false; 767 | } 768 | if (isset($this->num_shards) !== isset($x->num_shards)) return false; 769 | if (isset($this->num_shards) && !$this->integerEquals($this->num_shards, $x->num_shards)) return false; 770 | if (sizeof($this->prev_num_shards_search_false) !== sizeof($x->prev_num_shards_search_false)) return false; 771 | foreach (array_map(null, $this->prev_num_shards_search_false, $x->prev_num_shards_search_false) as $v) { 772 | if (!$this->integerEquals($v[0], $v[1])) return false; 773 | } 774 | if (isset($this->local_replica) !== isset($x->local_replica)) return false; 775 | if (isset($this->local_replica) && $this->local_replica !== $x->local_replica) return false; 776 | return true; 777 | } 778 | public function shortDebugString($prefix = "") { 779 | $res = ''; 780 | foreach ($this->prev_num_shards as $value) { 781 | $res .= $prefix . "prev_num_shards: " . $this->debugFormatInt32($value) . "\n"; 782 | } 783 | if (isset($this->num_shards)) { 784 | $res .= $prefix . "num_shards: " . $this->debugFormatInt32($this->num_shards) . "\n"; 785 | } 786 | foreach ($this->prev_num_shards_search_false as $value) { 787 | $res .= $prefix . "prev_num_shards_search_false: " . $this->debugFormatInt32($value) . "\n"; 788 | } 789 | if (isset($this->local_replica)) { 790 | $res .= $prefix . "local_replica: " . $this->debugFormatString($this->local_replica) . "\n"; 791 | } 792 | return $res; 793 | } 794 | } 795 | } 796 | namespace storage_onestore_v3\IndexMetadata { 797 | class IndexState { 798 | const ACTIVE = 0; 799 | const SOFT_DELETED = 1; 800 | const PURGING = 2; 801 | } 802 | } 803 | namespace storage_onestore_v3 { 804 | class IndexMetadata extends \google\net\ProtocolMessage { 805 | public function getIsOverFieldNumberThreshold() { 806 | if (!isset($this->is_over_field_number_threshold)) { 807 | return false; 808 | } 809 | return $this->is_over_field_number_threshold; 810 | } 811 | public function setIsOverFieldNumberThreshold($val) { 812 | $this->is_over_field_number_threshold = $val; 813 | return $this; 814 | } 815 | public function clearIsOverFieldNumberThreshold() { 816 | unset($this->is_over_field_number_threshold); 817 | return $this; 818 | } 819 | public function hasIsOverFieldNumberThreshold() { 820 | return isset($this->is_over_field_number_threshold); 821 | } 822 | public function getIndexShardSettings() { 823 | if (!isset($this->index_shard_settings)) { 824 | return new \storage_onestore_v3\IndexShardSettings(); 825 | } 826 | return $this->index_shard_settings; 827 | } 828 | public function mutableIndexShardSettings() { 829 | if (!isset($this->index_shard_settings)) { 830 | $res = new \storage_onestore_v3\IndexShardSettings(); 831 | $this->index_shard_settings = $res; 832 | return $res; 833 | } 834 | return $this->index_shard_settings; 835 | } 836 | public function clearIndexShardSettings() { 837 | if (isset($this->index_shard_settings)) { 838 | unset($this->index_shard_settings); 839 | } 840 | } 841 | public function hasIndexShardSettings() { 842 | return isset($this->index_shard_settings); 843 | } 844 | public function getIndexState() { 845 | if (!isset($this->index_state)) { 846 | return 0; 847 | } 848 | return $this->index_state; 849 | } 850 | public function setIndexState($val) { 851 | $this->index_state = $val; 852 | return $this; 853 | } 854 | public function clearIndexState() { 855 | unset($this->index_state); 856 | return $this; 857 | } 858 | public function hasIndexState() { 859 | return isset($this->index_state); 860 | } 861 | public function getIndexDeleteTime() { 862 | if (!isset($this->index_delete_time)) { 863 | return "0"; 864 | } 865 | return $this->index_delete_time; 866 | } 867 | public function setIndexDeleteTime($val) { 868 | if (is_double($val)) { 869 | $this->index_delete_time = sprintf('%0.0F', $val); 870 | } else { 871 | $this->index_delete_time = $val; 872 | } 873 | return $this; 874 | } 875 | public function clearIndexDeleteTime() { 876 | unset($this->index_delete_time); 877 | return $this; 878 | } 879 | public function hasIndexDeleteTime() { 880 | return isset($this->index_delete_time); 881 | } 882 | public function clear() { 883 | $this->clearIsOverFieldNumberThreshold(); 884 | $this->clearIndexShardSettings(); 885 | $this->clearIndexState(); 886 | $this->clearIndexDeleteTime(); 887 | } 888 | public function byteSizePartial() { 889 | $res = 0; 890 | if (isset($this->is_over_field_number_threshold)) { 891 | $res += 2; 892 | } 893 | if (isset($this->index_shard_settings)) { 894 | $res += 1; 895 | $res += $this->lengthString($this->index_shard_settings->byteSizePartial()); 896 | } 897 | if (isset($this->index_state)) { 898 | $res += 1; 899 | $res += $this->lengthVarInt64($this->index_state); 900 | } 901 | if (isset($this->index_delete_time)) { 902 | $res += 1; 903 | $res += $this->lengthVarInt64($this->index_delete_time); 904 | } 905 | return $res; 906 | } 907 | public function outputPartial($out) { 908 | if (isset($this->is_over_field_number_threshold)) { 909 | $out->putVarInt32(8); 910 | $out->putBoolean($this->is_over_field_number_threshold); 911 | } 912 | if (isset($this->index_shard_settings)) { 913 | $out->putVarInt32(18); 914 | $out->putVarInt32($this->index_shard_settings->byteSizePartial()); 915 | $this->index_shard_settings->outputPartial($out); 916 | } 917 | if (isset($this->index_state)) { 918 | $out->putVarInt32(24); 919 | $out->putVarInt32($this->index_state); 920 | } 921 | if (isset($this->index_delete_time)) { 922 | $out->putVarInt32(32); 923 | $out->putVarInt64($this->index_delete_time); 924 | } 925 | } 926 | public function tryMerge($d) { 927 | while($d->avail() > 0) { 928 | $tt = $d->getVarInt32(); 929 | switch ($tt) { 930 | case 8: 931 | $this->setIsOverFieldNumberThreshold($d->getBoolean()); 932 | break; 933 | case 18: 934 | $length = $d->getVarInt32(); 935 | $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length); 936 | $d->skip($length); 937 | $this->mutableIndexShardSettings()->tryMerge($tmp); 938 | break; 939 | case 24: 940 | $this->setIndexState($d->getVarInt32()); 941 | break; 942 | case 32: 943 | $this->setIndexDeleteTime($d->getVarInt64()); 944 | break; 945 | case 0: 946 | throw new \google\net\ProtocolBufferDecodeError(); 947 | break; 948 | default: 949 | $d->skipData($tt); 950 | } 951 | }; 952 | } 953 | public function checkInitialized() { 954 | if (isset($this->index_shard_settings) && (!$this->index_shard_settings->isInitialized())) return 'index_shard_settings'; 955 | return null; 956 | } 957 | public function mergeFrom($x) { 958 | if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); } 959 | if ($x->hasIsOverFieldNumberThreshold()) { 960 | $this->setIsOverFieldNumberThreshold($x->getIsOverFieldNumberThreshold()); 961 | } 962 | if ($x->hasIndexShardSettings()) { 963 | $this->mutableIndexShardSettings()->mergeFrom($x->getIndexShardSettings()); 964 | } 965 | if ($x->hasIndexState()) { 966 | $this->setIndexState($x->getIndexState()); 967 | } 968 | if ($x->hasIndexDeleteTime()) { 969 | $this->setIndexDeleteTime($x->getIndexDeleteTime()); 970 | } 971 | } 972 | public function equals($x) { 973 | if ($x === $this) { return true; } 974 | if (isset($this->is_over_field_number_threshold) !== isset($x->is_over_field_number_threshold)) return false; 975 | if (isset($this->is_over_field_number_threshold) && $this->is_over_field_number_threshold !== $x->is_over_field_number_threshold) return false; 976 | if (isset($this->index_shard_settings) !== isset($x->index_shard_settings)) return false; 977 | if (isset($this->index_shard_settings) && !$this->index_shard_settings->equals($x->index_shard_settings)) return false; 978 | if (isset($this->index_state) !== isset($x->index_state)) return false; 979 | if (isset($this->index_state) && $this->index_state !== $x->index_state) return false; 980 | if (isset($this->index_delete_time) !== isset($x->index_delete_time)) return false; 981 | if (isset($this->index_delete_time) && !$this->integerEquals($this->index_delete_time, $x->index_delete_time)) return false; 982 | return true; 983 | } 984 | public function shortDebugString($prefix = "") { 985 | $res = ''; 986 | if (isset($this->is_over_field_number_threshold)) { 987 | $res .= $prefix . "is_over_field_number_threshold: " . $this->debugFormatBool($this->is_over_field_number_threshold) . "\n"; 988 | } 989 | if (isset($this->index_shard_settings)) { 990 | $res .= $prefix . "index_shard_settings <\n" . $this->index_shard_settings->shortDebugString($prefix . " ") . $prefix . ">\n"; 991 | } 992 | if (isset($this->index_state)) { 993 | $res .= $prefix . "index_state: " . ($this->index_state) . "\n"; 994 | } 995 | if (isset($this->index_delete_time)) { 996 | $res .= $prefix . "index_delete_time: " . $this->debugFormatInt64($this->index_delete_time) . "\n"; 997 | } 998 | return $res; 999 | } 1000 | } 1001 | } 1002 | namespace storage_onestore_v3\FacetValue { 1003 | class ContentType { 1004 | const ATOM = 2; 1005 | const NUMBER = 4; 1006 | } 1007 | } 1008 | namespace storage_onestore_v3 { 1009 | class FacetValue extends \google\net\ProtocolMessage { 1010 | public function getType() { 1011 | if (!isset($this->type)) { 1012 | return 2; 1013 | } 1014 | return $this->type; 1015 | } 1016 | public function setType($val) { 1017 | $this->type = $val; 1018 | return $this; 1019 | } 1020 | public function clearType() { 1021 | unset($this->type); 1022 | return $this; 1023 | } 1024 | public function hasType() { 1025 | return isset($this->type); 1026 | } 1027 | public function getStringValue() { 1028 | if (!isset($this->string_value)) { 1029 | return ''; 1030 | } 1031 | return $this->string_value; 1032 | } 1033 | public function setStringValue($val) { 1034 | $this->string_value = $val; 1035 | return $this; 1036 | } 1037 | public function clearStringValue() { 1038 | unset($this->string_value); 1039 | return $this; 1040 | } 1041 | public function hasStringValue() { 1042 | return isset($this->string_value); 1043 | } 1044 | public function clear() { 1045 | $this->clearType(); 1046 | $this->clearStringValue(); 1047 | } 1048 | public function byteSizePartial() { 1049 | $res = 0; 1050 | if (isset($this->type)) { 1051 | $res += 1; 1052 | $res += $this->lengthVarInt64($this->type); 1053 | } 1054 | if (isset($this->string_value)) { 1055 | $res += 1; 1056 | $res += $this->lengthString(strlen($this->string_value)); 1057 | } 1058 | return $res; 1059 | } 1060 | public function outputPartial($out) { 1061 | if (isset($this->type)) { 1062 | $out->putVarInt32(8); 1063 | $out->putVarInt32($this->type); 1064 | } 1065 | if (isset($this->string_value)) { 1066 | $out->putVarInt32(26); 1067 | $out->putPrefixedString($this->string_value); 1068 | } 1069 | } 1070 | public function tryMerge($d) { 1071 | while($d->avail() > 0) { 1072 | $tt = $d->getVarInt32(); 1073 | switch ($tt) { 1074 | case 8: 1075 | $this->setType($d->getVarInt32()); 1076 | break; 1077 | case 26: 1078 | $length = $d->getVarInt32(); 1079 | $this->setStringValue(substr($d->buffer(), $d->pos(), $length)); 1080 | $d->skip($length); 1081 | break; 1082 | case 0: 1083 | throw new \google\net\ProtocolBufferDecodeError(); 1084 | break; 1085 | default: 1086 | $d->skipData($tt); 1087 | } 1088 | }; 1089 | } 1090 | public function checkInitialized() { 1091 | return null; 1092 | } 1093 | public function mergeFrom($x) { 1094 | if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); } 1095 | if ($x->hasType()) { 1096 | $this->setType($x->getType()); 1097 | } 1098 | if ($x->hasStringValue()) { 1099 | $this->setStringValue($x->getStringValue()); 1100 | } 1101 | } 1102 | public function equals($x) { 1103 | if ($x === $this) { return true; } 1104 | if (isset($this->type) !== isset($x->type)) return false; 1105 | if (isset($this->type) && $this->type !== $x->type) return false; 1106 | if (isset($this->string_value) !== isset($x->string_value)) return false; 1107 | if (isset($this->string_value) && $this->string_value !== $x->string_value) return false; 1108 | return true; 1109 | } 1110 | public function shortDebugString($prefix = "") { 1111 | $res = ''; 1112 | if (isset($this->type)) { 1113 | $res .= $prefix . "type: " . ($this->type) . "\n"; 1114 | } 1115 | if (isset($this->string_value)) { 1116 | $res .= $prefix . "string_value: " . $this->debugFormatString($this->string_value) . "\n"; 1117 | } 1118 | return $res; 1119 | } 1120 | } 1121 | } 1122 | namespace storage_onestore_v3 { 1123 | class Facet extends \google\net\ProtocolMessage { 1124 | public function getName() { 1125 | if (!isset($this->name)) { 1126 | return ''; 1127 | } 1128 | return $this->name; 1129 | } 1130 | public function setName($val) { 1131 | $this->name = $val; 1132 | return $this; 1133 | } 1134 | public function clearName() { 1135 | unset($this->name); 1136 | return $this; 1137 | } 1138 | public function hasName() { 1139 | return isset($this->name); 1140 | } 1141 | public function getValue() { 1142 | if (!isset($this->value)) { 1143 | return new \storage_onestore_v3\FacetValue(); 1144 | } 1145 | return $this->value; 1146 | } 1147 | public function mutableValue() { 1148 | if (!isset($this->value)) { 1149 | $res = new \storage_onestore_v3\FacetValue(); 1150 | $this->value = $res; 1151 | return $res; 1152 | } 1153 | return $this->value; 1154 | } 1155 | public function clearValue() { 1156 | if (isset($this->value)) { 1157 | unset($this->value); 1158 | } 1159 | } 1160 | public function hasValue() { 1161 | return isset($this->value); 1162 | } 1163 | public function clear() { 1164 | $this->clearName(); 1165 | $this->clearValue(); 1166 | } 1167 | public function byteSizePartial() { 1168 | $res = 0; 1169 | if (isset($this->name)) { 1170 | $res += 1; 1171 | $res += $this->lengthString(strlen($this->name)); 1172 | } 1173 | if (isset($this->value)) { 1174 | $res += 1; 1175 | $res += $this->lengthString($this->value->byteSizePartial()); 1176 | } 1177 | return $res; 1178 | } 1179 | public function outputPartial($out) { 1180 | if (isset($this->name)) { 1181 | $out->putVarInt32(10); 1182 | $out->putPrefixedString($this->name); 1183 | } 1184 | if (isset($this->value)) { 1185 | $out->putVarInt32(18); 1186 | $out->putVarInt32($this->value->byteSizePartial()); 1187 | $this->value->outputPartial($out); 1188 | } 1189 | } 1190 | public function tryMerge($d) { 1191 | while($d->avail() > 0) { 1192 | $tt = $d->getVarInt32(); 1193 | switch ($tt) { 1194 | case 10: 1195 | $length = $d->getVarInt32(); 1196 | $this->setName(substr($d->buffer(), $d->pos(), $length)); 1197 | $d->skip($length); 1198 | break; 1199 | case 18: 1200 | $length = $d->getVarInt32(); 1201 | $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length); 1202 | $d->skip($length); 1203 | $this->mutableValue()->tryMerge($tmp); 1204 | break; 1205 | case 0: 1206 | throw new \google\net\ProtocolBufferDecodeError(); 1207 | break; 1208 | default: 1209 | $d->skipData($tt); 1210 | } 1211 | }; 1212 | } 1213 | public function checkInitialized() { 1214 | if (!isset($this->name)) return 'name'; 1215 | if ((!isset($this->value)) || (!$this->value->isInitialized())) return 'value'; 1216 | return null; 1217 | } 1218 | public function mergeFrom($x) { 1219 | if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); } 1220 | if ($x->hasName()) { 1221 | $this->setName($x->getName()); 1222 | } 1223 | if ($x->hasValue()) { 1224 | $this->mutableValue()->mergeFrom($x->getValue()); 1225 | } 1226 | } 1227 | public function equals($x) { 1228 | if ($x === $this) { return true; } 1229 | if (isset($this->name) !== isset($x->name)) return false; 1230 | if (isset($this->name) && $this->name !== $x->name) return false; 1231 | if (isset($this->value) !== isset($x->value)) return false; 1232 | if (isset($this->value) && !$this->value->equals($x->value)) return false; 1233 | return true; 1234 | } 1235 | public function shortDebugString($prefix = "") { 1236 | $res = ''; 1237 | if (isset($this->name)) { 1238 | $res .= $prefix . "name: " . $this->debugFormatString($this->name) . "\n"; 1239 | } 1240 | if (isset($this->value)) { 1241 | $res .= $prefix . "value <\n" . $this->value->shortDebugString($prefix . " ") . $prefix . ">\n"; 1242 | } 1243 | return $res; 1244 | } 1245 | } 1246 | } 1247 | namespace storage_onestore_v3 { 1248 | class DocumentMetadata extends \google\net\ProtocolMessage { 1249 | public function getVersion() { 1250 | if (!isset($this->version)) { 1251 | return "0"; 1252 | } 1253 | return $this->version; 1254 | } 1255 | public function setVersion($val) { 1256 | if (is_double($val)) { 1257 | $this->version = sprintf('%0.0F', $val); 1258 | } else { 1259 | $this->version = $val; 1260 | } 1261 | return $this; 1262 | } 1263 | public function clearVersion() { 1264 | unset($this->version); 1265 | return $this; 1266 | } 1267 | public function hasVersion() { 1268 | return isset($this->version); 1269 | } 1270 | public function getCommittedStVersion() { 1271 | if (!isset($this->committed_st_version)) { 1272 | return "0"; 1273 | } 1274 | return $this->committed_st_version; 1275 | } 1276 | public function setCommittedStVersion($val) { 1277 | if (is_double($val)) { 1278 | $this->committed_st_version = sprintf('%0.0F', $val); 1279 | } else { 1280 | $this->committed_st_version = $val; 1281 | } 1282 | return $this; 1283 | } 1284 | public function clearCommittedStVersion() { 1285 | unset($this->committed_st_version); 1286 | return $this; 1287 | } 1288 | public function hasCommittedStVersion() { 1289 | return isset($this->committed_st_version); 1290 | } 1291 | public function clear() { 1292 | $this->clearVersion(); 1293 | $this->clearCommittedStVersion(); 1294 | } 1295 | public function byteSizePartial() { 1296 | $res = 0; 1297 | if (isset($this->version)) { 1298 | $res += 1; 1299 | $res += $this->lengthVarInt64($this->version); 1300 | } 1301 | if (isset($this->committed_st_version)) { 1302 | $res += 1; 1303 | $res += $this->lengthVarInt64($this->committed_st_version); 1304 | } 1305 | return $res; 1306 | } 1307 | public function outputPartial($out) { 1308 | if (isset($this->version)) { 1309 | $out->putVarInt32(8); 1310 | $out->putVarInt64($this->version); 1311 | } 1312 | if (isset($this->committed_st_version)) { 1313 | $out->putVarInt32(16); 1314 | $out->putVarInt64($this->committed_st_version); 1315 | } 1316 | } 1317 | public function tryMerge($d) { 1318 | while($d->avail() > 0) { 1319 | $tt = $d->getVarInt32(); 1320 | switch ($tt) { 1321 | case 8: 1322 | $this->setVersion($d->getVarInt64()); 1323 | break; 1324 | case 16: 1325 | $this->setCommittedStVersion($d->getVarInt64()); 1326 | break; 1327 | case 0: 1328 | throw new \google\net\ProtocolBufferDecodeError(); 1329 | break; 1330 | default: 1331 | $d->skipData($tt); 1332 | } 1333 | }; 1334 | } 1335 | public function checkInitialized() { 1336 | return null; 1337 | } 1338 | public function mergeFrom($x) { 1339 | if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); } 1340 | if ($x->hasVersion()) { 1341 | $this->setVersion($x->getVersion()); 1342 | } 1343 | if ($x->hasCommittedStVersion()) { 1344 | $this->setCommittedStVersion($x->getCommittedStVersion()); 1345 | } 1346 | } 1347 | public function equals($x) { 1348 | if ($x === $this) { return true; } 1349 | if (isset($this->version) !== isset($x->version)) return false; 1350 | if (isset($this->version) && !$this->integerEquals($this->version, $x->version)) return false; 1351 | if (isset($this->committed_st_version) !== isset($x->committed_st_version)) return false; 1352 | if (isset($this->committed_st_version) && !$this->integerEquals($this->committed_st_version, $x->committed_st_version)) return false; 1353 | return true; 1354 | } 1355 | public function shortDebugString($prefix = "") { 1356 | $res = ''; 1357 | if (isset($this->version)) { 1358 | $res .= $prefix . "version: " . $this->debugFormatInt64($this->version) . "\n"; 1359 | } 1360 | if (isset($this->committed_st_version)) { 1361 | $res .= $prefix . "committed_st_version: " . $this->debugFormatInt64($this->committed_st_version) . "\n"; 1362 | } 1363 | return $res; 1364 | } 1365 | } 1366 | } 1367 | namespace storage_onestore_v3\Document { 1368 | class Storage { 1369 | const DISK = 0; 1370 | } 1371 | } 1372 | namespace storage_onestore_v3 { 1373 | class Document extends \google\net\ProtocolMessage { 1374 | private $field = array(); 1375 | private $facet = array(); 1376 | public function getId() { 1377 | if (!isset($this->id)) { 1378 | return ''; 1379 | } 1380 | return $this->id; 1381 | } 1382 | public function setId($val) { 1383 | $this->id = $val; 1384 | return $this; 1385 | } 1386 | public function clearId() { 1387 | unset($this->id); 1388 | return $this; 1389 | } 1390 | public function hasId() { 1391 | return isset($this->id); 1392 | } 1393 | public function getLanguage() { 1394 | if (!isset($this->language)) { 1395 | return "en"; 1396 | } 1397 | return $this->language; 1398 | } 1399 | public function setLanguage($val) { 1400 | $this->language = $val; 1401 | return $this; 1402 | } 1403 | public function clearLanguage() { 1404 | unset($this->language); 1405 | return $this; 1406 | } 1407 | public function hasLanguage() { 1408 | return isset($this->language); 1409 | } 1410 | public function getFieldSize() { 1411 | return sizeof($this->field); 1412 | } 1413 | public function getFieldList() { 1414 | return $this->field; 1415 | } 1416 | public function mutableField($idx) { 1417 | if (!isset($this->field[$idx])) { 1418 | $val = new \storage_onestore_v3\Field(); 1419 | $this->field[$idx] = $val; 1420 | return $val; 1421 | } 1422 | return $this->field[$idx]; 1423 | } 1424 | public function getField($idx) { 1425 | if (isset($this->field[$idx])) { 1426 | return $this->field[$idx]; 1427 | } 1428 | if ($idx >= end(array_keys($this->field))) { 1429 | throw new \OutOfRangeException('index out of range: ' + $idx); 1430 | } 1431 | return new \storage_onestore_v3\Field(); 1432 | } 1433 | public function addField() { 1434 | $val = new \storage_onestore_v3\Field(); 1435 | $this->field[] = $val; 1436 | return $val; 1437 | } 1438 | public function clearField() { 1439 | $this->field = array(); 1440 | } 1441 | public function getOrderId() { 1442 | if (!isset($this->order_id)) { 1443 | return 0; 1444 | } 1445 | return $this->order_id; 1446 | } 1447 | public function setOrderId($val) { 1448 | $this->order_id = $val; 1449 | return $this; 1450 | } 1451 | public function clearOrderId() { 1452 | unset($this->order_id); 1453 | return $this; 1454 | } 1455 | public function hasOrderId() { 1456 | return isset($this->order_id); 1457 | } 1458 | public function getStorage() { 1459 | if (!isset($this->storage)) { 1460 | return 0; 1461 | } 1462 | return $this->storage; 1463 | } 1464 | public function setStorage($val) { 1465 | $this->storage = $val; 1466 | return $this; 1467 | } 1468 | public function clearStorage() { 1469 | unset($this->storage); 1470 | return $this; 1471 | } 1472 | public function hasStorage() { 1473 | return isset($this->storage); 1474 | } 1475 | public function getFacetSize() { 1476 | return sizeof($this->facet); 1477 | } 1478 | public function getFacetList() { 1479 | return $this->facet; 1480 | } 1481 | public function mutableFacet($idx) { 1482 | if (!isset($this->facet[$idx])) { 1483 | $val = new \storage_onestore_v3\Facet(); 1484 | $this->facet[$idx] = $val; 1485 | return $val; 1486 | } 1487 | return $this->facet[$idx]; 1488 | } 1489 | public function getFacet($idx) { 1490 | if (isset($this->facet[$idx])) { 1491 | return $this->facet[$idx]; 1492 | } 1493 | if ($idx >= end(array_keys($this->facet))) { 1494 | throw new \OutOfRangeException('index out of range: ' + $idx); 1495 | } 1496 | return new \storage_onestore_v3\Facet(); 1497 | } 1498 | public function addFacet() { 1499 | $val = new \storage_onestore_v3\Facet(); 1500 | $this->facet[] = $val; 1501 | return $val; 1502 | } 1503 | public function clearFacet() { 1504 | $this->facet = array(); 1505 | } 1506 | public function clear() { 1507 | $this->clearId(); 1508 | $this->clearLanguage(); 1509 | $this->clearField(); 1510 | $this->clearOrderId(); 1511 | $this->clearStorage(); 1512 | $this->clearFacet(); 1513 | } 1514 | public function byteSizePartial() { 1515 | $res = 0; 1516 | if (isset($this->id)) { 1517 | $res += 1; 1518 | $res += $this->lengthString(strlen($this->id)); 1519 | } 1520 | if (isset($this->language)) { 1521 | $res += 1; 1522 | $res += $this->lengthString(strlen($this->language)); 1523 | } 1524 | $this->checkProtoArray($this->field); 1525 | $res += 1 * sizeof($this->field); 1526 | foreach ($this->field as $value) { 1527 | $res += $this->lengthString($value->byteSizePartial()); 1528 | } 1529 | if (isset($this->order_id)) { 1530 | $res += 1; 1531 | $res += $this->lengthVarInt64($this->order_id); 1532 | } 1533 | if (isset($this->storage)) { 1534 | $res += 1; 1535 | $res += $this->lengthVarInt64($this->storage); 1536 | } 1537 | $this->checkProtoArray($this->facet); 1538 | $res += 1 * sizeof($this->facet); 1539 | foreach ($this->facet as $value) { 1540 | $res += $this->lengthString($value->byteSizePartial()); 1541 | } 1542 | return $res; 1543 | } 1544 | public function outputPartial($out) { 1545 | if (isset($this->id)) { 1546 | $out->putVarInt32(10); 1547 | $out->putPrefixedString($this->id); 1548 | } 1549 | if (isset($this->language)) { 1550 | $out->putVarInt32(18); 1551 | $out->putPrefixedString($this->language); 1552 | } 1553 | $this->checkProtoArray($this->field); 1554 | foreach ($this->field as $value) { 1555 | $out->putVarInt32(26); 1556 | $out->putVarInt32($value->byteSizePartial()); 1557 | $value->outputPartial($out); 1558 | } 1559 | if (isset($this->order_id)) { 1560 | $out->putVarInt32(32); 1561 | $out->putVarInt32($this->order_id); 1562 | } 1563 | if (isset($this->storage)) { 1564 | $out->putVarInt32(40); 1565 | $out->putVarInt32($this->storage); 1566 | } 1567 | $this->checkProtoArray($this->facet); 1568 | foreach ($this->facet as $value) { 1569 | $out->putVarInt32(66); 1570 | $out->putVarInt32($value->byteSizePartial()); 1571 | $value->outputPartial($out); 1572 | } 1573 | } 1574 | public function tryMerge($d) { 1575 | while($d->avail() > 0) { 1576 | $tt = $d->getVarInt32(); 1577 | switch ($tt) { 1578 | case 10: 1579 | $length = $d->getVarInt32(); 1580 | $this->setId(substr($d->buffer(), $d->pos(), $length)); 1581 | $d->skip($length); 1582 | break; 1583 | case 18: 1584 | $length = $d->getVarInt32(); 1585 | $this->setLanguage(substr($d->buffer(), $d->pos(), $length)); 1586 | $d->skip($length); 1587 | break; 1588 | case 26: 1589 | $length = $d->getVarInt32(); 1590 | $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length); 1591 | $d->skip($length); 1592 | $this->addField()->tryMerge($tmp); 1593 | break; 1594 | case 32: 1595 | $this->setOrderId($d->getVarInt32()); 1596 | break; 1597 | case 40: 1598 | $this->setStorage($d->getVarInt32()); 1599 | break; 1600 | case 66: 1601 | $length = $d->getVarInt32(); 1602 | $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length); 1603 | $d->skip($length); 1604 | $this->addFacet()->tryMerge($tmp); 1605 | break; 1606 | case 0: 1607 | throw new \google\net\ProtocolBufferDecodeError(); 1608 | break; 1609 | default: 1610 | $d->skipData($tt); 1611 | } 1612 | }; 1613 | } 1614 | public function checkInitialized() { 1615 | foreach ($this->field as $value) { 1616 | if (!$value->isInitialized()) return 'field'; 1617 | } 1618 | foreach ($this->facet as $value) { 1619 | if (!$value->isInitialized()) return 'facet'; 1620 | } 1621 | return null; 1622 | } 1623 | public function mergeFrom($x) { 1624 | if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); } 1625 | if ($x->hasId()) { 1626 | $this->setId($x->getId()); 1627 | } 1628 | if ($x->hasLanguage()) { 1629 | $this->setLanguage($x->getLanguage()); 1630 | } 1631 | foreach ($x->getFieldList() as $v) { 1632 | $this->addField()->copyFrom($v); 1633 | } 1634 | if ($x->hasOrderId()) { 1635 | $this->setOrderId($x->getOrderId()); 1636 | } 1637 | if ($x->hasStorage()) { 1638 | $this->setStorage($x->getStorage()); 1639 | } 1640 | foreach ($x->getFacetList() as $v) { 1641 | $this->addFacet()->copyFrom($v); 1642 | } 1643 | } 1644 | public function equals($x) { 1645 | if ($x === $this) { return true; } 1646 | if (isset($this->id) !== isset($x->id)) return false; 1647 | if (isset($this->id) && $this->id !== $x->id) return false; 1648 | if (isset($this->language) !== isset($x->language)) return false; 1649 | if (isset($this->language) && $this->language !== $x->language) return false; 1650 | if (sizeof($this->field) !== sizeof($x->field)) return false; 1651 | foreach (array_map(null, $this->field, $x->field) as $v) { 1652 | if (!$v[0]->equals($v[1])) return false; 1653 | } 1654 | if (isset($this->order_id) !== isset($x->order_id)) return false; 1655 | if (isset($this->order_id) && !$this->integerEquals($this->order_id, $x->order_id)) return false; 1656 | if (isset($this->storage) !== isset($x->storage)) return false; 1657 | if (isset($this->storage) && $this->storage !== $x->storage) return false; 1658 | if (sizeof($this->facet) !== sizeof($x->facet)) return false; 1659 | foreach (array_map(null, $this->facet, $x->facet) as $v) { 1660 | if (!$v[0]->equals($v[1])) return false; 1661 | } 1662 | return true; 1663 | } 1664 | public function shortDebugString($prefix = "") { 1665 | $res = ''; 1666 | if (isset($this->id)) { 1667 | $res .= $prefix . "id: " . $this->debugFormatString($this->id) . "\n"; 1668 | } 1669 | if (isset($this->language)) { 1670 | $res .= $prefix . "language: " . $this->debugFormatString($this->language) . "\n"; 1671 | } 1672 | foreach ($this->field as $value) { 1673 | $res .= $prefix . "field <\n" . $value->shortDebugString($prefix . " ") . $prefix . ">\n"; 1674 | } 1675 | if (isset($this->order_id)) { 1676 | $res .= $prefix . "order_id: " . $this->debugFormatInt32($this->order_id) . "\n"; 1677 | } 1678 | if (isset($this->storage)) { 1679 | $res .= $prefix . "storage: " . ($this->storage) . "\n"; 1680 | } 1681 | foreach ($this->facet as $value) { 1682 | $res .= $prefix . "facet <\n" . $value->shortDebugString($prefix . " ") . $prefix . ">\n"; 1683 | } 1684 | return $res; 1685 | } 1686 | } 1687 | } 1688 | --------------------------------------------------------------------------------