/**
  * @param string $term
  * @return string
  */
 public function autocompleteAction($term)
 {
     $searchProperty = $this->widgetConfiguration['searchProperty'];
     /** @var $queryResult QueryResultInterface */
     $queryResult = $this->widgetConfiguration['objects'];
     $query = clone $queryResult->getQuery();
     $constraint = $query->getConstraint();
     if ($constraint !== null) {
         $query->matching($query->logicalAnd($constraint, $query->like($searchProperty, '%' . $term . '%', false)));
     } else {
         $query->matching($query->like($searchProperty, '%' . $term . '%', false));
     }
     if (isset($this->configuration['limit'])) {
         $query->setLimit((int) $this->configuration['limit']);
     }
     $results = $query->execute();
     $output = array();
     $values = array();
     foreach ($results as $singleResult) {
         $val = ObjectAccess::getPropertyPath($singleResult, $searchProperty);
         if (isset($values[$val])) {
             continue;
         }
         $values[$val] = true;
         $output[] = array('id' => $val, 'label' => $val, 'value' => $val);
     }
     return json_encode($output);
 }
 /**
  * The input is assumed to be an array or Collection of objects. Groups this input by the $groupingKey property of each element.
  *
  * @param array|Collection $set
  * @param string $groupingKey
  * @return array
  */
 public function groupBy($set, $groupingKey)
 {
     $result = array();
     foreach ($set as $element) {
         $result[ObjectAccess::getPropertyPath($element, $groupingKey)][] = $element;
     }
     return $result;
 }
예제 #3
0
 /**
  * @return void
  */
 public function up()
 {
     $this->processConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, function (array &$configuration) {
         $presetsConfiguration = ObjectAccess::getPropertyPath($configuration, 'Neos.Form.presets');
         if (!is_array($presetsConfiguration)) {
             return;
         }
         $presetsConfiguration = $this->renameTranslationPackage($presetsConfiguration);
         $configuration['TYPO3']['Form']['presets'] = $presetsConfiguration;
     }, true);
 }
 /**
  * Renders the view
  *
  * @return string The rendered view
  * @api
  */
 public function render()
 {
     $source = $this->getOption('templateSource');
     $templatePathAndFilename = $this->getOption('templatePathAndFilename');
     if ($templatePathAndFilename !== null) {
         $source = file_get_contents($templatePathAndFilename);
     }
     return preg_replace_callback('/\\{([a-zA-Z0-9\\-_.]+)\\}/', function ($matches) {
         return ObjectAccess::getPropertyPath($this->variables, $matches[1]);
     }, $source);
 }
 /**
  * Updates the password credential from the POST vars, if the POST parameters
  * are available. Sets the authentication status to AUTHENTICATION_NEEDED, if credentials have been sent.
  *
  * Note: You need to send the password in this POST parameter:
  *       __authentication[TYPO3][Flow][Security][Authentication][Token][PasswordToken][password]
  *
  * @param ActionRequest $actionRequest The current action request
  * @return void
  */
 public function updateCredentials(ActionRequest $actionRequest)
 {
     if ($actionRequest->getHttpRequest()->getMethod() !== 'POST') {
         return;
     }
     $postArguments = $actionRequest->getInternalArguments();
     $password = ObjectAccess::getPropertyPath($postArguments, '__authentication.TYPO3.Flow.Security.Authentication.Token.PasswordToken.password');
     if (!empty($password)) {
         $this->credentials['password'] = $password;
         $this->setAuthenticationStatus(self::AUTHENTICATION_NEEDED);
     }
 }
 /**
  * Returns contents of Composer manifest - or part there of.
  *
  * @param string $manifestPath
  * @param string $configurationPath Optional. Only return the part of the manifest indexed by configurationPath
  * @return array|mixed
  */
 public static function getComposerManifest($manifestPath, $configurationPath = null)
 {
     $composerManifest = static::readComposerManifest($manifestPath);
     if ($composerManifest === null) {
         return null;
     }
     if ($configurationPath !== null) {
         return ObjectAccess::getPropertyPath($composerManifest, $configurationPath);
     } else {
         return $composerManifest;
     }
 }
 /**
  * {@inheritdoc}
  *
  * @param FlowQuery $flowQuery the FlowQuery object
  * @param array $arguments the property path to use (in index 0)
  * @return mixed
  */
 public function evaluate(FlowQuery $flowQuery, array $arguments)
 {
     if (!isset($arguments[0]) || empty($arguments[0])) {
         throw new FlowQueryException('property() must be given an attribute name when used on objects, fetching all attributes is not supported.', 1332492263);
     } else {
         $context = $flowQuery->getContext();
         if (!isset($context[0])) {
             return null;
         }
         $element = $context[0];
         $propertyPath = $arguments[0];
         return ObjectAccess::getPropertyPath($element, $propertyPath);
     }
 }
 /**
  * @test
  * @dataProvider attributeExamples
  */
 public function evaluateTests($properties, $expectedOutput)
 {
     $path = 'attributes/test';
     $this->mockTsRuntime->expects($this->any())->method('evaluate')->will($this->returnCallback(function ($evaluatePath, $that) use($path, $properties) {
         $relativePath = str_replace($path . '/', '', $evaluatePath);
         return ObjectAccess::getPropertyPath($properties, str_replace('/', '.', $relativePath));
     }));
     $typoScriptObjectName = 'Neos.Fusion:Attributes';
     $renderer = new AttributesImplementation($this->mockTsRuntime, $path, $typoScriptObjectName);
     if ($properties !== null) {
         foreach ($properties as $name => $value) {
             ObjectAccess::setProperty($renderer, $name, $value);
         }
     }
     $result = $renderer->evaluate();
     $this->assertEquals($expectedOutput, $result);
 }
 /**
  * {@inheritdoc}
  *
  * @param FlowQuery $flowQuery the FlowQuery object
  * @param array $arguments the arguments for this operation
  * @return mixed
  */
 public function evaluate(FlowQuery $flowQuery, array $arguments)
 {
     if (!isset($arguments[0]) || empty($arguments[0])) {
         throw new FlowQueryException('property() does not support returning all attributes yet', 1332492263);
     } else {
         $context = $flowQuery->getContext();
         $propertyPath = $arguments[0];
         if (!isset($context[0])) {
             return null;
         }
         $element = $context[0];
         if ($propertyPath[0] === '_') {
             return ObjectAccess::getPropertyPath($element, substr($propertyPath, 1));
         } else {
             return $element->getProperty($propertyPath);
         }
     }
 }
 /**
  * {@inheritdoc}
  *
  * First argument is the node property to sort by. Works with internal arguments (_xyz) as well.
  * Second argument is the sort direction (ASC or DESC).
  *
  * @param FlowQuery $flowQuery the FlowQuery object
  * @param array $arguments the arguments for this operation.
  * @return mixed
  */
 public function evaluate(FlowQuery $flowQuery, array $arguments)
 {
     $nodes = $flowQuery->getContext();
     // Check sort property
     if (isset($arguments[0]) && !empty($arguments[0])) {
         $sortProperty = $arguments[0];
     } else {
         throw new \Neos\Eel\FlowQuery\FlowQueryException('Please provide a node property to sort by.', 1467881104);
     }
     // Check sort direction
     if (isset($arguments[1]) && !empty($arguments[1]) && in_array(strtoupper($arguments[1]), ['ASC', 'DESC'])) {
         $sortOrder = strtoupper($arguments[1]);
     } else {
         throw new \Neos\Eel\FlowQuery\FlowQueryException('Please provide a valid sort direction (ASC or DESC)', 1467881105);
     }
     $sortedNodes = [];
     $sortSequence = [];
     $nodesByIdentifier = [];
     // Determine the property value to sort by
     /** @var Node $node */
     foreach ($nodes as $node) {
         if ($sortProperty[0] === '_') {
             $propertyValue = \Neos\Utility\ObjectAccess::getPropertyPath($node, substr($sortProperty, 1));
         } else {
             $propertyValue = $node->getProperty($sortProperty);
         }
         if ($propertyValue instanceof \DateTime) {
             $propertyValue = $propertyValue->getTimestamp();
         }
         $sortSequence[$node->getIdentifier()] = $propertyValue;
         $nodesByIdentifier[$node->getIdentifier()] = $node;
     }
     // Create the sort sequence
     if ($sortOrder === 'DESC') {
         arsort($sortSequence);
     } elseif ($sortOrder === 'ASC') {
         asort($sortSequence);
     }
     // Build the sorted context that is returned
     foreach ($sortSequence as $nodeIdentifier => $value) {
         $sortedNodes[] = $nodesByIdentifier[$nodeIdentifier];
     }
     $flowQuery->setContext($sortedNodes);
 }
 /**
  * @param array $commandOptions
  * @param array $adjustmentOptions
  * @param string $commandOptionPath
  * @param string $adjustmentOptionName
  * @return void
  */
 protected function transferOptionFromCommandToAdjustment(array $commandOptions, array &$adjustmentOptions, $commandOptionPath, $adjustmentOptionName)
 {
     $commandOptionValue = ObjectAccess::getPropertyPath($commandOptions, $commandOptionPath);
     if ($commandOptionValue !== null) {
         ObjectAccess::setProperty($adjustmentOptions, $adjustmentOptionName, $commandOptionValue);
     }
 }
 /**
  * Redirects directly to \Neos\Utility\ObjectAccess::getPropertyPath($result, $propertyPath)
  * This is only needed for unit tests!
  *
  * @param mixed $object The object to fetch the property from
  * @param string $path The path to the property to be fetched
  * @return mixed The property value
  */
 public function getObjectValueByPath($object, $path)
 {
     return ObjectAccess::getPropertyPath($object, $path);
 }
예제 #13
0
 /**
  * Read the option called $optionName from $this->options, and parse {...}
  * as object accessors.
  *
  * if $optionName was not found, the corresponding default option is returned (from $this->defaultOptions)
  *
  * @param string $optionName
  * @return mixed
  * @api
  */
 protected function parseOption($optionName)
 {
     if (!isset($this->options[$optionName]) || $this->options[$optionName] === '') {
         if (isset($this->defaultOptions[$optionName])) {
             $option = $this->defaultOptions[$optionName];
         } else {
             return null;
         }
     } else {
         $option = $this->options[$optionName];
     }
     if (!is_string($option)) {
         return $option;
     }
     $formRuntime = $this->finisherContext->getFormRuntime();
     return preg_replace_callback('/{([^}]+)}/', function ($match) use($formRuntime) {
         return \Neos\Utility\ObjectAccess::getPropertyPath($formRuntime, $match[1]);
     }, $option);
 }
 /**
  * Returns the route value of the current route part.
  * This method can be overridden by custom RoutePartHandlers to implement custom resolving mechanisms.
  *
  * @param array $routeValues An array with key/value pairs to be resolved by Dynamic Route Parts.
  * @return string|array value to resolve.
  * @api
  */
 protected function findValueToResolve(array $routeValues)
 {
     return ObjectAccess::getPropertyPath($routeValues, $this->name);
 }
 /**
  * @param string $path
  * @return mixed
  */
 public function getOptionByPath($path)
 {
     return ObjectAccess::getPropertyPath($this->options, $path);
 }
 /**
  * {@inheritdoc}
  *
  * @param object $element
  * @param string $propertyPath
  * @return mixed
  */
 protected function getPropertyPath($element, $propertyPath)
 {
     if ($propertyPath[0] === '_') {
         return ObjectAccess::getPropertyPath($element, substr($propertyPath, 1));
     } else {
         return $element->getProperty($propertyPath);
     }
 }
 /**
  * Evaluate a property path. This is outsourced to a single method
  * to make overriding this functionality easy.
  *
  * @param object $element
  * @param string $propertyPath
  * @return mixed
  */
 protected function getPropertyPath($element, $propertyPath)
 {
     return ObjectAccess::getPropertyPath($element, $propertyPath);
 }
예제 #18
0
 /**
  * returns the default value of the specified form element
  * or NULL if no default value was set
  *
  * @param string $elementIdentifier identifier of the form element. This supports property paths!
  * @return mixed The elements default value
  * @internal
  */
 public function getElementDefaultValueByIdentifier($elementIdentifier)
 {
     return \Neos\Utility\ObjectAccess::getPropertyPath($this->elementDefaultValues, $elementIdentifier);
 }
 /**
  * @test
  */
 public function getPropertyPathReturnsNullIfSubjectOnPathIsNoObject()
 {
     $object = new \stdClass();
     $object->foo = 'Hello World';
     $this->assertNull(ObjectAccess::getPropertyPath($object, 'foo.bar'));
 }
예제 #20
0
 /**
  * Iterates through all segments in $this->uriPattern and creates
  * appropriate RoutePart instances.
  *
  * @return void
  * @throws InvalidRoutePartHandlerException
  * @throws InvalidUriPatternException
  */
 public function parse()
 {
     if ($this->isParsed || $this->uriPattern === null || $this->uriPattern === '') {
         return;
     }
     $this->routeParts = [];
     $currentRoutePartIsOptional = false;
     if (substr($this->uriPattern, -1) === '/') {
         throw new InvalidUriPatternException('The URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" ends with a slash, which is not allowed. You can put the trailing slash in brackets to make it optional.', 1234782997);
     }
     if ($this->uriPattern[0] === '/') {
         throw new InvalidUriPatternException('The URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" starts with a slash, which is not allowed.', 1234782983);
     }
     $matches = [];
     preg_match_all(self::PATTERN_EXTRACTROUTEPARTS, $this->uriPattern, $matches, PREG_SET_ORDER);
     /** @var $lastRoutePart RoutePartInterface */
     $lastRoutePart = null;
     foreach ($matches as $match) {
         $routePartType = empty($match['dynamic']) ? self::ROUTEPART_TYPE_STATIC : self::ROUTEPART_TYPE_DYNAMIC;
         $routePartName = $match['content'];
         if (!empty($match['optionalStart'])) {
             if ($lastRoutePart !== null && $lastRoutePart->isOptional()) {
                 throw new InvalidUriPatternException('the URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" contains successive optional Route sections, which is not allowed.', 1234562050);
             }
             $currentRoutePartIsOptional = true;
         }
         $routePart = null;
         switch ($routePartType) {
             case self::ROUTEPART_TYPE_DYNAMIC:
                 if ($lastRoutePart instanceof DynamicRoutePartInterface) {
                     throw new InvalidUriPatternException('the URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" contains successive Dynamic Route Parts, which is not allowed.', 1218446975);
                 }
                 if (isset($this->routePartsConfiguration[$routePartName]['handler'])) {
                     $routePart = $this->objectManager->get($this->routePartsConfiguration[$routePartName]['handler']);
                     if (!$routePart instanceof DynamicRoutePartInterface) {
                         throw new InvalidRoutePartHandlerException(sprintf('routePart handlers must implement "%s" in route "%s"', DynamicRoutePartInterface::class, $this->getName()), 1218480972);
                     }
                 } elseif (isset($this->routePartsConfiguration[$routePartName]['objectType'])) {
                     $routePart = new IdentityRoutePart();
                     $routePart->setObjectType($this->routePartsConfiguration[$routePartName]['objectType']);
                     if (isset($this->routePartsConfiguration[$routePartName]['uriPattern'])) {
                         $routePart->setUriPattern($this->routePartsConfiguration[$routePartName]['uriPattern']);
                     }
                 } else {
                     $routePart = new DynamicRoutePart();
                 }
                 $routePartDefaultValue = ObjectAccess::getPropertyPath($this->defaults, $routePartName);
                 if ($routePartDefaultValue !== null) {
                     $routePart->setDefaultValue($routePartDefaultValue);
                 }
                 break;
             case self::ROUTEPART_TYPE_STATIC:
                 $routePart = new StaticRoutePart();
                 if ($lastRoutePart !== null && $lastRoutePart instanceof DynamicRoutePartInterface) {
                     /** @var DynamicRoutePartInterface $lastRoutePart */
                     $lastRoutePart->setSplitString($routePartName);
                 }
         }
         $routePart->setName($routePartName);
         if ($currentRoutePartIsOptional) {
             $routePart->setOptional(true);
             if ($routePart instanceof DynamicRoutePartInterface && !$routePart->hasDefaultValue()) {
                 throw new InvalidRouteSetupException(sprintf('There is no default value specified for the optional route part "{%s}" of route "%s", but all dynamic optional route parts need a default.', $routePartName, $this->getName()), 1477140679);
             }
         }
         $routePart->setLowerCase($this->lowerCase);
         if (isset($this->routePartsConfiguration[$routePartName]['options'])) {
             $routePart->setOptions($this->routePartsConfiguration[$routePartName]['options']);
         }
         if (isset($this->routePartsConfiguration[$routePartName]['toLowerCase'])) {
             $routePart->setLowerCase($this->routePartsConfiguration[$routePartName]['toLowerCase']);
         }
         $this->routeParts[] = $routePart;
         if (!empty($match['optionalEnd'])) {
             if (!$currentRoutePartIsOptional) {
                 throw new InvalidUriPatternException('The URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" contains an unopened optional section.', 1234564495);
             }
             $currentRoutePartIsOptional = false;
         }
         $lastRoutePart = $routePart;
     }
     if ($currentRoutePartIsOptional) {
         throw new InvalidUriPatternException('The URI pattern "' . $this->uriPattern . '" of route "' . $this->getName() . '" contains an unterminated optional section.', 1234563922);
     }
     $this->isParsed = true;
 }
 /**
  * Evaluate the property name filter by traversing to the child object. We only support
  * nested objects right now
  *
  * @param FlowQuery $query
  * @param string $propertyNameFilter
  * @return void
  */
 protected function evaluatePropertyNameFilter(FlowQuery $query, $propertyNameFilter)
 {
     $resultObjects = [];
     $resultObjectHashes = [];
     foreach ($query->getContext() as $element) {
         $subProperty = ObjectAccess::getPropertyPath($element, $propertyNameFilter);
         if (is_object($subProperty) || is_array($subProperty)) {
             if (is_array($subProperty) || $subProperty instanceof \Traversable) {
                 foreach ($subProperty as $childElement) {
                     if (!isset($resultObjectHashes[spl_object_hash($childElement)])) {
                         $resultObjectHashes[spl_object_hash($childElement)] = true;
                         $resultObjects[] = $childElement;
                     }
                 }
             } elseif (!isset($resultObjectHashes[spl_object_hash($subProperty)])) {
                 $resultObjectHashes[spl_object_hash($subProperty)] = true;
                 $resultObjects[] = $subProperty;
             }
         }
     }
     $query->setContext($resultObjects);
 }
 /**
  * @return array
  */
 protected function getRequireJsPathMapping()
 {
     $pathMappings = array();
     $validatorSettings = ObjectAccess::getPropertyPath($this->settings, 'userInterface.validators');
     if (is_array($validatorSettings)) {
         foreach ($validatorSettings as $validatorName => $validatorConfiguration) {
             if (isset($validatorConfiguration['path'])) {
                 $pathMappings[$validatorName] = $this->getStaticResourceWebBaseUri($validatorConfiguration['path']);
             }
         }
     }
     $editorSettings = ObjectAccess::getPropertyPath($this->settings, 'userInterface.inspector.editors');
     if (is_array($editorSettings)) {
         foreach ($editorSettings as $editorName => $editorConfiguration) {
             if (isset($editorConfiguration['path'])) {
                 $pathMappings[$editorName] = $this->getStaticResourceWebBaseUri($editorConfiguration['path']);
             }
         }
     }
     $requireJsPathMappingSettings = ObjectAccess::getPropertyPath($this->settings, 'userInterface.requireJsPathMapping');
     if (is_array($requireJsPathMappingSettings)) {
         foreach ($requireJsPathMappingSettings as $namespace => $path) {
             $pathMappings[$namespace] = $this->getStaticResourceWebBaseUri($path);
         }
     }
     return $pathMappings;
 }
 /**
  * Creates a URI representation (path segment) for the given object matching $this->uriPattern.
  *
  * @param mixed $object object of type $this->objectType
  * @return string URI representation (path segment) of the given object
  * @throws InvalidUriPatternException
  */
 protected function createPathSegmentForObject($object)
 {
     $matches = [];
     preg_match_all('/(?P<dynamic>{?)(?P<content>[^}{]+)}?/', $this->getUriPattern(), $matches, PREG_SET_ORDER);
     $pathSegment = '';
     foreach ($matches as $match) {
         if (empty($match['dynamic'])) {
             $pathSegment .= $match['content'];
         } else {
             $dynamicPathSegmentParts = explode(':', $match['content']);
             $propertyPath = $dynamicPathSegmentParts[0];
             $dynamicPathSegment = ObjectAccess::getPropertyPath($object, $propertyPath);
             if (is_object($dynamicPathSegment)) {
                 if ($dynamicPathSegment instanceof \DateTimeInterface) {
                     $dateFormat = isset($dynamicPathSegmentParts[1]) ? trim($dynamicPathSegmentParts[1]) : 'Y-m-d';
                     $pathSegment .= $this->rewriteForUri($dynamicPathSegment->format($dateFormat));
                 } else {
                     throw new InvalidUriPatternException(sprintf('Invalid uriPattern "%s" for route part "%s". Property "%s" must be of type string or \\DateTime. "%s" given.', $this->getUriPattern(), $this->getName(), $propertyPath, is_object($dynamicPathSegment) ? get_class($dynamicPathSegment) : gettype($dynamicPathSegment)), 1316442409);
                 }
             } else {
                 $pathSegment .= $this->rewriteForUri($dynamicPathSegment);
             }
         }
     }
     return $pathSegment;
 }
 /**
  * Get the current property of the object bound to this form.
  *
  * @return mixed Value
  */
 protected function getPropertyValue()
 {
     if (!$this->viewHelperVariableContainer->exists(\Neos\FluidAdaptor\ViewHelpers\FormViewHelper::class, 'formObject')) {
         return null;
     }
     $formObject = $this->viewHelperVariableContainer->get(\Neos\FluidAdaptor\ViewHelpers\FormViewHelper::class, 'formObject');
     $propertyNameOrPath = $this->arguments['property'];
     return ObjectAccess::getPropertyPath($formObject, $propertyNameOrPath);
 }
 /**
  * Get the option value for an object
  *
  * @param mixed $valueElement
  * @return string
  */
 protected function getOptionValueScalar($valueElement)
 {
     if (is_object($valueElement)) {
         if ($this->hasArgument('optionValueField')) {
             return ObjectAccess::getPropertyPath($valueElement, $this->arguments['optionValueField']);
         } elseif ($this->persistenceManager->getIdentifierByObject($valueElement) !== null) {
             return $this->persistenceManager->getIdentifierByObject($valueElement);
         } else {
             return (string) $valueElement;
         }
     } else {
         return $valueElement;
     }
 }
예제 #26
0
 /**
  * Checks if the given $nodeType is allowed as a childNode of the given $childNodeName
  * (which must be auto-created in $this NodeType).
  *
  * Only allowed to be called if $childNodeName is auto-created.
  *
  * @param string $childNodeName The name of a configured childNode of this NodeType
  * @param NodeType $nodeType The NodeType to check constraints for.
  * @return boolean TRUE if the $nodeType is allowed as grandchild node, FALSE otherwise.
  * @throws \InvalidArgumentException If the given $childNodeName is not configured to be auto-created in $this.
  */
 public function allowsGrandchildNodeType($childNodeName, NodeType $nodeType)
 {
     $autoCreatedChildNodes = $this->getAutoCreatedChildNodes();
     if (!isset($autoCreatedChildNodes[$childNodeName])) {
         throw new \InvalidArgumentException('The method "allowsGrandchildNodeType" can only be used on auto-created childNodes, given $childNodeName "' . $childNodeName . '" is not auto-created.', 1403858395);
     }
     $constraints = $autoCreatedChildNodes[$childNodeName]->getConfiguration('constraints.nodeTypes') ?: array();
     $childNodeConfiguration = [];
     foreach ($this->getConfiguration('childNodes') as $name => $configuration) {
         $childNodeConfiguration[Utility::renderValidNodeName($name)] = $configuration;
     }
     $childNodeConstraintConfiguration = ObjectAccess::getPropertyPath($childNodeConfiguration, $childNodeName . '.constraints.nodeTypes') ?: array();
     $constraints = Arrays::arrayMergeRecursiveOverrule($constraints, $childNodeConstraintConfiguration);
     return $this->isNodeTypeAllowedByConstraints($nodeType, $constraints);
 }
예제 #27
0
 /**
  * Collect the array keys inside $this->subject with each position meta-argument.
  * If there is no position but the array is numerically ordered, we use the array index as position.
  *
  * @return array an associative array where each key of $subject has a position string assigned
  */
 protected function collectArrayKeysAndPositions()
 {
     $arrayKeysWithPosition = [];
     foreach ($this->subject as $key => $value) {
         // if the value was set to NULL it was unset and should not be used
         if ($value === null) {
             continue;
         }
         $position = ObjectAccess::getPropertyPath($value, $this->positionPropertyPath);
         if ($position !== null) {
             $arrayKeysWithPosition[$key] = $position;
         } elseif (is_numeric($key)) {
             $arrayKeysWithPosition[$key] = $key;
         } else {
             $arrayKeysWithPosition[$key] = 0;
         }
     }
     return $arrayKeysWithPosition;
 }