PageRenderTime 54ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/OData Producer for PHP/library/ODataProducer/ObjectModel/ObjectModelSerializerBase.php

#
PHP | 777 lines | 382 code | 57 blank | 338 comment | 57 complexity | 36404a8e76b3b46107c79679e93f3bd0 MD5 | raw file
  1. <?php
  2. /**
  3. * Base class for object model serializer.
  4. *
  5. * PHP version 5.3
  6. *
  7. * @category ODataProducer
  8. * @package ODataProducer_ObjectModel
  9. * @author Anu T Chandy <odataphpproducer_alias@microsoft.com>
  10. * @copyright 2011 Microsoft Corp. (http://www.microsoft.com)
  11. * @license New BSD license, (http://www.opensource.org/licenses/bsd-license.php)
  12. * @version SVN: 1.0
  13. * @link http://odataphpproducer.codeplex.com
  14. *
  15. */
  16. namespace ODataProducer\ObjectModel;
  17. use ODataProducer\Common\ODataConstants;
  18. use ODataProducer\DataService;
  19. use ODataProducer\Providers\MetadataQueryProviderWrapper;
  20. use ODataProducer\Providers\Metadata\ResourceSetWrapper;
  21. use ODataProducer\Providers\Metadata\ResourceProperty;
  22. use ODataProducer\Providers\Metadata\ResourceTypeKind;
  23. use ODataProducer\Providers\Metadata\ResourceType;
  24. use ODataProducer\Providers\Metadata\Type\IType;
  25. use ODataProducer\UriProcessor\RequestDescription;
  26. use ODataProducer\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
  27. use ODataProducer\Common\InvalidOperationException;
  28. use ODataProducer\Common\ODataException;
  29. use ODataProducer\Common\Messages;
  30. /**
  31. * Base class for object model serializer.
  32. *
  33. * @category ODataProducer
  34. * @package ODataProducer_ObjectModel
  35. * @author Anu T Chandy <odataphpproducer_alias@microsoft.com>
  36. * @copyright 2011 Microsoft Corp. (http://www.microsoft.com)
  37. * @license New BSD license, (http://www.opensource.org/licenses/bsd-license.php)
  38. * @version Release: 1.0
  39. * @link http://odataphpproducer.codeplex.com
  40. */
  41. class ObjectModelSerializerBase
  42. {
  43. /**
  44. * Holds refernece to the data service instance.
  45. *
  46. * @var DataService
  47. */
  48. protected $dataService;
  49. /**
  50. * Request description instance describes OData request the
  51. * the client has submitted and result of the request.
  52. *
  53. * @var RequestDescription
  54. */
  55. protected $requestDescription;
  56. /**
  57. * Collection of segment names
  58. * Remark: Read 'ObjectModelSerializerNotes.txt' for more
  59. * details about segment.
  60. *
  61. * @var array(string)
  62. */
  63. private $_segmentNames;
  64. /**
  65. * Collection of segment ResourceSetWrapper instances
  66. * Remark: Read 'ObjectModelSerializerNotes.txt' for more
  67. * details about segment.
  68. *
  69. * @var array(ResourceSetWrapper)
  70. */
  71. private $_segmentResourceSetWrappers;
  72. /**
  73. * Result counts for segments
  74. * Remark: Read 'ObjectModelSerializerNotes.txt' for more
  75. * details about segment.
  76. *
  77. * @var array(int)
  78. */
  79. private $_segmentResultCounts;
  80. /**
  81. * Collection of complex type instances used for cycle detection.
  82. *
  83. * @var array(mixed)
  84. */
  85. protected $complexTypeInstanceCollection;
  86. /**
  87. * Absolute service Uri.
  88. *
  89. * @var string
  90. */
  91. protected $absoluteServiceUri;
  92. /**
  93. * Absolute service Uri with slash.
  94. *
  95. * @var string
  96. */
  97. protected $absoluteServiceUriWithSlash;
  98. /**
  99. * Constructs a new instance of ObjectModelSerializerBase.
  100. *
  101. * @param DataService &$dataService Reference to the
  102. * data service instance.
  103. * @param RequestDescription &$requestDescription Type instance describing
  104. * the client submitted
  105. * request.
  106. */
  107. protected function __construct(DataService &$dataService, RequestDescription &$requestDescription)
  108. {
  109. $this->dataService = $dataService;
  110. $this->requestDescription = $requestDescription;
  111. $this->absoluteServiceUri = $dataService->getHost()->getAbsoluteServiceUri()->getUrlAsString();
  112. $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
  113. $this->_segmentNames = array();
  114. $this->_segmentResourceSetWrappers = array();
  115. $this->_segmentResultCounts = array();
  116. $this->complexTypeInstanceCollection = array();
  117. }
  118. /**
  119. * Builds the key for the given entity instance.
  120. * Note: The generated key can be directly used in the uri,
  121. * this function will perform
  122. * required escaping of characters, for example:
  123. * Ships(ShipName='Antonio%20Moreno%20Taquer%C3%ADa',ShipID=123),
  124. * Note to method caller: Don't do urlencoding on
  125. * return value of this method as it already encoded.
  126. *
  127. * @param mixed &$entityInstance Entity instance for which
  128. * key value needs to be prepared.
  129. * @param ResourceType &$resourceType Resource type instance containing
  130. * metadata about the instance.
  131. * @param string $containerName Name of the entity set that
  132. * the entity instance belongs to.
  133. *
  134. * @return string Key for the given resource, with values
  135. * encoded for use in a URI.
  136. */
  137. protected function getEntryInstanceKey(&$entityInstance, ResourceType &$resourceType, $containerName)
  138. {
  139. $keyProperties = $resourceType->getKeyProperties();
  140. $this->assert(count($keyProperties) != 0, 'count($keyProperties) != 0');
  141. $keyString = $containerName . '(';
  142. $comma = null;
  143. foreach ($keyProperties as $keyName => $resourceProperty) {
  144. $keyType = $resourceProperty->getInstanceType();
  145. $this->assert(
  146. array_search('ODataProducer\Providers\Metadata\Type\IType', class_implements($keyType)) !== false,
  147. 'array_search(\'ODataProducer\Providers\Metadata\Type\IType\', class_implements($keyType)) !== false'
  148. );
  149. $keyValue = $this->getPropertyValue($entityInstance, $resourceType, $resourceProperty);
  150. if (is_null($keyValue)) {
  151. ODataException::createInternalServerError(Messages::badQueryNullKeysAreNotSupported($resourceType->getName(), $keyName));
  152. }
  153. $keyValue = $keyType->convertToOData($keyValue);
  154. $keyString .= $comma . $keyName.'='.$keyValue;
  155. $comma = ',';
  156. }
  157. $keyString .= ')';
  158. return $keyString;
  159. }
  160. /**
  161. * Get the value of a given property from an instance.
  162. *
  163. * @param mixed &$object Instance of a type which
  164. * contains this property.
  165. * @param ResourceType &$resourceType Resource type instance
  166. * containing metadata about
  167. * the instance.
  168. * @param ResourceProperty &$resourceProperty Resource property instance
  169. * containing metadata about the
  170. * property whose value
  171. * to be retrieved.
  172. *
  173. * @return mixed The value of the given property.
  174. *
  175. * @throws ODataException If reflection exception occured
  176. * while trying to access the property.
  177. */
  178. protected function getPropertyValue(&$object, ResourceType &$resourceType, ResourceProperty &$resourceProperty)
  179. {
  180. try {
  181. $reflectionProperty = new \ReflectionProperty($object, $resourceProperty->getName());
  182. $propertyValue = $reflectionProperty->getValue($object);
  183. return $propertyValue;
  184. } catch (\ReflectionException $reflectionException) {
  185. throw ODataException::createInternalServerError(
  186. Messages::objectModelSerializerFailedToAccessProperty(
  187. $resourceProperty->getName(),
  188. $resourceType->getName()
  189. )
  190. );
  191. }
  192. }
  193. /**
  194. * Resource set wrapper for the resource being serialized.
  195. *
  196. * @return ResourceSetWrapper
  197. */
  198. protected function getCurrentResourceSetWrapper()
  199. {
  200. $count = count($this->_segmentResourceSetWrappers);
  201. if ($count == 0) {
  202. return $this->requestDescription->getTargetResourceSetWrapper();
  203. } else {
  204. return $this->_segmentResourceSetWrappers[$count - 1];
  205. }
  206. }
  207. /**
  208. * Whether the current resource set is root resource set.
  209. *
  210. * @return boolean true if the current resource set root container else
  211. * false.
  212. */
  213. protected function isRootResourceSet()
  214. {
  215. return empty($this->_segmentResourceSetWrappers)
  216. || count($this->_segmentResourceSetWrappers) == 1;
  217. }
  218. /**
  219. * Returns the etag for the given resource.
  220. *
  221. * @param mixed &$entryObject Resource for which etag value
  222. * needs to be returned
  223. * @param ResourceType &$resourceType Resource type of the $entryObject
  224. *
  225. * @return string/NULL ETag value for the given resource
  226. * (with values encoded for use in a URI)
  227. * if there are etag properties, NULL if there is no etag property.
  228. */
  229. protected function getETagForEntry(&$entryObject, ResourceType &$resourceType)
  230. {
  231. $eTag = null;
  232. $comma = null;
  233. foreach ($resourceType->getETagProperties() as $eTagProperty) {
  234. $type = $eTagProperty->getInstanceType();
  235. $this->assert(
  236. !is_null($type)
  237. && array_search('ODataProducer\Providers\Metadata\Type\IType', class_implements($type)) !== false,
  238. '!is_null($type)
  239. && array_search(\'ODataProducer\Providers\Metadata\Type\IType\', class_implements($type)) !== false'
  240. );
  241. $value = $this->getPropertyValue($entryObject, $resourceType, $eTagProperty);
  242. if (is_null($value)) {
  243. $eTag = $eTag . $comma. 'null';
  244. } else {
  245. $eTag = $eTag . $comma . $type->convertToOData($value);
  246. }
  247. $comma = ',';
  248. }
  249. if (!is_null($eTag)) {
  250. // If eTag is made up of datetime or string properties then the above
  251. // IType::converToOData will perform utf8 and url encode. But we don't
  252. // want this for eTag value.
  253. $eTag = urldecode(utf8_decode($eTag));
  254. return ODataConstants::HTTP_WEAK_ETAG_PREFIX . rtrim($eTag, ',') . '"';
  255. }
  256. return null;
  257. }
  258. /**
  259. * Pushes a segment for the root of the tree being written out
  260. * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
  261. * 'Segment Stack' and this method.
  262. * Note: Calls to this method should be balanced with calls to popSegment.
  263. *
  264. * @return true if the segment was pushed, false otherwise.
  265. */
  266. protected function pushSegmentForRoot()
  267. {
  268. $segmentName = $this->requestDescription->getContainerName();
  269. $segmentResourceSetWrapper = $this->requestDescription->getTargetResourceSetWrapper();
  270. return $this->_pushSegment($segmentName, $segmentResourceSetWrapper);
  271. }
  272. /**
  273. * Pushes a segment for the current navigation property being written out.
  274. * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
  275. * 'Segment Stack' and this method.
  276. * Note: Calls to this method should be balanced with calls to popSegment.
  277. *
  278. * @param ResourceProperty &$resourceProperty The current navigation property
  279. * being written out.
  280. *
  281. * @return true if a segment was pushed, false otherwise
  282. *
  283. * @throws InvalidOperationException If this function invoked with non-navigation
  284. * property instance.
  285. */
  286. protected function pushSegmentForNavigationProperty(ResourceProperty &$resourceProperty)
  287. {
  288. if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY) {
  289. $this->assert(!empty($this->_segmentNames), '!is_empty($this->_segmentNames');
  290. $currentResourceSetWrapper = $this->getCurrentResourceSetWrapper();
  291. $currentResourceType = $currentResourceSetWrapper->getResourceType();
  292. $currentResourceSetWrapper = $this->dataService
  293. ->getMetadataQueryProviderWrapper()
  294. ->getResourceSetWrapperForNavigationProperty(
  295. $currentResourceSetWrapper,
  296. $currentResourceType,
  297. $resourceProperty
  298. );
  299. $this->assert(!is_null($currentResourceSetWrapper), '!null($currentResourceSetWrapper)');
  300. return $this->_pushSegment($resourceProperty->getName(), $currentResourceSetWrapper);
  301. } else {
  302. throw new InvalidOperationException('pushSegmentForNavigationProperty should not be called with non-entity type');
  303. }
  304. }
  305. /**
  306. * Gets collection of projection nodes under the current node.
  307. *
  308. * @return array(ProjectionNode/ExpandedProjectionNode)/NULL List of nodes
  309. * describing projections for the current segment, If this method returns
  310. * null it means no projections are to be applied and the entire resource
  311. * for the current segment should be serialized, If it returns non-null
  312. * only the properties described by the returned projection segments should
  313. * be serialized.
  314. */
  315. protected function getProjectionNodes()
  316. {
  317. $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
  318. if (is_null($expandedProjectionNode)
  319. || $expandedProjectionNode->canSelectAllProperties()
  320. ) {
  321. return null;
  322. }
  323. return $expandedProjectionNode->getChildNodes();
  324. }
  325. /**
  326. * Find a 'ExpandedProjectionNode' instance in the projection tree
  327. * which describes the current segment.
  328. *
  329. * @return ExpandedProjectionNode/NULL
  330. */
  331. protected function getCurrentExpandedProjectionNode()
  332. {
  333. $expandedProjectionNode = $this->requestDescription->getRootProjectionNode();
  334. if (is_null($expandedProjectionNode)) {
  335. return null;
  336. } else {
  337. $depth = count($this->_segmentNames);
  338. // $depth == 1 means serialization of root entry
  339. //(the resource identified by resource path) is going on,
  340. //so control won't get into the below for loop.
  341. //we will directly return the root node,
  342. //which is 'ExpandedProjectionNode'
  343. // for resource identified by resource path.
  344. if ($depth != 0) {
  345. for ($i = 1; $i < $depth; $i++) {
  346. $expandedProjectionNode
  347. = $expandedProjectionNode->findNode($this->_segmentNames[$i]);
  348. $this->assert(
  349. !is_null($expandedProjectionNode),
  350. '!is_null($expandedProjectionNode)'
  351. );
  352. $this->assert(
  353. $expandedProjectionNode instanceof ExpandedProjectionNode,
  354. '$expandedProjectionNode instanceof ExpandedProjectionNode'
  355. );
  356. }
  357. }
  358. }
  359. return $expandedProjectionNode;
  360. }
  361. /**
  362. * Check whether to expand a navigation property or not.
  363. *
  364. * @param string $navigationPropertyName Name of naviagtion property in question.
  365. *
  366. * @return boolean True if the given navigation should be
  367. * explanded otherwise false.
  368. */
  369. protected function shouldExpandSegment($navigationPropertyName)
  370. {
  371. $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
  372. if (is_null($expandedProjectionNode)) {
  373. return false;
  374. }
  375. $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
  376. return !is_null($expandedProjectionNode) && ($expandedProjectionNode instanceof ExpandedProjectionNode);
  377. }
  378. /**
  379. * Pushes information about the segment that is going to be serialized
  380. * to the 'Segment Stack'.
  381. * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
  382. * 'Segment Stack' and this method.
  383. * Note: Calls to this method should be balanced with calls to popSegment.
  384. *
  385. * @param string $segmentName Name of segment to push.
  386. * @param ResourceSetWrapper &$resourceSetWrapper The resource set
  387. * wrapper to push.
  388. *
  389. * @return true if the segment was push, false otherwise
  390. */
  391. private function _pushSegment($segmentName, ResourceSetWrapper &$resourceSetWrapper)
  392. {
  393. $rootProjectionNode = $this->requestDescription->getRootProjectionNode();
  394. // Even though there is no expand in the request URI, still we need to push
  395. // the segment information if we need to count
  396. //the number of entities written.
  397. // After serializing each entity we should check the count to see whether
  398. // we serialized more entities than configured
  399. //(page size, maxResultPerCollection).
  400. // But we will not do this check since library is doing paging and never
  401. // accumulate entities more than configured.
  402. //
  403. // if ((!is_null($rootProjectionNode) && $rootProjectionNode->isExpansionSpecified())
  404. // || ($resourceSetWrapper->getResourceSetPageSize() != 0)
  405. // || ($this->dataService->getServiceConfiguration()->getMaxResultsPerCollection() != PHP_INT_MAX)
  406. //) {}
  407. if (!is_null($rootProjectionNode)
  408. && $rootProjectionNode->isExpansionSpecified()
  409. ) {
  410. array_push($this->_segmentNames, $segmentName);
  411. array_push($this->_segmentResourceSetWrappers, $resourceSetWrapper);
  412. array_push($this->_segmentResultCounts, 0);
  413. return true;
  414. }
  415. return false;
  416. }
  417. /**
  418. * Get next page link from the given entity instance.
  419. *
  420. * @param mixed &$lastObject Last object serialized to be
  421. * used for generating $skiptoken.
  422. * @param string $absoluteUri Absolute response URI.
  423. *
  424. * @return URI for the link for next page.
  425. */
  426. protected function getNextLinkUri(&$lastObject, $absoluteUri)
  427. {
  428. $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
  429. $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
  430. $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
  431. $this->assert(!is_null($skipToken), '!is_null($skipToken)');
  432. $queryParameterString = null;
  433. if ($this->isRootResourceSet()) {
  434. $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
  435. } else {
  436. $queryParameterString = $this->getNextPageLinkQueryParametersForExpandedResourceSet();
  437. }
  438. $queryParameterString .= '$skiptoken=' . $skipToken;
  439. $odalaLink = new ODataLink();
  440. $odalaLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
  441. $odalaLink->url = rtrim($absoluteUri, '/') . '?' . $queryParameterString;
  442. return $odalaLink;
  443. }
  444. /**
  445. * Builds the string corresponding to query parameters for top level results
  446. * (result set identified by the resource path) to be put in next page link.
  447. *
  448. * @return string/NULL string representing the query parameters in the URI
  449. * query parameter format, NULL if there
  450. * is no query parameters
  451. * required for the next link of top level result set.
  452. */
  453. protected function getNextPageLinkQueryParametersForRootResourceSet()
  454. {
  455. $queryParameterString = null;
  456. foreach (array(ODataConstants::HTTPQUERY_STRING_FILTER,
  457. ODataConstants::HTTPQUERY_STRING_EXPAND,
  458. ODataConstants::HTTPQUERY_STRING_ORDERBY,
  459. ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
  460. ODataConstants::HTTPQUERY_STRING_SELECT) as $queryOption
  461. ) {
  462. $value = $this->dataService->getHost()->getQueryStringItem($queryOption);
  463. if (!is_null($value)) {
  464. if (!is_null($queryParameterString)) {
  465. $queryParameterString = $queryParameterString . '&';
  466. }
  467. $queryParameterString .= $queryOption . '=' . $value;
  468. }
  469. }
  470. $topCountValue = $this->requestDescription->getTopOptionCount();
  471. if (!is_null($topCountValue)) {
  472. $remainingCount = $topCountValue - $this->requestDescription->getTopCount();
  473. if (!is_null($queryParameterString)) {
  474. $queryParameterString .= '&';
  475. }
  476. $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
  477. }
  478. if (!is_null($queryParameterString)) {
  479. $queryParameterString .= '&';
  480. }
  481. return $queryParameterString;
  482. }
  483. /**
  484. * Builds the string corresponding to query parameters for current expanded
  485. * results to be put in next page link.
  486. *
  487. * @return string/NULL string representing the $select and $expand parameters
  488. * in the URI query parameter format, NULL if there is no
  489. * query parameters ($expand and/select) required for the
  490. * next link of expanded result set.
  491. */
  492. protected function getNextPageLinkQueryParametersForExpandedResourceSet()
  493. {
  494. $queryParameterString = null;
  495. $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
  496. if (!is_null($expandedProjectionNode)) {
  497. $pathSegments = array();
  498. $selectionPaths = null;
  499. $expansionPaths = null;
  500. $foundSelections = false;
  501. $foundExpansions = false;
  502. $this->_buildSelectionAndExpansionPathsForNode(
  503. $pathSegments,
  504. $selectionPaths,
  505. $expansionPaths,
  506. $expandedProjectionNode,
  507. $foundSelections,
  508. $foundExpansions
  509. );
  510. if ($foundSelections && $expandedProjectionNode->canSelectAllProperties()) {
  511. $this->_appendSelectionOrExpandPath($selectionPaths, $pathSegments, '*');
  512. }
  513. if (!is_null($selectionPaths)) {
  514. $queryParameterString = '$select=' . $selectionPaths;
  515. }
  516. if (!is_null($expansionPaths)) {
  517. if (!is_null($queryParameterString)) {
  518. $queryParameterString .= '&';
  519. }
  520. $queryParameterString = '$expand=' . $expansionPaths;
  521. }
  522. if (!is_null($queryParameterString)) {
  523. $queryParameterString .= '&';
  524. }
  525. }
  526. return $queryParameterString;
  527. }
  528. /**
  529. * Wheter next link is needed for the current resource set (feed)
  530. * being serialized.
  531. *
  532. * @param int $resultSetCount Number of entries in the current
  533. * resource set.
  534. *
  535. * @return boolean true if the feed must have a next page link
  536. */
  537. protected function needNextPageLink($resultSetCount)
  538. {
  539. $currentResourceSet = $this->getCurrentResourceSetWrapper();
  540. $recursionLevel = count($this->_segmentNames);
  541. //$this->assert($recursionLevel != 0, '$recursionLevel != 0');
  542. $pageSize = $currentResourceSet->getResourceSetPageSize();
  543. if ($recursionLevel == 1) {
  544. //presence of $top option affect next link for root container
  545. $topValueCount = $this->requestDescription->getTopOptionCount();
  546. if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
  547. return false;
  548. }
  549. }
  550. return $resultSetCount == $pageSize;
  551. }
  552. /**
  553. * Pops segment information from the 'Segment Stack'
  554. * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
  555. * 'Segment Stack' and this method.
  556. * Note: Calls to this method should be balanced with previous
  557. * calls to _pushSegment.
  558. *
  559. * @param boolean $needPop Is a pop required. Only true if last
  560. * push was successful.
  561. *
  562. * @return void
  563. *
  564. * @throws InvalidOperationException If found un-balanced call with _pushSegment
  565. */
  566. protected function popSegment($needPop)
  567. {
  568. if ($needPop) {
  569. if (!empty($this->_segmentNames)) {
  570. array_pop($this->_segmentNames);
  571. array_pop($this->_segmentResourceSetWrappers);
  572. array_pop($this->_segmentResultCounts);
  573. } else {
  574. throw new InvalidOperationException('Found non-balanced call to _pushSegment and popSegment');
  575. }
  576. }
  577. }
  578. /**
  579. * Recursive metod to build $expand and $select paths for a specified node.
  580. *
  581. * @param array(string) &$parentPathSegments Array of path
  582. * segments which leads
  583. * up to (including)
  584. * the segment
  585. * represented by
  586. * $expandedProjectionNode.
  587. * @param array(string) &$selectionPaths The string which
  588. * holds projection
  589. * path segment
  590. * seperated by comma,
  591. * On return this argument
  592. * will be updated with
  593. * the selection path
  594. * segments under
  595. * this node.
  596. * @param array(string) &$expansionPaths The string which holds
  597. * expansion path segment
  598. * seperated by comma.
  599. * On return this argument
  600. * will be updated with
  601. * the expand path
  602. * segments under
  603. * this node.
  604. * @param ExpandedProjectionNode &$expandedProjectionNode The expanded node for
  605. * which expansion
  606. * and selection path
  607. * to be build.
  608. * @param boolean &$foundSelections On return, this
  609. * argument will hold
  610. * true if any selection
  611. * defined under this node
  612. * false otherwise.
  613. * @param boolean &$foundExpansions On return, this
  614. * argument will hold
  615. * true if any expansion
  616. * defined under this node
  617. * false otherwise.
  618. *
  619. * @return void
  620. */
  621. private function _buildSelectionAndExpansionPathsForNode(&$parentPathSegments,
  622. &$selectionPaths, &$expansionPaths,
  623. ExpandedProjectionNode &$expandedProjectionNode,
  624. &$foundSelections, &$foundExpansions
  625. ) {
  626. $foundSelections = false;
  627. $foundExpansions = false;
  628. $foundSelectionOnChild = false;
  629. $foundExpansionOnChild = false;
  630. $expandedChildrenNeededToBeSelected = array();
  631. foreach ($expandedProjectionNode->getChildNodes() as $childNode) {
  632. if (!($childNode instanceof ExpandedProjectionNode)) {
  633. $foundSelections = true;
  634. $this->_appendSelectionOrExpandPath(
  635. $selectionPaths,
  636. $parentPathSegments,
  637. $childNode->getPropertyName()
  638. );
  639. } else {
  640. $foundExpansions = true;
  641. array_push($parentPathSegments, $childNode->getPropertyName());
  642. $this->_buildSelectionAndExpansionPathsForNode(
  643. $parentPathSegments,
  644. $selectionPaths, $expansionPaths,
  645. $childNode, $foundSelectionOnChild,
  646. $foundExpansionOnChild
  647. );
  648. array_pop($parentPathSegments);
  649. if ($childNode->canSelectAllProperties()) {
  650. if ($foundSelectionOnChild) {
  651. $this->_appendSelectionOrExpandPath(
  652. $selectionPaths,
  653. $parentPathSegments,
  654. $childNode->getPropertyName() . '/*'
  655. );
  656. } else {
  657. $expandedChildrenNeededToBeSelected[] = $childNode;
  658. }
  659. }
  660. }
  661. $foundSelections |= $foundSelectionOnChild;
  662. if (!$foundExpansionOnChild) {
  663. $this->_appendSelectionOrExpandPath(
  664. $expansionPaths,
  665. $parentPathSegments,
  666. $childNode->getPropertyName()
  667. );
  668. }
  669. }
  670. if (!$expandedProjectionNode->canSelectAllProperties() || $foundSelections) {
  671. foreach ($expandedChildrenNeededToBeSelected as $childToProject) {
  672. $this->_appendSelectionOrExpandPath(
  673. $selectionPaths,
  674. $parentPathSegments,
  675. $childNode->getPropertyName()
  676. );
  677. $foundSelections = true;
  678. }
  679. }
  680. }
  681. /**
  682. * Append the given path to $expand or $select path list.
  683. *
  684. * @param string &$path The $expand or $select path list
  685. * to which to append the given path.
  686. * @param array(string) &$parentPathSegments The list of path upto the
  687. * $segmentToAppend.
  688. * @param string $segmentToAppend The last segment of the path.
  689. *
  690. * @return void
  691. */
  692. private function _appendSelectionOrExpandPath(&$path, &$parentPathSegments, $segmentToAppend)
  693. {
  694. if (!is_null($path)) {
  695. $path .= ', ';
  696. }
  697. foreach ($parentPathSegments as $parentPathSegment) {
  698. $path .= $parentPathSegment . '/';
  699. }
  700. $path .= $segmentToAppend;
  701. }
  702. /**
  703. * Assert that the given condition is true.
  704. *
  705. * @param boolean $condition Condition to be asserted.
  706. * @param string $conditionAsString String containing message incase
  707. * if assertion fails.
  708. *
  709. * @throws InvalidOperationException Incase if assertion failes.
  710. *
  711. * @return void
  712. */
  713. protected function assert($condition, $conditionAsString)
  714. {
  715. if (!$condition) {
  716. throw new InvalidOperationException("Unexpected state, expecting $conditionAsString");
  717. }
  718. }
  719. }
  720. ?>