├── .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 | [](https://travis-ci.org/tomwalder/php-appengine-search)
2 | [](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 |
--------------------------------------------------------------------------------