/**
  * Finds properties' metadata for every property used in document or inner/nested object
  *
  * @param \ReflectionClass $documentReflection
  *
  * @return array
  */
 public function getPropertiesMetadata(\ReflectionClass $documentReflection)
 {
     $className = $documentReflection->getName();
     if (array_key_exists($className, $this->propertiesMetadata)) {
         return $this->propertiesMetadata[$className];
     }
     $propertyMetadata = [];
     /** @var \ReflectionProperty $property */
     foreach ($this->getDocumentPropertiesReflection($documentReflection) as $propertyName => $property) {
         $propertyAnnotation = $this->getPropertyAnnotationData($property);
         $propertyAnnotation = $propertyAnnotation ?: $this->reader->getPropertyAnnotation($property, Id::class);
         $propertyAnnotation = $propertyAnnotation ?: $this->reader->getPropertyAnnotation($property, ParentId::class);
         $propertyAnnotation = $propertyAnnotation ?: $this->reader->getPropertyAnnotation($property, Score::class);
         // Ignore class properties without any recognized annotation
         if ($propertyAnnotation === null) {
             continue;
         }
         switch (get_class($propertyAnnotation)) {
             case Property::class:
                 $propertyMetadata[$propertyAnnotation->name] = ['propertyName' => $propertyName, 'type' => $propertyAnnotation->type, 'multilanguage' => $propertyAnnotation->multilanguage];
                 // If property is a (nested) object
                 if (in_array($propertyAnnotation->type, ['object', 'nested'])) {
                     if (!$propertyAnnotation->objectName) {
                         throw new \InvalidArgumentException(sprintf('Property "%s" in %s is missing "objectName" setting', $propertyName, $className));
                     }
                     $child = new \ReflectionClass($this->documentLocator->resolveClassName($propertyAnnotation->objectName));
                     $propertyMetadata[$propertyAnnotation->name] = array_merge($propertyMetadata[$propertyAnnotation->name], ['multiple' => $propertyAnnotation->multiple, 'propertiesMetadata' => $this->getPropertiesMetadata($child), 'className' => $child->getName()]);
                 }
                 break;
             case Id::class:
                 $propertyAnnotation->name = '_id';
                 $propertyAnnotation->type = 'string';
                 $propertyMetadata[$propertyAnnotation->name] = ['propertyName' => $propertyName, 'type' => $propertyAnnotation->type];
                 break;
             case ParentId::class:
                 $propertyAnnotation->name = '_parent';
                 $propertyAnnotation->type = 'string';
                 $propertyMetadata[$propertyAnnotation->name] = ['propertyName' => $propertyName, 'type' => $propertyAnnotation->type];
                 break;
             case Score::class:
                 $propertyAnnotation->name = '_score';
                 $propertyAnnotation->type = 'float';
                 $propertyMetadata[$propertyAnnotation->name] = ['propertyName' => $propertyName, 'type' => $propertyAnnotation->type];
                 break;
         }
         if ($property->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 ($propertyAnnotation->type === 'boolean' && !$documentReflection->hasMethod($getterMethod)) {
                 $getterMethod = 'is' . $camelCaseName;
             }
             if ($documentReflection->hasMethod($getterMethod) && $documentReflection->hasMethod($setterMethod)) {
                 $propertyMetadata[$propertyAnnotation->name]['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[$propertyAnnotation->name]['propertyAccess'] = $propertyAccess;
     }
     $this->propertiesMetadata[$className] = $propertyMetadata;
     return $this->propertiesMetadata[$className];
 }
 /**
  * @param string $input
  * @param string $expected
  * @dataProvider providerForSnake
  */
 public function testSnake($input, $expected)
 {
     $this->assertEquals($expected, Caser::snake($input));
 }