From 5b18af78ebc8856481ea05a426015e617d609a88 Mon Sep 17 00:00:00 2001 From: Martynas Sudintas Date: Fri, 23 Jan 2015 14:49:25 +0200 Subject: [PATCH] Annotations are now allowed to put camel or underscore case properties --- Annotation/AbstractProperty.php | 42 +++++++ Annotation/Inherit.php | 2 +- Annotation/MultiField.php | 19 +--- Annotation/Property.php | 19 +--- Annotation/Skip.php | 2 +- .../Suggester/AbstractSuggesterProperty.php | 28 ++--- .../Suggester/Context/AbstractContext.php | 9 +- .../Suggester/ContextSuggesterProperty.php | 6 +- DataCollector/ElasticsearchDataCollector.php | 5 +- Mapping/AbstractAnnotationCamelizer.php | 47 ++++++++ Mapping/DocumentParser.php | 4 +- Mapping/DumperInterface.php | 27 +++++ Service/JsonFormatter.php | 76 ------------- .../ElasticsearchDataCollectorTest.php | 54 +++++++++ Tests/Unit/Annotation/PropertyTest.php | 13 ++- Tests/Unit/Service/JsonFormatterTest.php | 106 ------------------ Tests/app/fixture/Json/collector_body_0.json | 20 ++++ .../fixture/JsonFormatter/formatted_0.json | 4 - .../fixture/JsonFormatter/formatted_1.json | 23 ---- .../fixture/JsonFormatter/formatted_2.json | 4 - 20 files changed, 227 insertions(+), 283 deletions(-) create mode 100644 Annotation/AbstractProperty.php create mode 100644 Mapping/AbstractAnnotationCamelizer.php create mode 100644 Mapping/DumperInterface.php delete mode 100644 Service/JsonFormatter.php delete mode 100644 Tests/Unit/Service/JsonFormatterTest.php create mode 100644 Tests/app/fixture/Json/collector_body_0.json delete mode 100644 Tests/app/fixture/JsonFormatter/formatted_0.json delete mode 100644 Tests/app/fixture/JsonFormatter/formatted_1.json delete mode 100644 Tests/app/fixture/JsonFormatter/formatted_2.json diff --git a/Annotation/AbstractProperty.php b/Annotation/AbstractProperty.php new file mode 100644 index 00000000..1916951f --- /dev/null +++ b/Annotation/AbstractProperty.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ONGR\ElasticsearchBundle\Annotation; + +use ONGR\ElasticsearchBundle\Mapping\AbstractAnnotationCamelizer; +use ONGR\ElasticsearchBundle\Mapping\DumperInterface; + +/** + * Makes sure thats annotations are well formated. + */ +abstract class AbstractProperty extends AbstractAnnotationCamelizer implements DumperInterface +{ + /** + * {@inheritdoc} + */ + public function dump(array $exclude = []) + { + $array = array_diff_key( + array_filter(get_object_vars($this)), + array_flip(array_merge(['name', 'objectName', 'multiple'], $exclude)) + ); + + return array_combine( + array_map( + function ($key) { + return $this->underscore($key); + }, + array_keys($array) + ), + array_values($array) + ); + } +} diff --git a/Annotation/Inherit.php b/Annotation/Inherit.php index 6ba8ae3c..dcdb4548 100644 --- a/Annotation/Inherit.php +++ b/Annotation/Inherit.php @@ -31,7 +31,7 @@ final class Inherit * * @throws \InvalidArgumentException */ - public function __constructor(array $values) + public function __construct(array $values) { if (is_string($values['value'])) { $this->value = [$values['value']]; diff --git a/Annotation/MultiField.php b/Annotation/MultiField.php index 1fe65a23..a6894df3 100644 --- a/Annotation/MultiField.php +++ b/Annotation/MultiField.php @@ -19,7 +19,7 @@ * @Annotation * @Target("ANNOTATION") */ -final class MultiField +final class MultiField extends AbstractProperty { /** * @var string @@ -48,23 +48,10 @@ final class MultiField /** * @var string */ - public $index_analyzer; + public $indexAnalyzer; /** * @var string */ - public $search_analyzer; - - /** - * Filters object values. - * - * @return array - */ - public function filter() - { - return array_diff_key( - array_filter(get_object_vars($this)), - array_flip(['name']) - ); - } + public $searchAnalyzer; } diff --git a/Annotation/Property.php b/Annotation/Property.php index db045758..83c17346 100644 --- a/Annotation/Property.php +++ b/Annotation/Property.php @@ -19,7 +19,7 @@ * @Annotation * @Target("PROPERTY") */ -final class Property +final class Property extends AbstractProperty { /** * @var string @@ -48,12 +48,12 @@ final class Property /** * @var string */ - public $index_analyzer; + public $indexAnalyzer; /** * @var string */ - public $search_analyzer; + public $searchAnalyzer; /** * @var float @@ -84,17 +84,4 @@ final class Property * @var bool OneToOne or OneToMany. */ public $multiple; - - /** - * Filters object null values and name. - * - * @return array - */ - public function filter() - { - return array_diff_key( - array_filter(get_object_vars($this)), - array_flip(['name', 'objectName', 'multiple']) - ); - } } diff --git a/Annotation/Skip.php b/Annotation/Skip.php index affb718d..7609ebe2 100644 --- a/Annotation/Skip.php +++ b/Annotation/Skip.php @@ -31,7 +31,7 @@ final class Skip * * @throws \InvalidArgumentException */ - public function __constructor(array $values) + public function __construct(array $values) { if (is_string($values['value'])) { $this->value = [$values['value']]; diff --git a/Annotation/Suggester/AbstractSuggesterProperty.php b/Annotation/Suggester/AbstractSuggesterProperty.php index adb64e51..04e370bb 100644 --- a/Annotation/Suggester/AbstractSuggesterProperty.php +++ b/Annotation/Suggester/AbstractSuggesterProperty.php @@ -12,11 +12,12 @@ namespace ONGR\ElasticsearchBundle\Annotation\Suggester; use Doctrine\Common\Annotations\Annotation\Required; +use ONGR\ElasticsearchBundle\Annotation\AbstractProperty; /** * Abstract class for various suggester annotations. */ -abstract class AbstractSuggesterProperty +abstract class AbstractSuggesterProperty extends AbstractProperty { /** * @var string @@ -40,45 +41,30 @@ abstract class AbstractSuggesterProperty /** * @var string */ - public $index_analyzer; + public $indexAnalyzer; /** * @var string */ - public $search_analyzer; + public $searchAnalyzer; /** * @var int */ - public $preserve_separators; + public $preserveSeparators; /** * @var bool */ - public $preserve_position_increments; + public $preservePositionIncrements; /** * @var int */ - public $max_input_length; + public $maxInputLength; /** * @var bool */ public $payloads; - - /** - * Returns required properties. - * - * @param array $extraExclude Extra object variables to exclude. - * - * @return array - */ - public function filter($extraExclude = []) - { - return array_diff_key( - array_filter(get_object_vars($this)), - array_flip(array_merge(['name', 'objectName', 'classObjectName'], $extraExclude)) - ); - } } diff --git a/Annotation/Suggester/Context/AbstractContext.php b/Annotation/Suggester/Context/AbstractContext.php index 1b2a6bee..c5caab82 100644 --- a/Annotation/Suggester/Context/AbstractContext.php +++ b/Annotation/Suggester/Context/AbstractContext.php @@ -12,11 +12,12 @@ namespace ONGR\ElasticsearchBundle\Annotation\Suggester\Context; use Doctrine\Common\Annotations\Annotation\Required; +use ONGR\ElasticsearchBundle\Mapping\DumperInterface; /** * Abstract class for various context annotations. */ -abstract class AbstractContext +abstract class AbstractContext implements DumperInterface { /** * @var array @@ -43,11 +44,9 @@ abstract class AbstractContext abstract public function getType(); /** - * Returns filtered object data. - * - * @return array + * {@inheritdoc} */ - public function filter() + public function dump(array $exclude = []) { $vars = array_diff_key( array_filter(get_object_vars($this)), diff --git a/Annotation/Suggester/ContextSuggesterProperty.php b/Annotation/Suggester/ContextSuggesterProperty.php index 95d5f2ed..e369d4e4 100644 --- a/Annotation/Suggester/ContextSuggesterProperty.php +++ b/Annotation/Suggester/ContextSuggesterProperty.php @@ -20,13 +20,13 @@ class ContextSuggesterProperty extends AbstractSuggesterProperty /** * {@inheritdoc} */ - public function filter($extraExclude = []) + public function dump(array $exclude = []) { - $data = parent::filter(['context']); + $data = parent::dump(['context']); /** @var AbstractContext $singleContext */ foreach ($this->context as $singleContext) { - $data['context'][$singleContext->name] = $singleContext->filter(); + $data['context'][$singleContext->name] = $singleContext->dump(); } return $data; diff --git a/DataCollector/ElasticsearchDataCollector.php b/DataCollector/ElasticsearchDataCollector.php index f6869ae4..46eeb62a 100644 --- a/DataCollector/ElasticsearchDataCollector.php +++ b/DataCollector/ElasticsearchDataCollector.php @@ -12,8 +12,8 @@ namespace ONGR\ElasticsearchBundle\DataCollector; use Monolog\Logger; +use ONGR\ElasticsearchBundle\Common\JsonFormatter; use ONGR\ElasticsearchBundle\Logger\Handler\CollectionHandler; -use ONGR\ElasticsearchBundle\Service\JsonFormatter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; @@ -174,9 +174,10 @@ private function handleRecords($route, $records) private function addQuery($route, $record, $queryBody) { parse_str(parse_url($record['context']['uri'], PHP_URL_QUERY), $httpParameters); + $body = json_decode(trim($queryBody, "'"), true); $this->queries[$route][] = array_merge( [ - 'body' => JsonFormatter::prettify(trim($queryBody, "'")), + 'body' => $body !== null ? json_encode($body, JSON_PRETTY_PRINT) : '', 'method' => $record['context']['method'], 'httpParameters' => $httpParameters, 'time' => $record['context']['duration'] * 100, diff --git a/Mapping/AbstractAnnotationCamelizer.php b/Mapping/AbstractAnnotationCamelizer.php new file mode 100644 index 00000000..0e9ebf2c --- /dev/null +++ b/Mapping/AbstractAnnotationCamelizer.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ONGR\ElasticsearchBundle\Mapping; + +use Doctrine\Common\Inflector\Inflector; + +/** + * Transforms document properties from underscore case to camel case. + */ +abstract class AbstractAnnotationCamelizer +{ + /** + * Camelizes properties when reading from document. + * + * @param array $values Key-value for properties to be defined in this class. + */ + public function __construct(array $values) + { + foreach ($values as $key => $value) { + $this->{Inflector::camelize($key)} = $value; + } + } + + /** + * Converts string from camel into underscore case (port from rails). + * + * @param string $word + * + * @return string + */ + protected function underscore($word) + { + $word = preg_replace('#([A-Z\d]+)([A-Z][a-z])#', '\1_\2', $word); + $word = preg_replace('#([a-z\d])([A-Z])#', '\1_\2', $word); + + return strtolower(strtr($word, '-', '_')); + } +} diff --git a/Mapping/DocumentParser.php b/Mapping/DocumentParser.php index 7a48fef4..8ec76455 100644 --- a/Mapping/DocumentParser.php +++ b/Mapping/DocumentParser.php @@ -293,7 +293,7 @@ private function getProperties(\ReflectionClass $reflectionClass, $properties = continue; } - $maps = $type->filter(); + $maps = $type->dump(); // Object. if (in_array($type->type, ['object', 'nested']) && !empty($type->objectName)) { @@ -305,7 +305,7 @@ private function getProperties(\ReflectionClass $reflectionClass, $properties = $fieldsMap = []; /** @var MultiField $field */ foreach ($maps['fields'] as $field) { - $fieldsMap[$field->name] = $field->filter(); + $fieldsMap[$field->name] = $field->dump(); } $maps['fields'] = $fieldsMap; } diff --git a/Mapping/DumperInterface.php b/Mapping/DumperInterface.php new file mode 100644 index 00000000..e23e9d68 --- /dev/null +++ b/Mapping/DumperInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ONGR\ElasticsearchBundle\Mapping; + +/** + * DumperInterface is the interface implemented by elasticsearch document annotations. + */ +interface DumperInterface +{ + /** + * Dumps properties into array. + * + * @param array $exclude Properties array to exclude from dump. + * + * @return array + */ + public function dump(array $exclude = []); +} diff --git a/Service/JsonFormatter.php b/Service/JsonFormatter.php deleted file mode 100644 index 17bb0ab1..00000000 --- a/Service/JsonFormatter.php +++ /dev/null @@ -1,76 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace ONGR\ElasticsearchBundle\Service; - -/** - * Class JsonFormatter. - */ -class JsonFormatter -{ - /** - * Pretty prints json. - * - * @param string $json - * - * @return string - */ - public static function prettify($json) - { - $result = ''; - $pos = 0; - $strLen = strlen($json); - $indentStr = ' '; - $newLine = "\n"; - $prevChar = ''; - $outOfQuotes = true; - - for ($i = 0; $i <= $strLen; $i++) { - $char = substr($json, $i, 1); - - if ($char == '"' && $prevChar != '\\') { - $outOfQuotes = !$outOfQuotes; - } elseif (($char == '}' || $char == ']') && $outOfQuotes) { - $result .= $newLine; - $pos --; - for ($j = 0; $j < $pos; $j++) { - $result .= $indentStr; - } - } - $result .= $char; - - if ($prevChar == '"' && $char == ':') { - $result .= ' '; - } - - if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) { - $nextChar = (isset($json[$i + 1]) ? $json[$i + 1] : ''); - - if ($nextChar != ']' && $nextChar != '}') { - $result .= $newLine; - } else { - $result .= $nextChar; - $i++; - continue; - } - if ($char == '{' || $char == '[') { - $pos++; - } - for ($j = 0; $j < $pos; $j++) { - $result .= $indentStr; - } - } - $prevChar = $char; - } - - return $result; - } -} diff --git a/Tests/Functional/DataCollector/ElasticsearchDataCollectorTest.php b/Tests/Functional/DataCollector/ElasticsearchDataCollectorTest.php index d9b727f4..2f1c213a 100644 --- a/Tests/Functional/DataCollector/ElasticsearchDataCollectorTest.php +++ b/Tests/Functional/DataCollector/ElasticsearchDataCollectorTest.php @@ -12,6 +12,8 @@ namespace ONGR\ElasticsearchBundle\Tests\Functional\DataCollector; use ONGR\ElasticsearchBundle\DataCollector\ElasticsearchDataCollector; +use ONGR\ElasticsearchBundle\DSL\Query\TermQuery; +use ONGR\ElasticsearchBundle\ORM\Repository; use ONGR\ElasticsearchBundle\Test\ElasticsearchTestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -105,6 +107,40 @@ public function testGetQueries() ); } + /** + * Tests if term query is correct. + */ + public function testGetTermQuery() + { + $manager = $this->getManager(); + + $repository = $manager->getRepository('AcmeTestBundle:Product'); + $search = $repository + ->createSearch() + ->addQuery(new TermQuery('title', 'pizza')); + $result = $repository->execute($search, Repository::RESULTS_OBJECT); + + $queries = $this->getCollector()->getQueries(); + $lastQuery = end($queries[ElasticsearchDataCollector::UNDEFINED_ROUTE]); + $time = $lastQuery['time']; + unset($lastQuery['time']); + + $this->assertGreaterThan(0.0, $time, 'Time should be greater than 0'); + $this->assertEquals( + [ + 'body' => $this->getFileContents('collector_body_0.json'), + 'method' => 'POST', + 'path' => '/ongr-elasticsearch-bundle-test/product/_search', + 'host' => '127.0.0.1', + 'httpParameters' => [], + 'scheme' => 'http', + 'port' => 9200, + ], + $lastQuery, + 'Logged data did not match expected data.' + ); + } + /** * @return ElasticsearchDataCollector */ @@ -115,4 +151,22 @@ private function getCollector() return $collector; } + + /** + * Returns file contents from fixture. + * + * @param string $filename + * + * @return string + */ + private function getFileContents($filename) + { + $contents = file_get_contents(__DIR__ . '/../../app/fixture/Json/' . $filename); + // Checks for new line at the end of file. + if (substr($contents, -1) == "\n") { + $contents = substr($contents, 0, -1); + } + + return $contents; + } } diff --git a/Tests/Unit/Annotation/PropertyTest.php b/Tests/Unit/Annotation/PropertyTest.php index bbaf02f1..31d42f86 100644 --- a/Tests/Unit/Annotation/PropertyTest.php +++ b/Tests/Unit/Annotation/PropertyTest.php @@ -20,11 +20,17 @@ class PropertyTest extends \PHPUnit_Framework_TestCase */ public function testFilter() { - $type = new Property(); + $type = new Property( + [ + 'object_name' => 'foo/bar', + 'type' => 'string', + ] + ); + + $this->assertEquals('foo/bar', $type->objectName, 'Properties should be camelized'); $type->name = 'id'; $type->index = 'no_index'; - $type->type = 'string'; $type->analyzer = null; $this->assertEquals( @@ -32,7 +38,8 @@ public function testFilter() 'index' => 'no_index', 'type' => 'string', ], - $type->filter() + $type->dump(), + 'Properties should be filtered' ); } } diff --git a/Tests/Unit/Service/JsonFormatterTest.php b/Tests/Unit/Service/JsonFormatterTest.php deleted file mode 100644 index ffeb706b..00000000 --- a/Tests/Unit/Service/JsonFormatterTest.php +++ /dev/null @@ -1,106 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace ONGR\ElasticsearchBundle\Tests\Unit\Service; - -use ONGR\ElasticsearchBundle\Service\JsonFormatter; - -class JsonFormatterTest extends \PHPUnit_Framework_TestCase -{ - /** - * Data provider for testPrettify. - * - * @return array - */ - public function getTestPrettifyData() - { - $out = []; - - // Case #0: simple. - $data0 = [ - 'total' => 0, - 'raw' => [], - ]; - $pretty0 = $this->getFileContents('formatted_0.json'); - $out[] = [ - json_encode($data0), - $pretty0, - ]; - - // Case #1: more data. - $data1 = [ - 'total' => [ - 'hit' => 1, - 'name' => 'bunch', - ], - 'hits' => [ - ['_id' => 1], - ['_id' => 2], - ], - 0 => [ - 'level1' => [ - 'level2' => [ - 'level3' => [ - 'level4' => [], - ], - ], - ], - ], - ]; - $pretty1 = $this->getFileContents('formatted_1.json'); - $out[] = [ - json_encode($data1), - $pretty1, - ]; - - // Case #2: no data. - $data2 = [new \stdClass(), [] ]; - $pretty2 = $this->getFileContents('formatted_2.json'); - $out[] = [ - json_encode($data2), - $pretty2, - ]; - - return $out; - } - - /** - * Tests prettify method. - * - * @param string $inlineJson - * @param string $prettyJson - * - * @dataProvider getTestPrettifyData - */ - public function testPrettify($inlineJson, $prettyJson) - { - $this->assertEquals($prettyJson, JsonFormatter::prettify($inlineJson)); - } - - /** - * Returns file contents from fixture. - * - * @param string $filename - * - * @return string - */ - private function getFileContents($filename) - { - $contents = file_get_contents(__DIR__ . '/../../app/fixture/JsonFormatter/' . $filename); - - // Checks for new line at the end of file. - if (substr($contents, -1) == "\n") { - $contents = substr($contents, 0, -1); - } - - return $contents; - } -} diff --git a/Tests/app/fixture/Json/collector_body_0.json b/Tests/app/fixture/Json/collector_body_0.json new file mode 100644 index 00000000..3f14a912 --- /dev/null +++ b/Tests/app/fixture/Json/collector_body_0.json @@ -0,0 +1,20 @@ +{ + "query": { + "bool": { + "must": [ + { + "term": { + "title": { + "value": "pizza" + } + } + } + ] + } + }, + "fields": [ + "_parent", + "_ttl", + "_source" + ] +} diff --git a/Tests/app/fixture/JsonFormatter/formatted_0.json b/Tests/app/fixture/JsonFormatter/formatted_0.json deleted file mode 100644 index a1518452..00000000 --- a/Tests/app/fixture/JsonFormatter/formatted_0.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "total": 0, - "raw": [] -} diff --git a/Tests/app/fixture/JsonFormatter/formatted_1.json b/Tests/app/fixture/JsonFormatter/formatted_1.json deleted file mode 100644 index 916382bd..00000000 --- a/Tests/app/fixture/JsonFormatter/formatted_1.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "total": { - "hit": 1, - "name": "bunch" - }, - "hits": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ], - "0": { - "level1": { - "level2": { - "level3": { - "level4": [] - } - } - } - } -} diff --git a/Tests/app/fixture/JsonFormatter/formatted_2.json b/Tests/app/fixture/JsonFormatter/formatted_2.json deleted file mode 100644 index 0f2389f0..00000000 --- a/Tests/app/fixture/JsonFormatter/formatted_2.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - {}, - [] -]