diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml
index d51fcb5..c683cad 100644
--- a/.github/workflows/phpunit-tests.yml
+++ b/.github/workflows/phpunit-tests.yml
@@ -111,7 +111,7 @@ jobs:
- name: 'Run phpunit tests'
run: |
- vendor/bin/simple-phpunit --coverage-clover=tests/App/build/clover.xml
+ vendor/bin/phpunit --coverage-clover=tests/App/build/clover.xml
- name: Upload coverage results to Coveralls
uses: coverallsapp/github-action@v2
diff --git a/.gitignore b/.gitignore
index 35c81fc..6491e08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,5 +8,6 @@
.phpunit.result.cache
.idea
/.php-cs-fixer.cache
-/.phpstan-cache
+/.phpstan-cache/
/.rector-cache/
+/.phpunit.cache/
diff --git a/README.md b/README.md
index 938dc6e..3c77273 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ This bundle is licensed under the MIT license. Please, see the complete license
```
composer install
docker compose up --detach --wait
-vendor/bin/simple-phpunit
+vendor/bin/phpunit
docker compose down --remove-orphans
```
diff --git a/composer.json b/composer.json
index eb48c30..9b0232e 100644
--- a/composer.json
+++ b/composer.json
@@ -23,23 +23,22 @@
"symfony/http-kernel": "^5.4 || ^6.4",
"symfony/event-dispatcher-contracts": "^3.5",
- "doctrine/annotations": "^1.2",
- "doctrine/cache": "^1.4",
"elasticsearch/elasticsearch": "^8.0"
},
"require-dev": {
- "symfony/stopwatch": "^5.4 || ^6.4",
- "symfony/phpunit-bridge": "^5.4 || ^6.4",
- "symfony/browser-kit": "^5.4 || ^6.4",
"symfony/dotenv": "^5.4 || ^6.4",
- "doctrine/orm": "^2.6.3",
- "monolog/monolog": "^2.0|^3.0",
+ "doctrine/orm": "^2.6.3",
+ "doctrine/annotations": "^1.2",
"knplabs/knp-paginator-bundle": "^4.0 || ^5.0",
- "friendsofphp/php-cs-fixer": "^3.34",
+ "monolog/monolog": "^2.0|^3.0",
+
+ "phpunit/phpunit": "^10.5",
"php-coveralls/php-coveralls": "^2.1",
"jchook/phpunit-assert-throws": "^1.0",
- "dms/phpunit-arraysubset-asserts": "^0.2.1",
+ "dms/phpunit-arraysubset-asserts": "^0.5.0",
+
+ "friendsofphp/php-cs-fixer": "^3.34",
"phpstan/phpstan": "^1.12",
"phpstan/phpstan-symfony": "^1.4",
"phpstan/phpstan-phpunit": "^1.4",
@@ -48,7 +47,8 @@
"suggest": {
"monolog/monolog": "Allows for client-level logging and tracing",
"knplabs/knp-paginator-bundle": "Allows for search results to be paginated",
- "doctrine/orm": "Allows for using Doctrine as source for rebuilding indices"
+ "doctrine/orm": "Allows for using Doctrine as source for rebuilding indices",
+ "doctrine/annotations": "Allows for using annotations to configure the bundle, which is now deprecated"
},
"autoload": {
"psr-4": {
@@ -69,7 +69,7 @@
},
"scripts": {
"run-tests": [
- "XDEBUG_MODE=coverage vendor/bin/simple-phpunit --coverage-text"
+ "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text"
],
"check-code": [
"vendor/bin/phpstan",
diff --git a/config/services.yml b/config/services.yml
index 1e3a13d..19c0ee3 100644
--- a/config/services.yml
+++ b/config/services.yml
@@ -73,18 +73,19 @@ services:
arguments:
- '%sfes.entity_locations%'
- Sineflow\ElasticsearchBundle\Mapping\DocumentParser:
+ Sineflow\ElasticsearchBundle\Mapping\DocumentAttributeParser:
arguments:
- - '@annotation_reader'
- '@Sineflow\ElasticsearchBundle\Mapping\DocumentLocator'
- '%sfes.mlproperty.language_separator%'
- '%sfes.languages%'
Sineflow\ElasticsearchBundle\Mapping\DocumentMetadataCollector:
arguments:
- - '%sfes.indices%'
- - '@Sineflow\ElasticsearchBundle\Mapping\DocumentLocator'
- - '@Sineflow\ElasticsearchBundle\Mapping\DocumentParser'
+ $indexManagers: '%sfes.indices%'
+ $documentLocator: '@Sineflow\ElasticsearchBundle\Mapping\DocumentLocator'
+ $annotationParser: '@?Sineflow\ElasticsearchBundle\Mapping\DocumentParser'
+ $attributeParser: '@Sineflow\ElasticsearchBundle\Mapping\DocumentAttributeParser'
+ $useAnnotations: '%sfes.use_annotations%'
Sineflow\ElasticsearchBundle\Subscriber\KnpPaginateQuerySubscriber:
arguments: ['@request_stack']
diff --git a/docs/configuration.md b/docs/configuration.md
index 8f96ef9..1f71bf1 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -7,6 +7,8 @@ sineflow_elasticsearch:
languages: ['en', 'fr']
+ use_annotations: false
+
metadata_cache_pool: sfes.metadata_cache_pool
entity_locations:
diff --git a/docs/crud.md b/docs/crud.md
index aefb623..2cd8e1e 100644
--- a/docs/crud.md
+++ b/docs/crud.md
@@ -11,15 +11,14 @@ For all steps below we assume that there is an `App` entity location with the `P
use Sineflow\ElasticsearchBundle\Annotation as ES;
use Sineflow\ElasticsearchBundle\Document\AbstractDocument;
-/**
- * @ES\Document
- */
+#[ES\Document]
class Product extends AbstractDocument
{
- /**
- * @ES\Property(type="text", name="title")
- */
- public $title;
+ #[ES\Property(
+ name: 'title',
+ type: 'text',
+ )]
+ public ?string $title = null;
}
```
diff --git a/docs/i18n.md b/docs/i18n.md
index 404627e..7558712 100644
--- a/docs/i18n.md
+++ b/docs/i18n.md
@@ -16,21 +16,19 @@ sineflow_elasticsearch:
* Next, you need to declare your multilanguage field as such in your annotation:
```
- /**
- * @ES\Property(
- * name="title",
- * type="text",
- * multilanguage=true,
- * multilanguageDefaultOptions={
- * "type":"text",
- * "index":false
- * },
- * options={
- * "analyzer":"{lang}_analyzer",
- * }
- * )
- */
- public $title;
+#[ES\Property(
+ name: 'title',
+ type: 'text',
+ multilanguage: true,
+ multilanguageDefaultOptions: [
+ 'type' => 'text',
+ 'index' => false,
+ ],
+ options: [
+ 'analyzer' => '{lang}_analyzer',
+ ],
+)]
+public ?MLProperty $title = null;
```
> Note the use of **{lang}** in the analyzer declaration. It is going to be replaced by a respective language code at runtime.
diff --git a/docs/mapping-annotations.md b/docs/mapping-annotations.md
new file mode 100644
index 0000000..7343035
--- /dev/null
+++ b/docs/mapping-annotations.md
@@ -0,0 +1,182 @@
+# Mapping
+
+The Elasticsearch bundle requires document mapping definitions to create the correct index schema and be able to convert data to objects and vice versa - think Doctrine.
+
+For this to work, you need `doctrine/annotations` installed in your project and `use_annotations` set to `true` in your configuration.
+
+## Document class annotations
+
+Elasticsearch index mappings are defined using annotations within document entity classes that implement DocumentInterface:
+```php
+ Make sure your document classes directly implement DocumentInterface or extend AbstractDocument.
+
+
+### Document annotation
+
+The class representing a document must be annotated as `@ES\Document`. The following properties are supported inside that annotation:
+
+- `repositoryClass` Allows you to specify a specific repository class for this document. If not specified, the default repository class is used.
+```
+repositoryClass="App\Document\Repository\ProductRepository"
+```
+
+- `providerClass` Allows you to specify a specific data provider that will be used as data source when rebuilding the index. If not specified, the default self-provider is used, i.e the index is rebuilt from itself.
+```
+providerClass="App\Document\Provider\ProductProvider"
+```
+
+- `options` Allows to specify any type option supported by Elasticsearch, such as [\_all](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-all-field.html), [dynamic_templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-templates.html), [dynamic_date_formats](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html#date-detection), etc.
+
+### Property annotation
+
+Each field within the document is specified using the `@ES\Property` annotation. The following properties are supported inside that annotation:
+
+- `name` Specifies the name of the field (required).
+
+- `multilanguage` A flag that specifies whether the field will be multilanguage. For more information, see [declaring multilanguage properties](#mlproperties).
+```
+multilanguage=true
+```
+
+- `objectName` When the field type is `object` or `nested`, this property must be specified, as it specifies which class defines the (nested) object.
+```
+objectName="App:ObjAlias"
+```
+
+- `multiple` Relevant only for `object` and `nested` fields. It specifies whether the field contains a single object or multiple ones.
+```
+multiple=true
+```
+
+- `options` An array of literal options, sent to Elasticsearch as they are. The only exception is with multilanguage properties, where further processing is applied.
+```
+options={
+ "analyzer":"my_special_analyzer",
+ "null_value":0
+}
+```
+
+### Multilanguage properties
+
+Sometimes, you may have a field that is available in more than one language. This is declared like this:
+
+```
+ /**
+ * @ES\Property(
+ * name="name",
+ * type="text",
+ * multilanguage=true,
+ * multilanguageDefaultOptions={
+ * "type":"text",
+ * "index":false
+ * },
+ * options={
+ * "analyzer":"{lang}_analyzer",
+ * }
+ * )
+ */
+ public $name;
+```
+> Note the use of `{lang}` placeholder in the options.
+
+When you have a property definition like that, there will not be a field `name` in your index, but instead there will be `name-en`, `name-fr`, `name-de`, etc. where the suffixes are taken from the available languages in your application.
+There will also be a field `name-default`, whose default mapping of `type:keyword;ignore_above:256` you can optionally override by specifying alternative `multilanguageDefaultOptions`.
+
+You may also use the special `{lang}` placeholder in the options array, as often you would need to specify different analyzers, depending on the language. For more information on how that works, see [multilanguage support](i18n.md).
+
+### Meta property annotations
+
+#### @ES\Id
+
+If you need to have access to the `_id` property of an Elasticsearch document you need to have a class property with this annotation.
+This way, you can specify the `_id` when you create or update a document and you will also have that value populated in your object when you retrieve an existing document.
+
+```php
+use Sineflow\ElasticsearchBundle\Annotation as ES;
+
+/**
+ * @ES\Document
+ */
+class Product
+{
+ /**
+ * @var string
+ *
+ * @ES\Id
+ */
+ public $id;
+}
+```
+> Such property is already defined in `AbstractDocument`, so you can just extend it.
+
+#### @ES\Score
+
+You should have a property with this annotation, if you wish the matching `_score` of the document to be populated in it when searching.
+
+```php
+use Sineflow\ElasticsearchBundle\Annotation as ES;
+
+/**
+ * @ES\Document
+ */
+class Product
+{
+ /**
+ * @var float
+ *
+ * @ES\Score
+ */
+ public $score;
+}
+```
+> Such property is already defined in `AbstractDocument`, so you can just extend it.
+
+## DocObject class annotation
+
+Object classes are almost the same as document classes:
+
+```php
+ **Mapping with annotations is still supported, but is deprecated and will be removed in the future.
+To see how it works, check [mapping-annotations.md](mapping-annotations.md).**
+
The Elasticsearch bundle requires document mapping definitions to create the correct index schema and be able to convert data to objects and vice versa - think Doctrine.
-## Document class annotations
+## Document class attributes
-Elasticsearch index mappings are defined using annotations within document entity classes that implement DocumentInterface:
+Elasticsearch index mappings are defined using attributes within document entity classes that implement DocumentInterface:
```php
Make sure your document classes directly implement DocumentInterface or extend AbstractDocument.
+> Make sure your document classes directly implement `DocumentInterface` or extend `AbstractDocument`.
-### Document annotation
+### Document attribute
-The class representing a document must be annotated as `@ES\Document`. The following properties are supported inside that annotation:
+The class representing a document must be annotated as `@ES\Document`. The following properties are supported inside that attribute:
- `repositoryClass` Allows you to specify a specific repository class for this document. If not specified, the default repository class is used.
```
-repositoryClass="App\Document\Repository\ProductRepository"
+repositoryClass: App\Document\Repository\ProductRepository
```
- `providerClass` Allows you to specify a specific data provider that will be used as data source when rebuilding the index. If not specified, the default self-provider is used, i.e the index is rebuilt from itself.
```
-repositoryClass="App\Document\Provider\ProductProvider"
+providerClass: App\Document\Provider\ProductProvider
```
- `options` Allows to specify any type option supported by Elasticsearch, such as [\_all](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-all-field.html), [dynamic_templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-templates.html), [dynamic_date_formats](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html#date-detection), etc.
-### Property annotation
+### Property attribute
-Each field within the document is specified using the `@ES\Property` annotation. The following properties are supported inside that annotation:
+Each field within the document is specified using the `@ES\Property` attribute. The following properties are supported inside that attribute:
- `name` Specifies the name of the field (required).
- `multilanguage` A flag that specifies whether the field will be multilanguage. For more information, see [declaring multilanguage properties](#mlproperties).
```
-multilanguage=true
+multilanguage: true
```
- `objectName` When the field type is `object` or `nested`, this property must be specified, as it specifies which class defines the (nested) object.
```
-objectName="App:ObjAlias"
+objectName: App\Document\ObjAlias
```
- `multiple` Relevant only for `object` and `nested` fields. It specifies whether the field contains a single object or multiple ones.
```
-multiple=true
+multiple: true
```
- `options` An array of literal options, sent to Elasticsearch as they are. The only exception is with multilanguage properties, where further processing is applied.
```
-options={
- "analyzer":"my_special_analyzer",
- "null_value":0
-}
+options: [
+ 'analyzer': 'my_special_analyzer',
+ 'fields' => [
+ 'raw' => ['type' => 'keyword'],
+ 'title' => ['type' => 'text'],
+ ],
+],
```
### Multilanguage properties
@@ -79,21 +83,19 @@ options={
Sometimes, you may have a field that is available in more than one language. This is declared like this:
```
- /**
- * @ES\Property(
- * name="name",
- * type="text",
- * multilanguage=true,
- * multilanguageDefaultOptions={
- * "type":"text",
- * "index":false
- * },
- * options={
- * "analyzer":"{lang}_analyzer",
- * }
- * )
- */
- public $name;
+#[ES\Property(
+ name: 'name',
+ type: 'text',
+ multilanguage: true,
+ multilanguageDefaultOptions: [
+ 'type' => 'text',
+ 'index' => false,
+ ],
+ options: [
+ 'analyzer' => '{lang}_analyzer',
+ ],
+)]
+public ?MLProperty $name = null;
```
> Note the use of `{lang}` placeholder in the options.
@@ -102,54 +104,42 @@ There will also be a field `name-default`, whose default mapping of `type:keywor
You may also use the special `{lang}` placeholder in the options array, as often you would need to specify different analyzers, depending on the language. For more information on how that works, see [multilanguage support](i18n.md).
-### Meta property annotations
+### Meta property attributes
-#### @ES\Id
+#### Id
-If you need to have access to the `_id` property of an Elasticsearch document you need to have a class property with this annotation.
+If you need to have access to the `_id` property of an Elasticsearch document you need to have a class property with this attribute.
This way, you can specify the `_id` when you create or update a document and you will also have that value populated in your object when you retrieve an existing document.
```php
-use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\attribute as ES;
-/**
- * @ES\Document
- */
+#[ES\Document]
class Product
{
- /**
- * @var string
- *
- * @ES\Id
- */
- public $id;
+ #[ES\Id]
+ public ?string $id = null;
}
```
> Such property is already defined in `AbstractDocument`, so you can just extend it.
-#### @ES\Score
+#### Score
-You should have a property with this annotation, if you wish the matching `_score` of the document to be populated in it when searching.
+You should have a property with this attribute, if you wish the matching `_score` of the document to be populated in it when searching.
```php
-use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\Attribute as ES;
-/**
- * @ES\Document
- */
+#[ES\Document]
class Product
{
- /**
- * @var float
- *
- * @ES\Score
- */
- public $score;
+ #[ES\Score]
+ public ?float $score = null;
}
```
> Such property is already defined in `AbstractDocument`, so you can just extend it.
-## DocObject class annotation
+## DocObject class attribute
Object classes are almost the same as document classes:
@@ -158,23 +148,20 @@ Object classes are almost the same as document classes:
namespace App\Document;
use Sineflow\ElasticsearchBundle\Document\ObjectInterface;
-use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\Attribute as ES;
-/**
- * @ES\DocObject
- */
+#[ES\DocObject]
class ObjAlias implements ObjectInterface
{
- /**
- * @var string
- *
- * @ES\Property(name="title", type="text")
- */
- public $title;
+ #[ES\Property(
+ name: 'title',
+ type: 'text',
+ )]
+ public ?string $title = null;
}
```
-The difference with document classes is that the class must implement `ObjectInterface` and be annotated as `@ES\DocObject`. The mapping of the object properties follows the same rules as the one for the document properties.
+The difference with document classes is that the class must implement `ObjectInterface` and have a `DocObject` attribute. The mapping of the object properties follows the same rules as the one for the document properties.
More info about mapping is in the [elasticsearch mapping documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html)
diff --git a/docs/setup.md b/docs/setup.md
index a7337cb..ec3f818 100644
--- a/docs/setup.md
+++ b/docs/setup.md
@@ -65,20 +65,17 @@ The bundle uses `Document` objects to represent Elasticsearch documents. Now let
+ displayDetailsOnTestsThatTriggerDeprecations="true"
+ displayDetailsOnTestsThatTriggerErrors="true"
+ displayDetailsOnTestsThatTriggerNotices="true"
+ displayDetailsOnTestsThatTriggerWarnings="true"
+ bootstrap="tests/tests.bootstrap.php"
+ cacheDirectory=".phpunit.cache">
@@ -19,20 +17,15 @@
./tests/Functional/
-
- ./tests/
-
-
-
-
+
./
@@ -41,6 +34,9 @@
./vendor
./var
+
+
+
diff --git a/rector.php b/rector.php
index 3402a2a..4e2914e 100644
--- a/rector.php
+++ b/rector.php
@@ -5,6 +5,7 @@
use Rector\Caching\ValueObject\Storage\FileCacheStorage;
use Rector\Config\RectorConfig;
use Rector\Doctrine\Set\DoctrineSetList;
+use Rector\PHPUnit\Set\PHPUnitSetList;
use Rector\Symfony\Set\SymfonySetList;
return RectorConfig::configure()
@@ -26,6 +27,8 @@
->withSets([
SymfonySetList::SYMFONY_CODE_QUALITY,
DoctrineSetList::DOCTRINE_CODE_QUALITY,
+ PHPUnitSetList::PHPUNIT_CODE_QUALITY,
+ PHPUnitSetList::PHPUNIT_100,
])
->withPhpSets()
diff --git a/src/Attribute/DocObject.php b/src/Attribute/DocObject.php
new file mode 100644
index 0000000..1423a89
--- /dev/null
+++ b/src/Attribute/DocObject.php
@@ -0,0 +1,8 @@
+options;
+ }
+}
diff --git a/src/Attribute/Id.php b/src/Attribute/Id.php
new file mode 100644
index 0000000..bb9e09b
--- /dev/null
+++ b/src/Attribute/Id.php
@@ -0,0 +1,23 @@
+name;
+ }
+
+ public function getType(): ?string
+ {
+ return $this->type;
+ }
+
+ /**
+ * Dumps property fields as an array for index mapping
+ */
+ public function dump(array $settings = []): array
+ {
+ $result = $this->options;
+
+ // Although it is completely valid syntax to explicitly define objects as such in the mapping definition, ES does not do that by default.
+ // So, in order to ensure that the mapping for index creation would exactly match the mapping returned from the ES _mapping endpoint, we don't explicitly set 'object' data types
+ if ('object' !== $this->type) {
+ $result = \array_merge($result, ['type' => $this->type]);
+ }
+
+ if (isset($settings['language'])) {
+ if (!isset($settings['indexAnalyzers'])) {
+ throw new \InvalidArgumentException('Available index analyzers missing');
+ }
+
+ // Recursively replace {lang} in any string option with the respective language
+ \array_walk_recursive($result, static function (&$value, $key, $settings): void {
+ if (\is_string($value) && \str_contains($value, self::LANGUAGE_PLACEHOLDER)) {
+ if (\in_array($key, ['analyzer', 'index_analyzer', 'search_analyzer'])) {
+ // Replace {lang} in any analyzers with the respective language
+ // If no analyzer is defined for a certain language, replace {lang} with 'default'
+
+ // Get the names of all available analyzers in the index
+ $indexAnalyzers = \array_keys($settings['indexAnalyzers']);
+
+ // Make sure a default analyzer is defined, even if we don't need it right now
+ // because, if a new language is added and we don't have an analyzer for it, ES mapping would fail
+ $defaultAnalyzer = \str_replace(self::LANGUAGE_PLACEHOLDER, self::DEFAULT_LANG_SUFFIX, $value);
+ if (!\in_array($defaultAnalyzer, $indexAnalyzers)) {
+ throw new \LogicException(\sprintf('There must be a default language analyzer "%s" defined for index', $defaultAnalyzer));
+ }
+
+ $value = \str_replace(self::LANGUAGE_PLACEHOLDER, $settings['language'], $value);
+ if (!\in_array($value, $indexAnalyzers)) {
+ $value = $defaultAnalyzer;
+ }
+ } else {
+ // If it's any other option, just replace with the respective language
+ $value = \str_replace(self::LANGUAGE_PLACEHOLDER, $settings['language'], $value);
+ }
+ }
+ }, $settings);
+ }
+
+ return $result;
+ }
+}
diff --git a/src/Attribute/PropertyAttributeInterface.php b/src/Attribute/PropertyAttributeInterface.php
new file mode 100644
index 0000000..5e7571e
--- /dev/null
+++ b/src/Attribute/PropertyAttributeInterface.php
@@ -0,0 +1,10 @@
+end()
->end()
->scalarNode('metadata_cache_pool')->end()
+ ->booleanNode('use_annotations')
+ ->info('Read mapping info from annotations instead of attributes. Used for backward compatibility')
+ ->defaultFalse()
+ ->end()
->append($this->getEntityLocationsNode())
->append($this->getConnectionsNode())
->append($this->getIndicesNode())
diff --git a/src/DependencyInjection/SineflowElasticsearchExtension.php b/src/DependencyInjection/SineflowElasticsearchExtension.php
index a0c083a..aa453f2 100644
--- a/src/DependencyInjection/SineflowElasticsearchExtension.php
+++ b/src/DependencyInjection/SineflowElasticsearchExtension.php
@@ -2,11 +2,15 @@
namespace Sineflow\ElasticsearchBundle\DependencyInjection;
+use Doctrine\Common\Annotations\AnnotationReader;
use Sineflow\ElasticsearchBundle\Document\Provider\ProviderInterface;
use Sineflow\ElasticsearchBundle\Document\Repository\ServiceRepositoryInterface;
+use Sineflow\ElasticsearchBundle\Mapping\DocumentLocator;
+use Sineflow\ElasticsearchBundle\Mapping\DocumentParser;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
+use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
@@ -27,6 +31,21 @@ public function load(array $configs, ContainerBuilder $container): void
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
+ // Conditionally register the annotation_reader service, only if doctrine/annotations is installed.
+ // This will not be needed when support for annotations is removed.
+ if (class_exists(AnnotationReader::class)) {
+ $container->register('annotation_reader', AnnotationReader::class)
+ ->setPublic(true);
+
+ $container->register(DocumentParser::class, DocumentParser::class)
+ ->setArguments([
+ new Reference('annotation_reader'),
+ new Reference(DocumentLocator::class),
+ '%sfes.mlproperty.language_separator%',
+ '%sfes.languages%',
+ ]);
+ }
+
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../../config'));
$loader->load('services.yml');
@@ -35,6 +54,7 @@ public function load(array $configs, ContainerBuilder $container): void
$container->setParameter('sfes.indices', $config['indices']);
$container->setParameter('sfes.languages', $config['languages']);
$container->setParameter('sfes.cache_pool', $config['metadata_cache_pool'] ?? null);
+ $container->setParameter('sfes.use_annotations', $config['use_annotations']);
$container
->registerForAutoconfiguration(ServiceRepositoryInterface::class)
diff --git a/src/Document/AbstractDocument.php b/src/Document/AbstractDocument.php
index fc8ad44..181acf7 100644
--- a/src/Document/AbstractDocument.php
+++ b/src/Document/AbstractDocument.php
@@ -3,6 +3,7 @@
namespace Sineflow\ElasticsearchBundle\Document;
use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\Attribute as SFES;
/**
* Document abstraction which introduces mandatory fields for the document.
@@ -12,10 +13,12 @@ abstract class AbstractDocument implements DocumentInterface
/**
* @ES\Id
*/
+ #[SFES\Id]
public string|int|null $id = null;
/**
* @ES\Score
*/
+ #[SFES\Score]
public ?float $score = null;
}
diff --git a/src/Exception/InvalidMappingException.php b/src/Exception/InvalidMappingException.php
new file mode 100644
index 0000000..88c91b1
--- /dev/null
+++ b/src/Exception/InvalidMappingException.php
@@ -0,0 +1,7 @@
+getAttributes(Document::class);
+
+ if (empty($documentAttributes)) {
+ throw new InvalidMappingException(sprintf('Class "%s" must have the "%s" attribute in order to be used as a Document', $documentReflection->getName(), Document::class));
+ }
+
+ /** @var Document $documentAttribute */
+ $documentAttribute = $documentAttributes[0]->newInstance();
+
+ $properties = $this->getProperties($documentReflection, $indexAnalyzers);
+
+ return [
+ 'properties' => $properties,
+ 'fields' => \array_filter($documentAttribute->dump()),
+ 'propertiesMetadata' => $this->getPropertiesMetadata($documentReflection),
+ 'repositoryClass' => $documentAttribute->repositoryClass,
+ 'providerClass' => $documentAttribute->providerClass,
+ 'className' => $documentReflection->getName(),
+ ];
+ }
+
+ /**
+ * Finds properties' metadata for every property used in document or inner/nested object
+ *
+ * @throws InvalidMappingException
+ * @throws \ReflectionException
+ * @throws \LogicException
+ */
+ public function getPropertiesMetadata(\ReflectionClass $documentReflection): array
+ {
+ $className = $documentReflection->getName();
+ if (\array_key_exists($className, $this->propertiesMetadata)) {
+ return $this->propertiesMetadata[$className];
+ }
+
+ $propertyMetadata = [];
+
+ /** @var \ReflectionProperty $propertyReflection */
+ foreach ($this->getDocumentPropertiesReflection($documentReflection) as $propertyName => $propertyReflection) {
+ $propertyAttributes = $propertyReflection->getAttributes(Property::class);
+ $propertyAttributes = $propertyAttributes ?: $propertyReflection->getAttributes(Id::class);
+ $propertyAttributes = $propertyAttributes ?: $propertyReflection->getAttributes(Score::class);
+
+ // Ignore class properties without any recognized attribute
+ if (empty($propertyAttributes)) {
+ continue;
+ }
+
+ /** @var PropertyAttributeInterface $propertyAttribute */
+ $propertyAttribute = $propertyAttributes[0]->newInstance();
+
+ switch ($propertyAttribute::class) {
+ case Property::class:
+ $propertyMetadata[$propertyAttribute->name] = [
+ 'propertyName' => $propertyName,
+ 'type' => $propertyAttribute->type,
+ ];
+ if ($propertyAttribute->multilanguage) {
+ $propertyMetadata[$propertyAttribute->name]['multilanguage'] = true;
+ }
+
+ // If property is a (nested) object
+ if (\in_array($propertyAttribute->type, ['object', 'nested'])) {
+ if (!$propertyAttribute->objectName) {
+ throw new InvalidMappingException(sprintf('Property "%s" in %s is missing "objectName" setting', $propertyName, $className));
+ }
+ $child = new \ReflectionClass($this->documentLocator->resolveClassName($propertyAttribute->objectName));
+ $propertyMetadata[$propertyAttribute->name] = \array_merge(
+ $propertyMetadata[$propertyAttribute->name],
+ [
+ 'multiple' => $propertyAttribute->multiple,
+ 'propertiesMetadata' => $this->getPropertiesMetadata($child),
+ 'className' => $child->getName(),
+ ],
+ );
+ } else {
+ if (null !== $propertyAttribute->enumType) {
+ if (!enum_exists($propertyAttribute->enumType)) {
+ throw new InvalidMappingException(sprintf('Enum "%s" for property "%s" in %s does not exist', $propertyAttribute->enumType, $propertyName, $className));
+ }
+ $propertyMetadata[$propertyAttribute->name]['enumType'] = $propertyAttribute->enumType;
+ }
+ }
+ break;
+
+ case Score::class:
+ case Id::class:
+ $propertyMetadata[$propertyAttribute->getName()] = [
+ 'propertyName' => $propertyName,
+ 'type' => $propertyAttribute->getType(),
+ ];
+ break;
+ }
+
+ if ($propertyReflection->isPublic()) {
+ $propertyAccess = DocumentMetadata::PROPERTY_ACCESS_PUBLIC;
+ } else {
+ $propertyAccess = DocumentMetadata::PROPERTY_ACCESS_PRIVATE;
+ $camelCaseName = \ucfirst(Caser::camel($propertyName));
+ $setterMethod = 'set'.$camelCaseName;
+ $getterMethod = 'get'.$camelCaseName;
+ // Allow issers as getters for boolean properties
+ if ('boolean' === $propertyAttribute->getType() && !$documentReflection->hasMethod($getterMethod)) {
+ $getterMethod = 'is'.$camelCaseName;
+ }
+ if ($documentReflection->hasMethod($getterMethod) && $documentReflection->hasMethod($setterMethod)) {
+ $propertyMetadata[$propertyAttribute->getName()]['methods'] = [
+ 'getter' => $getterMethod,
+ 'setter' => $setterMethod,
+ ];
+ } else {
+ $message = sprintf('Property "%s" either needs to be public or %s() and %s() methods must be defined', $propertyName, $getterMethod, $setterMethod);
+ throw new \LogicException($message);
+ }
+ }
+
+ $propertyMetadata[$propertyAttribute->getName()]['propertyAccess'] = $propertyAccess;
+ }
+
+ $this->propertiesMetadata[$className] = $propertyMetadata;
+
+ return $this->propertiesMetadata[$className];
+ }
+
+ /**
+ * Returns all defined properties, including the ones from parents.
+ */
+ private function getDocumentPropertiesReflection(\ReflectionClass $documentReflection): array
+ {
+ if (\in_array($documentReflection->getName(), $this->properties)) {
+ return $this->properties[$documentReflection->getName()];
+ }
+
+ $properties = [];
+
+ foreach ($documentReflection->getProperties() as $property) {
+ if (!\in_array($property->getName(), $properties)) {
+ $properties[$property->getName()] = $property;
+ }
+ }
+
+ $parentReflection = $documentReflection->getParentClass();
+ if (false !== $parentReflection) {
+ $properties = \array_merge(
+ $properties,
+ \array_diff_key($this->getDocumentPropertiesReflection($parentReflection), $properties),
+ );
+ }
+
+ $this->properties[$documentReflection->getName()] = $properties;
+
+ return $properties;
+ }
+
+ /**
+ * Returns properties of reflection class.
+ *
+ * @param \ReflectionClass $documentReflection class to read properties from
+ *
+ * @throws \ReflectionException
+ * @throws InvalidMappingException
+ */
+ private function getProperties(\ReflectionClass $documentReflection, array $indexAnalyzers = []): array
+ {
+ $mapping = [];
+ /** @var \ReflectionProperty $propertyReflection */
+ foreach ($this->getDocumentPropertiesReflection($documentReflection) as $propertyReflection) {
+ $propertyAttributes = $propertyReflection->getAttributes(Property::class);
+ if (empty($propertyAttributes)) {
+ continue;
+ }
+
+ /** @var Property $propertyAttribute */
+ $propertyAttribute = $propertyAttributes[0]->newInstance();
+
+ // If it is a multi-language property
+ if (true === $propertyAttribute->multilanguage) {
+ if (!\in_array($propertyAttribute->getType(), ['string', 'keyword', 'text'])) {
+ throw new InvalidMappingException(sprintf('"%s" property in %s is declared as multilanguage, so can only be of type "keyword", "text" or the deprecated "string"', $propertyAttribute->getName(), $documentReflection->getName()));
+ }
+ if (!$this->languages) {
+ throw new InvalidMappingException('There must be at least one language specified in sineflow_elasticsearch.languages in order to use multilanguage properties');
+ }
+ foreach ($this->languages as $language) {
+ $mapping[$propertyAttribute->getName().$this->languageSeparator.$language] = $this->getPropertyMapping($propertyAttribute, $language, $indexAnalyzers);
+ }
+ // TODO: The application should decide whether it wants to use a default field at all and set its mapping on a global base
+ // The custom mapping from the application should be set here, using perhaps some kind of decorator
+ $mapping[$propertyAttribute->getName().$this->languageSeparator.Property::DEFAULT_LANG_SUFFIX] = $propertyAttribute->multilanguageDefaultOptions ?: [
+ 'type' => 'keyword',
+ 'ignore_above' => 256,
+ ];
+ } else {
+ $mapping[$propertyAttribute->getName()] = $this->getPropertyMapping($propertyAttribute, null, $indexAnalyzers);
+ }
+ }
+
+ return $mapping;
+ }
+
+ /**
+ * @throws \ReflectionException
+ */
+ private function getPropertyMapping(Property $propertyAttribute, ?string $language = null, array $indexAnalyzers = []): array
+ {
+ $propertyMapping = $propertyAttribute->dump([
+ 'language' => $language,
+ 'indexAnalyzers' => $indexAnalyzers,
+ ]);
+
+ // Inner/nested object
+ if (\in_array($propertyAttribute->type, ['object', 'nested']) && !empty($propertyAttribute->objectName)) {
+ $propertyMapping = \array_replace_recursive($propertyMapping, $this->getObjectMapping($propertyAttribute->objectName, $indexAnalyzers));
+ }
+
+ return $propertyMapping;
+ }
+
+ /**
+ * Returns object mapping.
+ *
+ * @throws \ReflectionException
+ */
+ private function getObjectMapping(string $objectName, array $indexAnalyzers = []): array
+ {
+ $className = $this->documentLocator->resolveClassName($objectName);
+
+ if (\array_key_exists($className, $this->objects)) {
+ return $this->objects[$className];
+ }
+
+ $this->objects[$className] = $this->getRelationMapping(new \ReflectionClass($className), $indexAnalyzers);
+
+ return $this->objects[$className];
+ }
+
+ /**
+ * Returns relation mapping by its reflection.
+ *
+ * @throws \ReflectionException
+ * @throws InvalidMappingException
+ */
+ private function getRelationMapping(\ReflectionClass $objectReflection, array $indexAnalyzers = []): array
+ {
+ $docObjectAttributes = $objectReflection->getAttributes(DocObject::class);
+ if (empty($docObjectAttributes)) {
+ throw new InvalidMappingException(sprintf('Class "%s" must have the "%s" attribute in order to be used as a nested object inside a Document', $objectReflection->getName(), DocObject::class));
+ }
+
+ return ['properties' => $this->getProperties($objectReflection, $indexAnalyzers)];
+ }
+}
diff --git a/src/Mapping/DocumentMetadataCollector.php b/src/Mapping/DocumentMetadataCollector.php
index 7d046c2..80cf7d4 100644
--- a/src/Mapping/DocumentMetadataCollector.php
+++ b/src/Mapping/DocumentMetadataCollector.php
@@ -27,17 +27,32 @@ class DocumentMetadataCollector implements WarmableInterface
*/
private array $documentClassToIndexManagerNames = [];
+ private readonly DocumentParser|DocumentAttributeParser $documentParser;
+
/**
- * @param array $indexManagers The list of index managers defined
- * @param DocumentParser $parser For reading entity annotations
- * @param CacheInterface $cache For caching entity metadata
+ * @param array $indexManagers The list of index managers defined
+ * @param DocumentParser|null $annotationParser For reading entity annotations
+ * @param DocumentAttributeParser $attributeParser For reading entity attributes
+ * @param CacheInterface $cache For caching entity metadata
+ * @param bool $useAnnotations Whether to use the attribute parser or the annotation parser
*/
public function __construct(
- private array $indexManagers,
+ private readonly array $indexManagers,
private readonly DocumentLocator $documentLocator,
- private readonly DocumentParser $parser,
+ private readonly ?DocumentParser $annotationParser,
+ private readonly DocumentAttributeParser $attributeParser,
private readonly CacheInterface $cache,
+ private readonly bool $useAnnotations = false,
) {
+ if ($this->useAnnotations) {
+ if (null === $this->annotationParser) {
+ throw new \LogicException('Annotations are enabled (use_annotations: true), but the "doctrine/annotations" package is not available.');
+ }
+ $this->documentParser = $this->annotationParser;
+ } else {
+ $this->documentParser = $this->attributeParser;
+ }
+
// Build an internal array with map of document class to index manager name
foreach ($this->indexManagers as $indexManagerName => $indexSettings) {
$documentClass = $this->documentLocator->resolveClassName($indexSettings['class']);
@@ -101,7 +116,7 @@ public function getObjectPropertiesMetadata(string $objectClass): array
$cacheKey = self::OBJECTS_CACHE_KEY_PREFIX.\strtr($objectClass, '\\', '.');
- return $this->cache->get($cacheKey, fn (ItemInterface $item): array => $this->parser->getPropertiesMetadata(new \ReflectionClass($objectClass)), 0);
+ return $this->cache->get($cacheKey, fn (ItemInterface $item): array => $this->documentParser->getPropertiesMetadata(new \ReflectionClass($objectClass)), 0);
}
/**
@@ -130,7 +145,8 @@ private function fetchDocumentMetadata(string $documentClass): DocumentMetadata
$documentClass = $this->documentLocator->resolveClassName($documentClass);
$indexManagerName = $this->getDocumentClassIndex($documentClass);
$indexAnalyzers = $this->indexManagers[$indexManagerName]['settings']['analysis']['analyzer'] ?? [];
- $documentMetadataArray = $this->parser->parse(new \ReflectionClass($documentClass), $indexAnalyzers);
+
+ $documentMetadataArray = $this->documentParser->parse(new \ReflectionClass($documentClass), $indexAnalyzers);
return new DocumentMetadata($documentMetadataArray);
}
diff --git a/src/Mapping/DocumentParser.php b/src/Mapping/DocumentParser.php
index 0e68d5a..d1ecbcd 100644
--- a/src/Mapping/DocumentParser.php
+++ b/src/Mapping/DocumentParser.php
@@ -13,6 +13,8 @@
/**
* Document parser used for reading document annotations.
+ *
+ * @deprecated Use DocumentAttributeParser instead.
*/
class DocumentParser
{
diff --git a/tests/AbstractContainerAwareTestCase.php b/tests/AbstractContainerAwareTestCase.php
index d79bd98..2d5a949 100644
--- a/tests/AbstractContainerAwareTestCase.php
+++ b/tests/AbstractContainerAwareTestCase.php
@@ -1,50 +1,9 @@
cachedContainer = null;
- }
-
- /**
- * Returns service container.
- *
- * @param array $kernelOptions Options used passed to kernel if it needs to be initialized.
- */
- protected function getContainer(array $kernelOptions = []): ContainerInterface
- {
- if (!$this->cachedContainer) {
- static::bootKernel($kernelOptions);
- // gets the special container that allows fetching private services
- $this->cachedContainer = static::$container;
- }
-
- return $this->cachedContainer;
- }
}
diff --git a/tests/App/config/config_test.yml b/tests/App/config/config_test.yml
index d1d3c2f..8aaee26 100644
--- a/tests/App/config/config_test.yml
+++ b/tests/App/config/config_test.yml
@@ -14,6 +14,8 @@ framework:
sineflow_elasticsearch:
+ use_annotations: false
+
languages: ['en', 'fr']
entity_locations:
diff --git a/tests/App/fixture/Acme/BarBundle/Document/ObjCategory.php b/tests/App/fixture/Acme/BarBundle/Document/ObjCategory.php
index 9d60557..0df3644 100644
--- a/tests/App/fixture/Acme/BarBundle/Document/ObjCategory.php
+++ b/tests/App/fixture/Acme/BarBundle/Document/ObjCategory.php
@@ -3,38 +3,51 @@
namespace Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document;
use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\Attribute as SFES;
use Sineflow\ElasticsearchBundle\Document\ObjectInterface;
+use Sineflow\ElasticsearchBundle\Result\ObjectIterator;
/**
* Category document for testing.
*
* @ES\DocObject
*/
+#[SFES\DocObject]
class ObjCategory implements ObjectInterface
{
/**
- * @var string Field without ESB annotation, should not be indexed.
+ * @var string Field without a SFES attribute or ES annotation - should not be indexed.
*/
- public $withoutAnnotation;
+ public string $withoutAnnotation;
/**
- * @var int
- *
* @ES\Property(type="integer", name="id")
*/
- public $id;
+ #[SFES\Property(
+ name: 'id',
+ type: 'integer',
+ )]
+ public ?int $id = null;
/**
- * @var string
- *
* @ES\Property(type="keyword", name="title")
*/
- public $title;
+ #[SFES\Property(
+ name: 'title',
+ type: 'keyword',
+ )]
+ public ?string $title = null;
/**
- * @var ObjTag[]
+ * @var ObjTag[]|ObjectIterator
*
* @ES\Property(type="object", name="tags", multiple=true, objectName="AcmeBarBundle:ObjTag")
*/
- public $tags;
+ #[SFES\Property(
+ name: 'tags',
+ type: 'object',
+ objectName: ObjTag::class,
+ multiple: true,
+ )]
+ public ObjectIterator|array $tags = [];
}
diff --git a/tests/App/fixture/Acme/BarBundle/Document/ObjTag.php b/tests/App/fixture/Acme/BarBundle/Document/ObjTag.php
index 99a6e6f..5d39220 100644
--- a/tests/App/fixture/Acme/BarBundle/Document/ObjTag.php
+++ b/tests/App/fixture/Acme/BarBundle/Document/ObjTag.php
@@ -3,6 +3,7 @@
namespace Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document;
use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\Attribute as SFES;
use Sineflow\ElasticsearchBundle\Document\ObjectInterface;
/**
@@ -10,12 +11,15 @@
*
* @ES\DocObject
*/
+#[SFES\DocObject]
class ObjTag implements ObjectInterface
{
/**
- * @var string
- *
* @ES\Property(type="text", name="tagname")
*/
- public $tagName;
+ #[SFES\Property(
+ name: 'tagname',
+ type: 'text',
+ )]
+ public ?string $tagName = null;
}
diff --git a/tests/App/fixture/Acme/BarBundle/Document/Product.php b/tests/App/fixture/Acme/BarBundle/Document/Product.php
index cdaa912..8fb5394 100644
--- a/tests/App/fixture/Acme/BarBundle/Document/Product.php
+++ b/tests/App/fixture/Acme/BarBundle/Document/Product.php
@@ -3,9 +3,11 @@
namespace Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document;
use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\Attribute as SFES;
use Sineflow\ElasticsearchBundle\Document\AbstractDocument;
use Sineflow\ElasticsearchBundle\Document\MLProperty;
use Sineflow\ElasticsearchBundle\Result\ObjectIterator;
+use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document\Repository\ProductRepository;
/**
* Product document for testing.
@@ -17,11 +19,15 @@
* }
* )
*/
+#[SFES\Document(
+ repositoryClass: ProductRepository::class,
+ options: [
+ 'dynamic' => 'strict',
+ ],
+)]
class Product extends AbstractDocument
{
/**
- * @var string
- *
* @ES\Property(
* type="text",
* name="title",
@@ -33,60 +39,87 @@ class Product extends AbstractDocument
* }
* )
*/
- public $title;
+ #[SFES\Property(
+ name: 'title',
+ type: 'text',
+ options: [
+ 'fields' => [
+ 'raw' => ['type' => 'keyword'],
+ 'title' => ['type' => 'text'],
+ ],
+ ],
+ )]
+ public ?string $title = null;
/**
- * @var string
- *
* @ES\Property(type="text", name="description")
*/
- public $description;
+ #[SFES\Property(
+ name: 'description',
+ type: 'text',
+ )]
+ public ?string $description = null;
/**
- * @var ObjCategory
- *
* @ES\Property(type="object", name="category", objectName="AcmeBarBundle:ObjCategory")
*/
- public $category;
+ #[SFES\Property(
+ name: 'category',
+ type: 'object',
+ objectName: ObjCategory::class,
+ )]
+ public ?ObjCategory $category = null;
/**
* @var ObjCategory[]|ObjectIterator
*
* @ES\Property(type="object", name="related_categories", multiple=true, objectName="AcmeBarBundle:ObjCategory")
*/
- public $relatedCategories;
+ #[SFES\Property(
+ name: 'related_categories',
+ type: 'object',
+ objectName: ObjCategory::class,
+ multiple: true,
+ )]
+ public array|ObjectIterator $relatedCategories = [];
/**
- * @var int
- *
* @ES\Property(type="float", name="price")
*/
- public $price;
+ #[SFES\Property(
+ name: 'price',
+ type: 'float',
+ )]
+ public ?int $price = null;
/**
- * @var string
- *
* @ES\Property(type="geo_point", name="location")
*/
- public $location;
+ #[SFES\Property(
+ name: 'location',
+ type: 'geo_point',
+ )]
+ public ?string $location = null;
/**
- * @var string
- *
* @ES\Property(type="boolean", name="limited")
*/
- public $limited;
+ #[SFES\Property(
+ name: 'limited',
+ type: 'boolean',
+ )]
+ public ?string $limited = null;
/**
- * @var string
- *
* @ES\Property(type="date", name="released")
*/
- public $released;
+ #[SFES\Property(
+ name: 'released',
+ type: 'date',
+ )]
+ public ?string $released = null;
/**
- * @var MLProperty
- *
* @ES\Property(
* name="ml_info",
* type="text",
@@ -102,11 +135,23 @@ class Product extends AbstractDocument
* }
* )
*/
- public $mlInfo;
+ #[SFES\Property(
+ name: 'ml_info',
+ type: 'text',
+ multilanguage: true,
+ options: [
+ 'analyzer' => '{lang}_analyzer',
+ 'fields' => [
+ 'ngram' => [
+ 'type' => 'text',
+ 'analyzer' => '{lang}_analyzer',
+ ],
+ ],
+ ],
+ )]
+ public ?MLProperty $mlInfo = null;
/**
- * @var MLProperty
- *
* @ES\Property(
* name="ml_more_info",
* type="text",
@@ -117,11 +162,18 @@ class Product extends AbstractDocument
* }
* )
*/
- public $mlMoreInfo;
+ #[SFES\Property(
+ name: 'ml_more_info',
+ type: 'text',
+ multilanguage: true,
+ multilanguageDefaultOptions: [
+ 'type' => 'text',
+ 'index' => false,
+ ],
+ )]
+ public ?MLProperty $mlMoreInfo = null;
/**
- * @var int
- *
* @ES\Property(
* type="text",
* name="pieces_count",
@@ -132,5 +184,17 @@ class Product extends AbstractDocument
* }
* )
*/
- public $tokenPiecesCount;
+ #[SFES\Property(
+ name: 'pieces_count',
+ type: 'text',
+ options: [
+ 'fields' => [
+ 'count' => [
+ 'type' => 'token_count',
+ 'analyzer' => 'whitespace',
+ ],
+ ],
+ ],
+ )]
+ public ?int $tokenPiecesCount = null;
}
diff --git a/tests/App/fixture/Acme/FooBundle/Document/Customer.php b/tests/App/fixture/Acme/FooBundle/Document/Customer.php
index ecea22a..7aa3830 100644
--- a/tests/App/fixture/Acme/FooBundle/Document/Customer.php
+++ b/tests/App/fixture/Acme/FooBundle/Document/Customer.php
@@ -3,6 +3,7 @@
namespace Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document;
use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\Attribute as SFES;
use Sineflow\ElasticsearchBundle\Document\AbstractDocument;
use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document\Provider\CustomerProvider;
use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Enum\CustomerTypeEnum;
@@ -12,6 +13,9 @@
* providerClass=CustomerProvider::class
* )
*/
+#[SFES\Document(
+ providerClass: CustomerProvider::class,
+)]
class Customer extends AbstractDocument
{
/**
@@ -19,6 +23,10 @@ class Customer extends AbstractDocument
*
* @ES\Property(name="name", type="keyword")
*/
+ #[SFES\Property(
+ name: 'name',
+ type: 'keyword',
+ )]
public string $name;
/**
@@ -30,25 +38,30 @@ class Customer extends AbstractDocument
* enumType=Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Enum\CustomerTypeEnum::class
* )
*/
+ #[SFES\Property(
+ name: 'customer_type',
+ type: 'integer',
+ enumType: CustomerTypeEnum::class,
+ )]
public ?CustomerTypeEnum $customerType = null;
/**
+ * @var bool
+ *
* @ES\Property(name="active", type="boolean")
*/
+ #[SFES\Property(
+ name: 'active',
+ type: 'boolean',
+ )]
private $active;
- /**
- * @return bool
- */
public function isActive()
{
return $this->active;
}
- /**
- * @param bool $active
- */
- public function setActive($active)
+ public function setActive($active): void
{
$this->active = $active;
}
diff --git a/tests/App/fixture/Acme/FooBundle/Document/EntityWithInvalidEnum.php b/tests/App/fixture/Acme/FooBundle/Document/EntityWithInvalidEnum.php
index 1a0a2af..59ea004 100644
--- a/tests/App/fixture/Acme/FooBundle/Document/EntityWithInvalidEnum.php
+++ b/tests/App/fixture/Acme/FooBundle/Document/EntityWithInvalidEnum.php
@@ -3,12 +3,14 @@
namespace Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document;
use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\Attribute as SFES;
use Sineflow\ElasticsearchBundle\Document\AbstractDocument;
use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Enum\CustomerTypeEnum;
/**
* @ES\Document
*/
+#[SFES\Document]
class EntityWithInvalidEnum extends AbstractDocument
{
/**
@@ -18,5 +20,10 @@ class EntityWithInvalidEnum extends AbstractDocument
* enumType=nonExistingEnumClass
* )
*/
+ #[SFES\Property(
+ name: 'enum_test',
+ type: 'string',
+ enumType: 'nonExistingEnumClass',
+ )]
public ?CustomerTypeEnum $enumTest = null;
}
diff --git a/tests/App/fixture/Acme/FooBundle/Document/Log.php b/tests/App/fixture/Acme/FooBundle/Document/Log.php
index 592b561..79ef879 100644
--- a/tests/App/fixture/Acme/FooBundle/Document/Log.php
+++ b/tests/App/fixture/Acme/FooBundle/Document/Log.php
@@ -3,17 +3,21 @@
namespace Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document;
use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\Attribute as SFES;
use Sineflow\ElasticsearchBundle\Document\AbstractDocument;
/**
* @ES\Document;
*/
+#[SFES\Document]
class Log extends AbstractDocument
{
/**
- * @var string
- *
* @ES\Property(name="entry", type="keyword")
*/
- public $entry;
+ #[SFES\Property(
+ name: 'entry',
+ type: 'keyword',
+ )]
+ public ?string $entry = null;
}
diff --git a/tests/App/fixture/Acme/FooBundle/Document/Order.php b/tests/App/fixture/Acme/FooBundle/Document/Order.php
index d27699a..2c90398 100644
--- a/tests/App/fixture/Acme/FooBundle/Document/Order.php
+++ b/tests/App/fixture/Acme/FooBundle/Document/Order.php
@@ -3,19 +3,26 @@
namespace Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document;
use Sineflow\ElasticsearchBundle\Annotation as ES;
+use Sineflow\ElasticsearchBundle\Attribute as SFES;
use Sineflow\ElasticsearchBundle\Document\AbstractDocument;
+use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document\Provider\OrderProvider;
/**
* @ES\Document(
* providerClass="Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document\Provider\OrderProvider"
* )
*/
+#[SFES\Document(
+ providerClass: OrderProvider::class,
+)]
class Order extends AbstractDocument
{
/**
- * @var int
- *
* @ES\Property(name="order_time", type="integer")
*/
- public $orderTime;
+ #[SFES\Property(
+ name: 'order_time',
+ type: 'integer',
+ )]
+ public ?int $orderTime = null;
}
diff --git a/tests/Functional/Document/Provider/ElasticsearchProviderTest.php b/tests/Functional/Document/Provider/ElasticsearchProviderTest.php
index 703c5d9..6ac32a6 100644
--- a/tests/Functional/Document/Provider/ElasticsearchProviderTest.php
+++ b/tests/Functional/Document/Provider/ElasticsearchProviderTest.php
@@ -37,9 +37,9 @@ public function testGetDocument(): void
$doc = $esProvider->getDocument(3);
- $this->assertEquals([
- '_id' => 3,
+ $this->assertSame([
'title' => 'Product 3',
+ '_id' => '3',
], $doc);
}
@@ -58,7 +58,7 @@ public function testGetDocuments(): void
\sort($ids);
// Make sure all and exact documents were returned
- $this->assertEquals([1, 2, 3], $ids);
+ $this->assertSame(['1', '2', '3'], $ids);
}
private function getProvider()
diff --git a/tests/Functional/Document/RepositoryTest.php b/tests/Functional/Document/RepositoryTest.php
index 3259cce..80b1c59 100644
--- a/tests/Functional/Document/RepositoryTest.php
+++ b/tests/Functional/Document/RepositoryTest.php
@@ -6,7 +6,6 @@
use Sineflow\ElasticsearchBundle\Document\Repository\Repository;
use Sineflow\ElasticsearchBundle\Finder\Finder;
use Sineflow\ElasticsearchBundle\Manager\IndexManager;
-use Sineflow\ElasticsearchBundle\Mapping\DocumentMetadataCollector;
use Sineflow\ElasticsearchBundle\Tests\AbstractElasticsearchTestCase;
class RepositoryTest extends AbstractElasticsearchTestCase
@@ -17,10 +16,6 @@ class RepositoryTest extends AbstractElasticsearchTestCase
private IndexManager $indexManager;
- private Finder $finder;
-
- private DocumentMetadataCollector $metadataCollector;
-
/**
* {@inheritdoc}
*/
@@ -48,12 +43,12 @@ protected function setUp(): void
{
parent::setUp();
- $this->finder = $this->getContainer()->get(Finder::class);
+ $finder = $this->getContainer()->get(Finder::class);
$this->indexManager = $this->getContainer()->get('sfes.index.bar');
$this->indexManager->getConnection()->setAutocommit(true);
- $this->repository = new Repository($this->indexManager, $this->finder);
+ $this->repository = new Repository($this->indexManager, $finder);
$this->getIndexManager('bar', !$this->hasCreatedIndexManager('bar'));
}
@@ -66,9 +61,9 @@ public function testGetIndexManager(): void
public function testGetById(): void
{
$doc = $this->repository->getById('doc1');
- $this->assertEquals('aaa', $doc->title);
+ $this->assertSame('aaa', $doc->title);
$doc = $this->repository->getById(2);
- $this->assertEquals('ccc', $doc->title);
+ $this->assertSame('ccc', $doc->title);
}
public function testCount(): void
@@ -81,17 +76,17 @@ public function testCount(): void
],
];
- $this->assertEquals(2, $this->repository->count($searchBody));
+ $this->assertSame(2, $this->repository->count($searchBody));
}
public function testReindex(): void
{
- $this->assertEquals(1, $this->repository->getById('doc1', Finder::RESULTS_RAW)['_version']);
+ $this->assertSame(1, $this->repository->getById('doc1', Finder::RESULTS_RAW)['_version']);
$this->indexManager->reindex('doc1');
$rawDoc = $this->repository->getById('doc1', Finder::RESULTS_RAW);
- $this->assertEquals(2, $rawDoc['_version']);
- $this->assertEquals('aaa', $rawDoc['_source']['title']);
+ $this->assertSame(2, $rawDoc['_version']);
+ $this->assertSame('aaa', $rawDoc['_source']['title']);
}
}
diff --git a/tests/Functional/Finder/Adapter/KnpPaginatorAdapterTest.php b/tests/Functional/Finder/Adapter/KnpPaginatorAdapterTest.php
index 1898b82..78862de 100644
--- a/tests/Functional/Finder/Adapter/KnpPaginatorAdapterTest.php
+++ b/tests/Functional/Finder/Adapter/KnpPaginatorAdapterTest.php
@@ -90,8 +90,8 @@ public function testPagination(): void
$this->assertCount(2, $pagination);
$this->assertInstanceOf(Product::class, $pagination->offsetGet(0));
- $this->assertEquals(3, $pagination->offsetGet(0)->id);
- $this->assertEquals(10, $pagination->getCustomParameter('aggregations')['avg_price']['value']);
+ $this->assertSame('3', $pagination->offsetGet(0)->id);
+ $this->assertEqualsWithDelta(10.0, $pagination->getCustomParameter('aggregations')['avg_price']['value'], PHP_FLOAT_EPSILON);
$this->assertIsArray($pagination->getCustomParameter('suggestions'));
// Test array results
@@ -102,8 +102,8 @@ public function testPagination(): void
/** @var SlidingPagination $pagination */
$pagination = $paginator->paginate($adapter, 2, 2);
- $this->assertEquals(3, $pagination->key());
- $this->assertEquals('3rd Product', $pagination->current()['title']);
+ $this->assertSame(3, $pagination->key());
+ $this->assertSame('3rd Product', $pagination->current()['title']);
$this->assertNull($pagination->getCustomParameter('aggregations'));
$this->assertNull($pagination->getCustomParameter('suggestions'));
@@ -115,9 +115,9 @@ public function testPagination(): void
/** @var SlidingPagination $pagination */
$pagination = $paginator->paginate($adapter, 2, 2);
- $this->assertEquals(3, $pagination->current()['_id']);
- $this->assertEquals('3rd Product', $pagination->current()['_source']['title']);
- $this->assertEquals(10, $pagination->getCustomParameter('aggregations')['avg_price']['value']);
+ $this->assertSame('3', $pagination->current()['_id']);
+ $this->assertSame('3rd Product', $pagination->current()['_source']['title']);
+ $this->assertEqualsWithDelta(10.0, $pagination->getCustomParameter('aggregations')['avg_price']['value'], PHP_FLOAT_EPSILON);
$this->assertIsArray($pagination->getCustomParameter('suggestions'));
}
@@ -139,14 +139,14 @@ public function testPaginationSorting(): void
// Do not apply default order to KNP, so just use the one in the query
/** @var SlidingPagination $pagination */
$pagination = $paginator->paginate($adapter, 1, 3);
- $this->assertEquals(3, $pagination->current()->id);
+ $this->assertSame('3', $pagination->current()->id);
// Test setting default order to KNP
$pagination = $paginator->paginate($adapter, 1, 3, [
'defaultSortFieldName' => '_id',
'defaultSortDirection' => 'desc',
]);
- $this->assertEquals(54321, $pagination->current()->id);
+ $this->assertSame('54321', $pagination->current()->id);
}
public function testInvalidResultsType(): void
diff --git a/tests/Functional/Finder/Adapter/ScrollAdapterTest.php b/tests/Functional/Finder/Adapter/ScrollAdapterTest.php
index 7ff01ea..9898f9f 100644
--- a/tests/Functional/Finder/Adapter/ScrollAdapterTest.php
+++ b/tests/Functional/Finder/Adapter/ScrollAdapterTest.php
@@ -75,9 +75,9 @@ public function testScanScroll(): void
++$scrolls;
}
- $this->assertEquals(6, $i, 'Total matching documents iterated');
- $this->assertEquals(6, $scrollAdapter->getTotalHits(), 'Total hits returned by scroll');
- $this->assertEquals(3, $scrolls, 'Total number of scrolls');
+ $this->assertSame(6, $i, 'Total matching documents iterated');
+ $this->assertSame(6, $scrollAdapter->getTotalHits(), 'Total hits returned by scroll');
+ $this->assertSame(3, $scrolls, 'Total number of scrolls');
// Test array results
/** @var ScrollAdapter $scrollAdapter */
@@ -98,9 +98,9 @@ public function testScanScroll(): void
}
++$scrolls;
}
- $this->assertEquals(6, $i, 'Total matching documents iterated');
- $this->assertEquals(6, $scrollAdapter->getTotalHits(), 'Total hits returned by scroll');
- $this->assertEquals(2, $scrolls, 'Total number of scrolls');
+ $this->assertSame(6, $i, 'Total matching documents iterated');
+ $this->assertSame(6, $scrollAdapter->getTotalHits(), 'Total hits returned by scroll');
+ $this->assertSame(2, $scrolls, 'Total number of scrolls');
// Test raw results
@@ -126,8 +126,8 @@ public function testScanScroll(): void
}
++$scrolls;
}
- $this->assertEquals(6, $i, 'Total matching documents iterated');
- $this->assertEquals(6, $scrollAdapter->getTotalHits(), 'Total hits returned by scroll');
- $this->assertEquals(2, $scrolls, 'Total number of scrolls');
+ $this->assertSame(6, $i, 'Total matching documents iterated');
+ $this->assertSame(6, $scrollAdapter->getTotalHits(), 'Total hits returned by scroll');
+ $this->assertSame(2, $scrolls, 'Total number of scrolls');
}
}
diff --git a/tests/Functional/Finder/FinderTest.php b/tests/Functional/Finder/FinderTest.php
index 8b5b75d..618a763 100644
--- a/tests/Functional/Finder/FinderTest.php
+++ b/tests/Functional/Finder/FinderTest.php
@@ -124,10 +124,10 @@ public function testGetById(): void
$docAsObject = $finder->get(Product::class, 'doc1');
$this->assertInstanceOf(Product::class, $docAsObject);
- $this->assertEquals('aaa', $docAsObject->title);
+ $this->assertSame('aaa', $docAsObject->title);
$docAsArray = $finder->get('AcmeBarBundle:Product', 'doc1', Finder::RESULTS_ARRAY);
- $this->assertEquals('aaa', $docAsArray['title']);
+ $this->assertSame('aaa', $docAsArray['title']);
$docAsRaw = $finder->get('AcmeBarBundle:Product', 'doc1', Finder::RESULTS_RAW);
$this->assertArraySubset([
@@ -169,18 +169,18 @@ public function testFindInMultipleTypesAndIndices(): void
$res = $finder->find(['AcmeBarBundle:Product', 'AcmeFooBundle:Customer'], $searchBody, Finder::RESULTS_OBJECT, [], $totalHits);
$this->assertInstanceOf(DocumentIterator::class, $res);
$this->assertCount(3, $res);
- $this->assertEquals(3, $totalHits);
+ $this->assertSame(3, $totalHits);
$resAsArray = iterator_to_array($res);
$this->assertInstanceOf(Customer::class, $resAsArray[0]);
$this->assertInstanceOf(Customer::class, $resAsArray[1]);
$this->assertInstanceOf(Product::class, $resAsArray[2]);
- $this->assertEquals(111, $resAsArray[0]->id);
+ $this->assertSame('111', $resAsArray[0]->id);
$this->assertSame('Jane Doe', $resAsArray[0]->name);
$this->assertSame(CustomerTypeEnum::COMPANY, $resAsArray[0]->customerType);
- $this->assertEquals(222, $resAsArray[1]->id);
+ $this->assertSame('222', $resAsArray[1]->id);
$this->assertSame('John Doe', $resAsArray[1]->name);
$this->assertSame(CustomerTypeEnum::INDIVIDUAL, $resAsArray[1]->customerType);
@@ -247,8 +247,8 @@ public function testCount(): void
],
];
- $this->assertEquals(2, $finder->count(['AcmeFooBundle:Customer'], $searchBody));
- $this->assertEquals(3, $finder->count(['AcmeBarBundle:Product', 'AcmeFooBundle:Customer'], $searchBody));
+ $this->assertSame(2, $finder->count(['AcmeFooBundle:Customer'], $searchBody));
+ $this->assertSame(3, $finder->count(['AcmeBarBundle:Product', 'AcmeFooBundle:Customer'], $searchBody));
}
public function testGetTargetIndices(): void
@@ -257,7 +257,7 @@ public function testGetTargetIndices(): void
$res = $finder->getTargetIndices(['AcmeBarBundle:Product', 'AcmeFooBundle:Customer']);
- $this->assertEquals([
+ $this->assertSame([
'sineflow-esb-test-bar',
'sineflow-esb-test-customer',
], $res);
diff --git a/tests/Functional/Manager/ConnectionManagerRegistryTest.php b/tests/Functional/Manager/ConnectionManagerRegistryTest.php
index 2625a03..ff31716 100644
--- a/tests/Functional/Manager/ConnectionManagerRegistryTest.php
+++ b/tests/Functional/Manager/ConnectionManagerRegistryTest.php
@@ -30,8 +30,6 @@ public function testGetAll(): void
$registry = $this->getContainer()->get(ConnectionManagerRegistry::class);
$connections = $registry->getAll();
- foreach ($connections as $connection) {
- $this->assertInstanceOf(ConnectionManager::class, $connection);
- }
+ $this->assertContainsOnlyInstancesOf(ConnectionManager::class, $connections);
}
}
diff --git a/tests/Functional/Manager/IndexManagerRegistryTest.php b/tests/Functional/Manager/IndexManagerRegistryTest.php
index 765111f..0c567f1 100644
--- a/tests/Functional/Manager/IndexManagerRegistryTest.php
+++ b/tests/Functional/Manager/IndexManagerRegistryTest.php
@@ -35,7 +35,7 @@ public function testGetByClass(): void
$product = new Product();
$im = $registry->getByClass($product::class);
$this->assertInstanceOf(IndexManager::class, $im);
- $this->assertEquals('bar', $im->getManagerName());
+ $this->assertSame('bar', $im->getManagerName());
}
public function testGetByEntity(): void
@@ -45,6 +45,6 @@ public function testGetByEntity(): void
$product = new Product();
$im = $registry->getByEntity($product);
$this->assertInstanceOf(IndexManager::class, $im);
- $this->assertEquals('bar', $im->getManagerName());
+ $this->assertSame('bar', $im->getManagerName());
}
}
diff --git a/tests/Functional/Manager/IndexManagerTest.php b/tests/Functional/Manager/IndexManagerTest.php
index f8e82bb..19d59dd 100644
--- a/tests/Functional/Manager/IndexManagerTest.php
+++ b/tests/Functional/Manager/IndexManagerTest.php
@@ -67,12 +67,12 @@ protected function getDataArray(): array
public function testGetReadAliasAndGetWriteAlias(): void
{
$imWithAliases = $this->getIndexManager('customer', false);
- $this->assertEquals('sineflow-esb-test-customer', $imWithAliases->getReadAlias());
- $this->assertEquals('sineflow-esb-test-customer_write', $imWithAliases->getWriteAlias());
+ $this->assertSame('sineflow-esb-test-customer', $imWithAliases->getReadAlias());
+ $this->assertSame('sineflow-esb-test-customer_write', $imWithAliases->getWriteAlias());
$imWithoutAliases = $this->getIndexManager('bar', false);
- $this->assertEquals('sineflow-esb-test-bar', $imWithoutAliases->getReadAlias());
- $this->assertEquals('sineflow-esb-test-bar', $imWithoutAliases->getWriteAlias());
+ $this->assertSame('sineflow-esb-test-bar', $imWithoutAliases->getReadAlias());
+ $this->assertSame('sineflow-esb-test-bar', $imWithoutAliases->getWriteAlias());
}
/**
@@ -157,7 +157,7 @@ public function testGetLiveIndex(): void
$imWithoutAliases = $this->getIndexManager('bar');
$liveIndex = $imWithoutAliases->getLiveIndex();
- $this->assertEquals('sineflow-esb-test-bar', $liveIndex);
+ $this->assertSame('sineflow-esb-test-bar', $liveIndex);
}
/**
@@ -192,7 +192,7 @@ public function testRebuildIndexWithoutDeletingOld(): void
$imWithAliases->getConnection()->getClient()->indices()->delete(['index' => $liveIndex]);
$newLiveIndex = $imWithAliases->getLiveIndex();
- $this->assertNotEquals($liveIndex, $newLiveIndex);
+ $this->assertNotSame($liveIndex, $newLiveIndex);
}
/**
@@ -212,7 +212,7 @@ public function testRebuildIndexAndDeleteOld(): void
$this->assertFalse($imWithAliases->getConnection()->getClient()->indices()->exists(['index' => $liveIndex])->asBool());
$newLiveIndex = $imWithAliases->getLiveIndex();
- $this->assertNotEquals($liveIndex, $newLiveIndex);
+ $this->assertNotSame($liveIndex, $newLiveIndex);
}
/**
@@ -236,7 +236,7 @@ public function testPersistForManagerWithoutAliasesWithoutAutocommit(): void
$imWithoutAliases->getConnection()->commit();
$doc = $imWithoutAliases->getRepository()->getById(555);
$this->assertInstanceOf(Product::class, $doc);
- $this->assertEquals('Acme title', $doc->title);
+ $this->assertSame('Acme title', $doc->title);
// Test persisting properties with null values
$product->title = null;
@@ -280,14 +280,14 @@ public function testPersistForManagerWithAliasesWithoutAutocommit(): void
$doc = $imWithAliases->getRepository()->getById(555);
$this->assertInstanceOf(Customer::class, $doc);
- $this->assertEquals('John Doe', $doc->name);
+ $this->assertSame('John Doe', $doc->name);
// Check that value is set in the additional index for the write alias as well
$raw = $imWithAliases->getConnection()->getClient()->get([
'index' => 'sineflow-esb-test-temp',
'id' => 555,
])->asArray();
- $this->assertEquals('John Doe', $raw['_source']['name']);
+ $this->assertSame('John Doe', $raw['_source']['name']);
$imWithAliases->getConnection()->getClient()->indices()->delete(['index' => 'sineflow-esb-test-temp']);
}
@@ -309,7 +309,7 @@ public function testPersistRawWithAutocommit(): void
]);
$doc = $imWithAliases->getRepository()->getById(444);
- $this->assertEquals('Jane', $doc->name);
+ $this->assertSame('Jane', $doc->name);
}
/**
@@ -328,7 +328,7 @@ public function testPersistStrictMappingDocRetrievedById(): void
$im->persist($doc);
$doc = $repo->getById('doc1');
- $this->assertEquals('NewName', $doc->title);
+ $this->assertSame('NewName', $doc->title);
}
/**
@@ -346,7 +346,7 @@ public function testUpdateWithCorrectParams(): void
]);
$doc = $imWithAliases->getRepository()->getById(111);
- $this->assertEquals('Alicia', $doc->name);
+ $this->assertSame('Alicia', $doc->name);
}
/**
@@ -416,18 +416,18 @@ public function testReindexWithElasticsearchSelfProvider(): void
$im->getConnection()->setAutocommit(false);
$rawDoc = $im->getRepository()->getById('abcde', Finder::RESULTS_RAW);
- $this->assertEquals(1, $rawDoc['_version']);
+ $this->assertSame(1, $rawDoc['_version']);
$im->reindex('abcde');
$rawDoc = $im->getRepository()->getById('abcde', Finder::RESULTS_RAW);
- $this->assertEquals(1, $rawDoc['_version']);
+ $this->assertSame(1, $rawDoc['_version']);
$im->getConnection()->commit();
$rawDoc = $im->getRepository()->getById('abcde', Finder::RESULTS_RAW);
- $this->assertEquals(2, $rawDoc['_version']);
- $this->assertEquals('log entry', $rawDoc['_source']['entry']);
+ $this->assertSame(2, $rawDoc['_version']);
+ $this->assertSame('log entry', $rawDoc['_source']['entry']);
}
public function testGetDataProvider(): void
@@ -463,8 +463,8 @@ public function testGetters(): void
$this->assertTrue($imWithAliases->getUseAliases());
$this->assertFalse($imWithoutAliases->getUseAliases());
- $this->assertEquals('customer', $imWithAliases->getManagerName());
- $this->assertEquals('bar', $imWithoutAliases->getManagerName());
+ $this->assertSame('customer', $imWithAliases->getManagerName());
+ $this->assertSame('bar', $imWithoutAliases->getManagerName());
}
/**
diff --git a/tests/Functional/Mapping/DocumentAttributeParserTest.php b/tests/Functional/Mapping/DocumentAttributeParserTest.php
new file mode 100644
index 0000000..07308a9
--- /dev/null
+++ b/tests/Functional/Mapping/DocumentAttributeParserTest.php
@@ -0,0 +1,305 @@
+getContainer()->get(DocumentLocator::class);
+ $separator = $this->getContainer()->getParameter('sfes.mlproperty.language_separator');
+ $languages = $this->getContainer()->getParameter('sfes.languages');
+ $this->documentAttributeParser = new DocumentAttributeParser($locator, $separator, $languages);
+ }
+
+ public function testParseNonDocument(): void
+ {
+ $this->expectException(InvalidMappingException::class);
+ $reflection = new \ReflectionClass(ObjCategory::class);
+ $res = $this->documentAttributeParser->parse($reflection, []);
+ }
+
+ public function testParseDocumentWithEnumProperty()
+ {
+ $reflection = new \ReflectionClass(Customer::class);
+ $res = $this->documentAttributeParser->parse($reflection, []);
+ $this->assertSame(CustomerTypeEnum::class, $res['propertiesMetadata']['customer_type']['enumType']);
+ }
+
+ public function testParseDocumentWithInvalidEnumFieldProperty()
+ {
+ $this->expectException(InvalidMappingException::class);
+ $reflection = new \ReflectionClass(EntityWithInvalidEnum::class);
+ $this->documentAttributeParser->parse($reflection, []);
+ }
+
+ public function testParse(): void
+ {
+ $reflection = new \ReflectionClass(Product::class);
+ $indexAnalyzers = [
+ 'default_analyzer' => [
+ 'type' => 'standard',
+ ],
+ 'en_analyzer' => [
+ 'type' => 'standard',
+ ],
+ ];
+
+ $res = $this->documentAttributeParser->parse($reflection, $indexAnalyzers);
+
+ $expected = [
+ 'properties' => [
+ 'title' => [
+ 'fields' => [
+ 'raw' => [
+ 'type' => 'keyword',
+ ],
+ 'title' => [
+ 'type' => 'text',
+ ],
+ ],
+ 'type' => 'text',
+ ],
+ 'description' => [
+ 'type' => 'text',
+ ],
+ 'category' => [
+ 'properties' => [
+ 'id' => [
+ 'type' => 'integer',
+ ],
+ 'title' => [
+ 'type' => 'keyword',
+ ],
+ 'tags' => [
+ 'properties' => [
+ 'tagname' => [
+ 'type' => 'text',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'related_categories' => [
+ 'properties' => [
+ 'id' => [
+ 'type' => 'integer',
+ ],
+ 'title' => [
+ 'type' => 'keyword',
+ ],
+ 'tags' => [
+ 'properties' => [
+ 'tagname' => [
+ 'type' => 'text',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'price' => [
+ 'type' => 'float',
+ ],
+ 'location' => [
+ 'type' => 'geo_point',
+ ],
+ 'limited' => [
+ 'type' => 'boolean',
+ ],
+ 'released' => [
+ 'type' => 'date',
+ ],
+ 'ml_info-en' => [
+ 'analyzer' => 'en_analyzer',
+ 'fields' => [
+ 'ngram' => [
+ 'type' => 'text',
+ 'analyzer' => 'en_analyzer',
+ ],
+ ],
+ 'type' => 'text',
+ ],
+ 'ml_info-fr' => [
+ 'analyzer' => 'default_analyzer',
+ 'fields' => [
+ 'ngram' => [
+ 'type' => 'text',
+ 'analyzer' => 'default_analyzer',
+ ],
+ ],
+ 'type' => 'text',
+ ],
+ 'ml_info-default' => [
+ 'type' => 'keyword',
+ 'ignore_above' => 256,
+ ],
+ 'ml_more_info-en' => [
+ 'type' => 'text',
+ ],
+ 'ml_more_info-fr' => [
+ 'type' => 'text',
+ ],
+ 'ml_more_info-default' => [
+ 'type' => 'text',
+ 'index' => false,
+ ],
+ 'pieces_count' => [
+ 'fields' => [
+ 'count' => [
+ 'type' => 'token_count',
+ 'analyzer' => 'whitespace',
+ ],
+ ],
+ 'type' => 'text',
+ ],
+ ],
+ 'fields' => [
+ 'dynamic' => 'strict',
+ ],
+ 'propertiesMetadata' => [
+ 'title' => [
+ 'propertyName' => 'title',
+ 'type' => 'text',
+ 'propertyAccess' => 1,
+ ],
+ 'description' => [
+ 'propertyName' => 'description',
+ 'type' => 'text',
+ 'propertyAccess' => 1,
+ ],
+ 'category' => [
+ 'propertyName' => 'category',
+ 'type' => 'object',
+ 'multiple' => null,
+ 'propertiesMetadata' => [
+ 'id' => [
+ 'propertyName' => 'id',
+ 'type' => 'integer',
+ 'propertyAccess' => 1,
+ ],
+ 'title' => [
+ 'propertyName' => 'title',
+ 'type' => 'keyword',
+ 'propertyAccess' => 1,
+ ],
+ 'tags' => [
+ 'propertyName' => 'tags',
+ 'type' => 'object',
+ 'multiple' => true,
+ 'propertiesMetadata' => [
+ 'tagname' => [
+ 'propertyName' => 'tagName',
+ 'type' => 'text',
+ 'propertyAccess' => 1,
+ ],
+ ],
+ 'className' => ObjTag::class,
+ 'propertyAccess' => 1,
+ ],
+ ],
+ 'className' => ObjCategory::class,
+ 'propertyAccess' => 1,
+ ],
+ 'related_categories' => [
+ 'propertyName' => 'relatedCategories',
+ 'type' => 'object',
+ 'multiple' => true,
+ 'propertiesMetadata' => [
+ 'id' => [
+ 'propertyName' => 'id',
+ 'type' => 'integer',
+ 'propertyAccess' => 1,
+ ],
+ 'title' => [
+ 'propertyName' => 'title',
+ 'type' => 'keyword',
+ 'propertyAccess' => 1,
+ ],
+ 'tags' => [
+ 'propertyName' => 'tags',
+ 'type' => 'object',
+ 'multiple' => true,
+ 'propertiesMetadata' => [
+ 'tagname' => [
+ 'propertyName' => 'tagName',
+ 'type' => 'text',
+ 'propertyAccess' => 1,
+ ],
+ ],
+ 'className' => ObjTag::class,
+ 'propertyAccess' => 1,
+ ],
+ ],
+ 'className' => ObjCategory::class,
+ 'propertyAccess' => 1,
+ ],
+ 'price' => [
+ 'propertyName' => 'price',
+ 'type' => 'float',
+ 'propertyAccess' => 1,
+ ],
+ 'location' => [
+ 'propertyName' => 'location',
+ 'type' => 'geo_point',
+ 'propertyAccess' => 1,
+ ],
+ 'limited' => [
+ 'propertyName' => 'limited',
+ 'type' => 'boolean',
+ 'propertyAccess' => 1,
+ ],
+ 'released' => [
+ 'propertyName' => 'released',
+ 'type' => 'date',
+ 'propertyAccess' => 1,
+ ],
+ 'ml_info' => [
+ 'propertyName' => 'mlInfo',
+ 'type' => 'text',
+ 'multilanguage' => true,
+ 'propertyAccess' => 1,
+ ],
+ 'ml_more_info' => [
+ 'propertyName' => 'mlMoreInfo',
+ 'type' => 'text',
+ 'multilanguage' => true,
+ 'propertyAccess' => 1,
+ ],
+ 'pieces_count' => [
+ 'propertyName' => 'tokenPiecesCount',
+ 'type' => 'text',
+ 'propertyAccess' => 1,
+ ],
+ '_id' => [
+ 'propertyName' => 'id',
+ 'type' => 'keyword',
+ 'propertyAccess' => 1,
+ ],
+ '_score' => [
+ 'propertyName' => 'score',
+ 'type' => 'float',
+ 'propertyAccess' => 1,
+ ],
+ ],
+ 'repositoryClass' => ProductRepository::class,
+ 'providerClass' => null,
+ 'className' => Product::class,
+ ];
+
+ $this->assertEquals($expected, $res);
+ }
+}
diff --git a/tests/Functional/Mapping/DocumentMetadataCollectorTest.php b/tests/Functional/Mapping/DocumentMetadataCollectorTest.php
index db153c3..6c3ca26 100644
--- a/tests/Functional/Mapping/DocumentMetadataCollectorTest.php
+++ b/tests/Functional/Mapping/DocumentMetadataCollectorTest.php
@@ -3,6 +3,7 @@
namespace Sineflow\ElasticsearchBundle\Tests\Functional\Mapping;
use Jchook\AssertThrows\AssertThrows;
+use Sineflow\ElasticsearchBundle\Mapping\DocumentAttributeParser;
use Sineflow\ElasticsearchBundle\Mapping\DocumentLocator;
use Sineflow\ElasticsearchBundle\Mapping\DocumentMetadata;
use Sineflow\ElasticsearchBundle\Mapping\DocumentMetadataCollector;
@@ -27,8 +28,8 @@ class DocumentMetadataCollectorTest extends AbstractContainerAwareTestCase
private DocumentMetadataCollector $metadataCollector;
private array $indexManagers;
private DocumentLocator $docLocator;
- private DocumentParser $docParser;
- private CacheInterface $cache;
+ private ?DocumentParser $docParser;
+ private DocumentAttributeParser $docAttributeParser;
private CacheInterface $nullCache;
/**
@@ -328,11 +329,18 @@ protected function setUp(): void
{
$this->indexManagers = $this->getContainer()->getParameter('sfes.indices');
$this->docLocator = $this->getContainer()->get(DocumentLocator::class);
- $this->docParser = $this->getContainer()->get(DocumentParser::class);
- $this->cache = $this->getContainer()->get('cache.system');
+ $this->docParser = $this->getContainer()->has(DocumentParser::class) ? $this->getContainer()->get(DocumentParser::class) : null;
+ $this->docAttributeParser = $this->getContainer()->get(DocumentAttributeParser::class);
+ $cache = $this->getContainer()->get('cache.system');
$this->nullCache = $this->getContainer()->get('app.null_cache_adapter');
- $this->metadataCollector = new DocumentMetadataCollector($this->indexManagers, $this->docLocator, $this->docParser, $this->cache);
+ $this->metadataCollector = new DocumentMetadataCollector(
+ $this->indexManagers,
+ $this->docLocator,
+ $this->docParser,
+ $this->docAttributeParser,
+ $cache
+ );
}
public function testGetDocumentMetadata(): void
@@ -355,7 +363,13 @@ public function testGetDocumentMetadata(): void
public function testMetadataWithCacheVsNoCache(): void
{
- $metadataCollectorWithCacheDisabled = new DocumentMetadataCollector($this->indexManagers, $this->docLocator, $this->docParser, $this->nullCache);
+ $metadataCollectorWithCacheDisabled = new DocumentMetadataCollector(
+ $this->indexManagers,
+ $this->docLocator,
+ $this->docParser,
+ $this->docAttributeParser,
+ $this->nullCache,
+ );
$this->assertEquals($this->metadataCollector->getDocumentMetadata('AcmeFooBundle:Customer'), $metadataCollectorWithCacheDisabled->getDocumentMetadata('AcmeFooBundle:Customer'));
$this->assertEquals($this->metadataCollector->getObjectPropertiesMetadata('AcmeFooBundle:Customer'), $metadataCollectorWithCacheDisabled->getObjectPropertiesMetadata('AcmeFooBundle:Customer'));
}
@@ -388,10 +402,10 @@ public function testGetObjectPropertiesMetadataWithValidClasses(): void
public function testGetDocumentClassIndex(): void
{
$docClassIndex = $this->metadataCollector->getDocumentClassIndex('AcmeBarBundle:Product');
- $this->assertEquals('bar', $docClassIndex);
+ $this->assertSame('bar', $docClassIndex);
$docClassIndex = $this->metadataCollector->getDocumentClassIndex(Product::class);
- $this->assertEquals('bar', $docClassIndex);
+ $this->assertSame('bar', $docClassIndex);
$this->assertThrows(\InvalidArgumentException::class, function (): void {
$this->metadataCollector->getDocumentClassIndex('Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document\NonExistingClass');
diff --git a/tests/Functional/Mapping/DocumentParserTest.php b/tests/Functional/Mapping/DocumentParserTest.php
index 23058ab..c33cc89 100644
--- a/tests/Functional/Mapping/DocumentParserTest.php
+++ b/tests/Functional/Mapping/DocumentParserTest.php
@@ -11,7 +11,9 @@
use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document\ObjTag;
use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document\Product;
use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document\Repository\ProductRepository;
+use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document\Customer;
use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Document\EntityWithInvalidEnum;
+use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\FooBundle\Enum\CustomerTypeEnum;
class DocumentParserTest extends AbstractContainerAwareTestCase
{
@@ -19,6 +21,10 @@ class DocumentParserTest extends AbstractContainerAwareTestCase
protected function setUp(): void
{
+ if (!class_exists(AnnotationReader::class)) {
+ $this->markTestSkipped('doctrine/annotations is not installed, skipping DocumentParser tests.');
+ }
+
$reader = new AnnotationReader();
$locator = $this->getContainer()->get(DocumentLocator::class);
$separator = $this->getContainer()->getParameter('sfes.mlproperty.language_separator');
@@ -31,7 +37,14 @@ public function testParseNonDocument(): void
$reflection = new \ReflectionClass(ObjCategory::class);
$res = $this->documentParser->parse($reflection, []);
- $this->assertEquals([], $res);
+ $this->assertSame([], $res);
+ }
+
+ public function testParseDocumentWithEnumProperty()
+ {
+ $reflection = new \ReflectionClass(Customer::class);
+ $res = $this->documentParser->parse($reflection, []);
+ $this->assertSame(CustomerTypeEnum::class, $res['propertiesMetadata']['customer_type']['enumType']);
}
public function testParseDocumentWithInvalidEnumFieldProperty()
diff --git a/tests/Functional/Profiler/ProfilerDataCollectorTest.php b/tests/Functional/Profiler/ProfilerDataCollectorTest.php
index 73f744a..02d2b60 100644
--- a/tests/Functional/Profiler/ProfilerDataCollectorTest.php
+++ b/tests/Functional/Profiler/ProfilerDataCollectorTest.php
@@ -50,7 +50,7 @@ public function testGetQueryCount(): void
// 1. DELETE query to remove existing index,
// 2. Internal call to GET /_aliases to check if index/alias already exists
// 3. PUT request to create index
- $this->assertEquals(3, $this->getCollector()->getQueryCount());
+ $this->assertSame(3, $this->getCollector()->getQueryCount());
$product = new Product();
$product->title = 'tuna';
@@ -61,7 +61,7 @@ public function testGetQueryCount(): void
// 1. GET /sineflow-esb-test-bar/_alias to check if more than one index should be written to
// 2. POST bulk request to enter the data
// 3. GET /_refresh, because of the $forceRefresh param of ->commit()
- $this->assertEquals(6, $this->getCollector()->getQueryCount());
+ $this->assertSame(6, $this->getCollector()->getQueryCount());
}
/**
diff --git a/tests/Functional/Result/DocumentConverterTest.php b/tests/Functional/Result/DocumentConverterTest.php
index e501231..c4e55c3 100644
--- a/tests/Functional/Result/DocumentConverterTest.php
+++ b/tests/Functional/Result/DocumentConverterTest.php
@@ -3,6 +3,7 @@
namespace Sineflow\ElasticsearchBundle\Tests\Functional\Result;
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
+use PHPUnit\Framework\Attributes\Depends;
use Sineflow\ElasticsearchBundle\Document\MLProperty;
use Sineflow\ElasticsearchBundle\Exception\DocumentConversionException;
use Sineflow\ElasticsearchBundle\Mapping\DocumentMetadataCollector;
@@ -21,10 +22,12 @@ class DocumentConverterTest extends AbstractContainerAwareTestCase
'title' => 'Foo Product',
'category' => [
'title' => 'Bar',
+ 'tags' => [],
],
'related_categories' => [
[
'title' => 'Acme',
+ 'tags' => [],
],
],
'ml_info-en' => 'info in English',
@@ -55,7 +58,7 @@ public function testAssignArrayToObjectWithNestedSingleValueInsteadOfArray(): vo
);
$category = $result->relatedCategories->current();
- $this->assertEquals($category->id, 123);
+ $this->assertSame(123, $category->id);
}
public function testAssignArrayToObjectWithNestedSingleValueArrayInsteadOfSingleValue(): void
@@ -81,7 +84,7 @@ public function testAssignArrayToObjectWithNestedSingleValueArrayInsteadOfSingle
$metadataCollector->getDocumentMetadata('AcmeBarBundle:Product')->getPropertiesMetadata()
);
- $this->assertEquals($result->category->id, 123);
+ $this->assertSame(123, $result->category->id);
}
public function testAssignArrayToObjectWithNestedMultiValueArrayInsteadOfSingleValue(): void
@@ -132,13 +135,13 @@ public function testAssignArrayToObjectWithAllFieldsCorrectlySet()
$this->assertSame($product, $result);
- $this->assertEquals('Foo Product', $product->title);
- $this->assertEquals('doc1', $product->id);
+ $this->assertSame('Foo Product', $product->title);
+ $this->assertSame('doc1', $product->id);
$this->assertInstanceOf(ObjCategory::class, $product->category);
$this->assertContainsOnlyInstancesOf(ObjCategory::class, $product->relatedCategories);
$this->assertInstanceOf(MLProperty::class, $product->mlInfo);
- $this->assertEquals('info in English', $product->mlInfo->getValue('en'));
- $this->assertEquals('info in French', $product->mlInfo->getValue('fr'));
+ $this->assertSame('info in English', $product->mlInfo->getValue('en'));
+ $this->assertSame('info in French', $product->mlInfo->getValue('fr'));
return $product;
}
@@ -154,11 +157,11 @@ public function testAssignArrayToObjectWithEmptyFields(): void
$converter->assignArrayToObject(
$rawDoc,
$product,
- $metadataCollector->getDocumentMetadata('AcmeBarBundle:Product')->getPropertiesMetadata()
+ $metadataCollector->getDocumentMetadata(Product::class)->getPropertiesMetadata()
);
$this->assertNull($product->title);
$this->assertNull($product->category);
- $this->assertNull($product->relatedCategories);
+ $this->assertSame([], $product->relatedCategories);
$this->assertNull($product->mlInfo);
}
@@ -175,15 +178,13 @@ public function testAssignArrayToObjectWithEmptyMultipleNestedField(): void
$converter->assignArrayToObject(
$rawDoc,
$product,
- $metadataCollector->getDocumentMetadata('AcmeBarBundle:Product')->getPropertiesMetadata()
+ $metadataCollector->getDocumentMetadata(Product::class)->getPropertiesMetadata()
);
$this->assertInstanceOf(ObjectIterator::class, $product->relatedCategories);
$this->assertSame(0, $product->relatedCategories->count());
}
- /**
- * @depends testAssignArrayToObjectWithAllFieldsCorrectlySet
- */
+ #[Depends('testAssignArrayToObjectWithAllFieldsCorrectlySet')]
public function testConvertToArray(Product $product): void
{
$converter = $this->getContainer()->get(DocumentConverter::class);
@@ -221,13 +222,13 @@ public function testConvertToDocumentWithSource(): void
/** @var Product $product */
$product = $converter->convertToDocument($rawFromEs, 'AcmeBarBundle:Product');
- $this->assertEquals('Foo Product', $product->title);
- $this->assertEquals('doc1', $product->id);
+ $this->assertSame('Foo Product', $product->title);
+ $this->assertSame('doc1', $product->id);
$this->assertInstanceOf(ObjCategory::class, $product->category);
$this->assertContainsOnlyInstancesOf(ObjCategory::class, $product->relatedCategories);
$this->assertInstanceOf(MLProperty::class, $product->mlInfo);
- $this->assertEquals('info in English', $product->mlInfo->getValue('en'));
- $this->assertEquals('info in French', $product->mlInfo->getValue('fr'));
+ $this->assertSame('info in English', $product->mlInfo->getValue('en'));
+ $this->assertSame('info in French', $product->mlInfo->getValue('fr'));
}
public function testConvertToDocumentWithFields(): void
@@ -259,9 +260,9 @@ public function testConvertToDocumentWithFields(): void
/** @var Product $product */
$product = $converter->convertToDocument($rawFromEs, 'AcmeBarBundle:Product');
- $this->assertEquals('Foo Product', $product->title);
- $this->assertEquals('doc1', $product->id);
+ $this->assertSame('Foo Product', $product->title);
+ $this->assertSame('doc1', $product->id);
$this->assertInstanceOf(MLProperty::class, $product->mlInfo);
- $this->assertEquals('info in English', $product->mlInfo->getValue('en'));
+ $this->assertSame('info in English', $product->mlInfo->getValue('en'));
}
}
diff --git a/tests/Functional/Result/DocumentIteratorTest.php b/tests/Functional/Result/DocumentIteratorTest.php
index 4cf6783..7bc2fc0 100644
--- a/tests/Functional/Result/DocumentIteratorTest.php
+++ b/tests/Functional/Result/DocumentIteratorTest.php
@@ -89,7 +89,7 @@ public function testIteration(): void
$this->assertCount(3, $iterator);
- $this->assertEquals(4, $iterator->getTotalCount());
+ $this->assertSame(4, $iterator->getTotalCount());
$iteration = 0;
/** @var Product $document */
@@ -105,9 +105,7 @@ public function testIteration(): void
$this->assertInstanceOf(Product::class, $document);
$this->assertInstanceOf(ObjectIterator::class, $categories);
- foreach ($categories as $category) {
- $this->assertInstanceOf(ObjCategory::class, $category);
- }
+ $this->assertContainsOnlyInstancesOf(ObjCategory::class, $categories);
++$iteration;
}
@@ -134,13 +132,13 @@ public function testManualIteration(): void
'3rd Product',
];
while ($iterator->valid()) {
- $this->assertEquals($i, $iterator->key());
+ $this->assertSame($i, $iterator->key());
$this->assertEquals($expected[$i], $iterator->current()->title);
$iterator->next();
++$i;
}
$iterator->rewind();
- $this->assertEquals($expected[0], $iterator->current()->title);
+ $this->assertSame($expected[0], $iterator->current()->title);
}
/**
@@ -157,7 +155,7 @@ public function testCurrentWithEmptyIterator(): void
}
/**
- * Make sure null is returned when field doesn't exist or is empty and ObjectIterator otherwise
+ * Make sure the default value is returned when field doesn't exist or is empty and ObjectIterator otherwise
*/
public function testNestedObjectIterator(): void
{
@@ -172,7 +170,7 @@ public function testNestedObjectIterator(): void
$this->assertContains($product->id, ['1', '2', '3', '54321']);
switch ($product->id) {
case '54321':
- $this->assertNull($product->relatedCategories);
+ $this->assertSame([], $product->relatedCategories);
break;
case '3':
$this->assertInstanceOf(ObjectIterator::class, $product->relatedCategories);
diff --git a/tests/Functional/Subscriber/EntityTrackerSubscriberTest.php b/tests/Functional/Subscriber/EntityTrackerSubscriberTest.php
index 9b658bd..5ecbd68 100644
--- a/tests/Functional/Subscriber/EntityTrackerSubscriberTest.php
+++ b/tests/Functional/Subscriber/EntityTrackerSubscriberTest.php
@@ -114,7 +114,7 @@ public function testPersistWithSeveralBulkOps(): void
$this->assertNull($rawCustomer->id);
$this->assertNull($customer->id);
$this->assertNull($secondRawCustomer->id);
- $this->assertEquals('555', $secondCustomer->id);
+ $this->assertSame('555', $secondCustomer->id);
$imWithAliases->getConnection()->commit();
$backupIm->getConnection()->commit();
@@ -122,8 +122,8 @@ public function testPersistWithSeveralBulkOps(): void
$this->assertNull($rawCustomer->id, 'id should not have been set');
$this->assertNotNull($customer->id, 'id should have been set');
$this->assertNull($secondRawCustomer->id, 'id should not have been set');
- $this->assertEquals('555', $secondCustomer->id);
- $this->assertEquals(123, $log->id);
+ $this->assertSame('555', $secondCustomer->id);
+ $this->assertSame('123', $log->id);
// Get the customer from ES by name
$finder = $this->getContainer()->get(Finder::class);
diff --git a/tests/Unit/Annotation/DocumentTest.php b/tests/Unit/Annotation/DocumentTest.php
index 51521d5..f74630e 100644
--- a/tests/Unit/Annotation/DocumentTest.php
+++ b/tests/Unit/Annotation/DocumentTest.php
@@ -23,7 +23,7 @@ public function testDump(): void
'foo' => 'bar',
];
- $this->assertEquals(
+ $this->assertSame(
[
'dynamic' => 'strict',
'foo' => 'bar',
diff --git a/tests/Unit/Annotation/PropertyTest.php b/tests/Unit/Annotation/PropertyTest.php
index ae140a1..b25ceaf 100644
--- a/tests/Unit/Annotation/PropertyTest.php
+++ b/tests/Unit/Annotation/PropertyTest.php
@@ -29,11 +29,11 @@ public function testDump(): void
];
$type->enumType = 'bar';
- $this->assertEquals(
+ $this->assertSame(
[
+ 'type' => 'mytype',
'analyzer' => 'standard',
'foo' => 'bar',
- 'type' => 'mytype',
],
$type->dump(),
'Properties should be filtered'
@@ -74,7 +74,7 @@ public function testDumpML(): void
],
];
- $this->assertEquals(
+ $this->assertSame(
[
'copy_to' => 'en_all',
'analyzer' => 'en_analyzer',
diff --git a/tests/Unit/DTO/BulkQueryItemTest.php b/tests/Unit/DTO/BulkQueryItemTest.php
index 03f0161..a7d28c2 100644
--- a/tests/Unit/DTO/BulkQueryItemTest.php
+++ b/tests/Unit/DTO/BulkQueryItemTest.php
@@ -2,6 +2,7 @@
namespace Sineflow\ElasticsearchBundle\Tests\Unit\DTO;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Sineflow\ElasticsearchBundle\DTO\BulkQueryItem;
@@ -13,57 +14,52 @@ class BulkQueryItemTest extends TestCase
/**
* @return array
*/
- public function getLinesProvider()
+ public static function getLinesProvider(): \Iterator
{
- return [
+ yield [
+ ['index', 'myindex', ['_id' => '3', 'foo' => 'bar'], false],
[
- ['index', 'myindex', ['_id' => '3', 'foo' => 'bar'], false],
[
- [
- 'index' => [
- '_index' => 'myindex',
- '_id' => 3,
- ],
- ],
- [
- 'foo' => 'bar',
+ 'index' => [
+ '_index' => 'myindex',
+ '_id' => 3,
],
],
+ [
+ 'foo' => 'bar',
+ ],
],
-
+ ];
+ yield [
+ ['create', 'myindex', [], false],
[
- ['create', 'myindex', [], false],
[
- [
- 'create' => [
- '_index' => 'myindex',
- ],
+ 'create' => [
+ '_index' => 'myindex',
],
- [],
],
+ [],
],
-
+ ];
+ yield [
+ ['update', 'myindex', ['_id' => '3'], 'forcedindex'],
[
- ['update', 'myindex', ['_id' => '3'], 'forcedindex'],
[
- [
- 'update' => [
- '_index' => 'forcedindex',
- '_id' => 3,
- ],
+ 'update' => [
+ '_index' => 'forcedindex',
+ '_id' => 3,
],
- [],
],
+ [],
],
-
+ ];
+ yield [
+ ['delete', 'myindex', ['_id' => '3'], false],
[
- ['delete', 'myindex', ['_id' => '3'], false],
[
- [
- 'delete' => [
- '_index' => 'myindex',
- '_id' => 3,
- ],
+ 'delete' => [
+ '_index' => 'myindex',
+ '_id' => 3,
],
],
],
@@ -73,9 +69,8 @@ public function getLinesProvider()
/**
* @param array $input
* @param array $expected
- *
- * @dataProvider getLinesProvider
*/
+ #[DataProvider('getLinesProvider')]
public function testGetLines($input, $expected): void
{
$bqi = new BulkQueryItem($input[0], $input[1], $input[2]);
diff --git a/tests/Unit/DTO/IndicesToDocumentClassesTest.php b/tests/Unit/DTO/IndicesToDocumentClassesTest.php
index d8f56c6..cb4af39 100644
--- a/tests/Unit/DTO/IndicesToDocumentClassesTest.php
+++ b/tests/Unit/DTO/IndicesToDocumentClassesTest.php
@@ -18,7 +18,7 @@ public function testGetSet(): void
$obj = new IndicesToDocumentClasses();
$obj->set('my_real_index', 'App:Entity');
- $this->assertEquals('App:Entity', $obj->get('my_real_index'));
+ $this->assertSame('App:Entity', $obj->get('my_real_index'));
$this->assertThrows(\InvalidArgumentException::class, static function () use ($obj): void {
$obj->set(null, 'App:Entity');
@@ -31,8 +31,8 @@ public function testGetSet(): void
$obj = new IndicesToDocumentClasses();
$obj->set(null, 'App:Entity');
- $this->assertEquals('App:Entity', $obj->get('second_real_index'));
- $this->assertEquals('App:Entity', $obj->get('non_existing_index'));
+ $this->assertSame('App:Entity', $obj->get('second_real_index'));
+ $this->assertSame('App:Entity', $obj->get('non_existing_index'));
$this->assertThrows(\InvalidArgumentException::class, static function () use ($obj): void {
$obj->set('my_real_index', 'App:Entity');
diff --git a/tests/Unit/DependencyInjection/Compiler/AddIndexManagersPassTest.php b/tests/Unit/DependencyInjection/Compiler/AddIndexManagersPassTest.php
index 3aaa2ba..b7d844c 100644
--- a/tests/Unit/DependencyInjection/Compiler/AddIndexManagersPassTest.php
+++ b/tests/Unit/DependencyInjection/Compiler/AddIndexManagersPassTest.php
@@ -60,14 +60,20 @@ public function testProcessWithSeveralManagers(): void
default => null,
}
);
+ $matcher = $this->exactly(1);
$containerMock
- ->expects($this->exactly(1))
+ ->expects($matcher)
->method('setDefinition')
- ->withConsecutive(
- [$this->equalTo('sfes.index.test')]
- )
- ->willReturn(new Definition());
+ ->willReturnCallback(
+ function (...$parameters) use ($matcher) {
+ if (1 === $matcher->numberOfInvocations()) {
+ $this->assertSame('sfes.index.test', $parameters[0]);
+ }
+
+ return new Definition();
+ }
+ );
$imPrototypeDefinitionMock = $this->getMockBuilder(Definition::class)
->getMock();
diff --git a/tests/Unit/DependencyInjection/ElasticsearchExtensionTest.php b/tests/Unit/DependencyInjection/ElasticsearchExtensionTest.php
index 33fd800..9c6079d 100644
--- a/tests/Unit/DependencyInjection/ElasticsearchExtensionTest.php
+++ b/tests/Unit/DependencyInjection/ElasticsearchExtensionTest.php
@@ -2,12 +2,14 @@
namespace Sineflow\ElasticsearchBundle\Tests\Unit\DependencyInjection;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Sineflow\ElasticsearchBundle\DependencyInjection\SineflowElasticsearchExtension;
use Sineflow\ElasticsearchBundle\Document\Provider\ProviderRegistry;
use Sineflow\ElasticsearchBundle\Finder\Finder;
use Sineflow\ElasticsearchBundle\Manager\ConnectionManagerRegistry;
use Sineflow\ElasticsearchBundle\Manager\IndexManagerRegistry;
+use Sineflow\ElasticsearchBundle\Mapping\DocumentAttributeParser;
use Sineflow\ElasticsearchBundle\Mapping\DocumentLocator;
use Sineflow\ElasticsearchBundle\Mapping\DocumentMetadataCollector;
use Sineflow\ElasticsearchBundle\Mapping\DocumentParser;
@@ -25,10 +27,12 @@ class ElasticsearchExtensionTest extends TestCase
/**
* @return array
*/
- public function getData()
+ public static function getData()
{
$parameters = [
'sineflow_elasticsearch' => [
+ 'use_annotations' => false,
+
'entity_locations' => [
'AcmeBarBundle' => [
'directory' => 'tests/App/fixture/Acme/BarBundle/Document',
@@ -168,9 +172,8 @@ public function getData()
* @param array $expectedEntityLocations
* @param array $expectedConnections
* @param array $expectedManagers
- *
- * @dataProvider getData
*/
+ #[DataProvider('getData')]
public function testLoad($parameters, $expectedEntityLocations, $expectedConnections, $expectedManagers): void
{
$container = new ContainerBuilder();
@@ -209,6 +212,7 @@ public function testLoad($parameters, $expectedEntityLocations, $expectedConnect
Finder::class,
DocumentLocator::class,
DocumentParser::class,
+ DocumentAttributeParser::class,
DocumentMetadataCollector::class,
ProfilerDataCollector::class,
KnpPaginateQuerySubscriber::class,
diff --git a/tests/Unit/Document/MLPropertyTest.php b/tests/Unit/Document/MLPropertyTest.php
index 96010dc..951b3e0 100644
--- a/tests/Unit/Document/MLPropertyTest.php
+++ b/tests/Unit/Document/MLPropertyTest.php
@@ -25,19 +25,19 @@ public function testGetSetValue(): void
$mlProperty->setValue('test default', 'default');
- $this->assertEquals(
+ $this->assertSame(
'test en',
$mlProperty->getValue('en'),
'MLProperty does not return required language correctly.'
);
- $this->assertEquals(
+ $this->assertSame(
'test default',
$mlProperty->getValue('default'),
'MLProperty does not return default language correctly.'
);
- $this->assertEquals(
+ $this->assertSame(
'test default',
$mlProperty->getValue('bg'),
'MLProperty does not return default language if required language is missing.'
@@ -54,7 +54,7 @@ public function testGetValues(): void
$mlProperty->setValue('test en', 'en');
$mlProperty->setValue('test bg', 'bg');
- $this->assertEquals(
+ $this->assertSame(
[
'default' => 'test default',
'en' => 'test en',
@@ -76,7 +76,7 @@ public function testConstruct(): void
'bg' => 'test bg',
]);
- $this->assertEquals(
+ $this->assertSame(
[
'default' => 'test default',
'en' => 'test en',
diff --git a/tests/Unit/Mapping/CaserTest.php b/tests/Unit/Mapping/CaserTest.php
index c723179..18834ab 100644
--- a/tests/Unit/Mapping/CaserTest.php
+++ b/tests/Unit/Mapping/CaserTest.php
@@ -2,12 +2,13 @@
namespace Sineflow\ElasticsearchBundle\Tests\Unit\Mapping;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Sineflow\ElasticsearchBundle\Mapping\Caser;
class CaserTest extends TestCase
{
- public function providerForCamel(): array
+ public static function providerForCamel(): array
{
$out = [
['foo_bar', 'fooBar'],
@@ -20,7 +21,7 @@ public function providerForCamel(): array
return $out;
}
- public function providerForSnake(): array
+ public static function providerForSnake(): array
{
$out = [
['FooBar', 'foo_bar'],
@@ -35,22 +36,20 @@ public function providerForSnake(): array
/**
* @param string $input
* @param string $expected
- *
- * @dataProvider providerForCamel
*/
+ #[DataProvider('providerForCamel')]
public function testCamel($input, $expected): void
{
- $this->assertEquals($expected, Caser::camel($input));
+ $this->assertSame($expected, Caser::camel($input));
}
/**
* @param string $input
* @param string $expected
- *
- * @dataProvider providerForSnake
*/
+ #[DataProvider('providerForSnake')]
public function testSnake($input, $expected): void
{
- $this->assertEquals($expected, Caser::snake($input));
+ $this->assertSame($expected, Caser::snake($input));
}
}
diff --git a/tests/Unit/Mapping/DocumentLocatorTest.php b/tests/Unit/Mapping/DocumentLocatorTest.php
index 5e5ee18..d0f544a 100644
--- a/tests/Unit/Mapping/DocumentLocatorTest.php
+++ b/tests/Unit/Mapping/DocumentLocatorTest.php
@@ -2,6 +2,7 @@
namespace Sineflow\ElasticsearchBundle\Tests\Unit\Mapping;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Sineflow\ElasticsearchBundle\Mapping\DocumentLocator;
use Sineflow\ElasticsearchBundle\Tests\App\Fixture\Acme\BarBundle\Document\Product;
@@ -33,7 +34,7 @@ protected function setUp(): void
/**
* Data provider
*/
- public function getTestResolveClassNameDataProvider(): array
+ public static function getTestResolveClassNameDataProvider(): array
{
$out = [
[
@@ -109,11 +110,10 @@ public function testGetAllDocumentDirs(): void
*
* @param string $className
* @param string $expectedClassName
- *
- * @dataProvider getTestResolveClassNameDataProvider
*/
+ #[DataProvider('getTestResolveClassNameDataProvider')]
public function testResolveClassName($className, $expectedClassName): void
{
- $this->assertEquals($expectedClassName, $this->locator->resolveClassName($className));
+ $this->assertSame($expectedClassName, $this->locator->resolveClassName($className));
}
}