PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/OData Producer for PHP/library/ODataProducer/UriProcessor/QueryProcessor/SkipTokenParser/InternalSkipTokenInfo.php

#
PHP | 317 lines | 168 code | 21 blank | 128 comment | 23 complexity | 13d38fd8bfbc1508b7cb81325d784403 MD5 | raw file
  1. <?php
  2. /**
  3. * Type which holds information about processed skiptoken value, this type
  4. * also provide method to search the given result set for the skiptoken
  5. * and to build skiptoken from an entry object.
  6. *
  7. * PHP version 5.3
  8. *
  9. * @category ODataProducer
  10. * @package ODataProducer_UriProcessor_QueryProcessor_SkipTokenParser
  11. * @author Anu T Chandy <odataphpproducer_alias@microsoft.com>
  12. * @copyright 2011 Microsoft Corp. (http://www.microsoft.com)
  13. * @license New BSD license, (http://www.opensource.org/licenses/bsd-license.php)
  14. * @version SVN: 1.0
  15. * @link http://odataphpproducer.codeplex.com
  16. *
  17. */
  18. namespace ODataProducer\UriProcessor\QueryProcessor\SkipTokenParser;
  19. use ODataProducer\Providers\Metadata\Type\Guid;
  20. use ODataProducer\Providers\Metadata\Type\Null1;
  21. use ODataProducer\Providers\Metadata\Type\DateTime;
  22. use ODataProducer\Providers\Metadata\Type\String;
  23. use ODataProducer\Providers\Metadata\ResourceType;
  24. use ODataProducer\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
  25. use ODataProducer\Common\Messages;
  26. use ODataProducer\Common\ODataException;
  27. /**
  28. * Type to hold information about processed skiptoken value.
  29. *
  30. * @category ODataProducer
  31. * @package ODataProducer_UriProcessor_QueryProcessor_SkipTokenParser
  32. * @author Anu T Chandy <odataphpproducer_alias@microsoft.com>
  33. * @copyright 2011 Microsoft Corp. (http://www.microsoft.com)
  34. * @license New BSD license, (http://www.opensource.org/licenses/bsd-license.php)
  35. * @version Release: 1.0
  36. * @link http://odataphpproducer.codeplex.com
  37. */
  38. class InternalSkipTokenInfo
  39. {
  40. /**
  41. * Reference to an instance of InternalOrderByInfo which holds
  42. * sorter function(s) generated from orderby clause.
  43. *
  44. * @var InternalOrderByInfo
  45. */
  46. private $_internalOrderByInfo;
  47. /**
  48. * Holds collection of values in the skiptoken corrosponds to the orderby
  49. * path segments.
  50. *
  51. * @var array(int (array(string, IType))
  52. */
  53. private $_orderByValuesInSkipToken;
  54. /**
  55. * Holds reference to the type of the resource pointed by the request uri.
  56. *
  57. * @var ResourceType
  58. */
  59. private $_resourceType;
  60. /**
  61. * Reference to the object holding parsed skiptoken value, this information
  62. * can be used by the IDSQP implementor for custom paging.
  63. *
  64. * @var SkipTokenInfo
  65. */
  66. private $_skipTokenInfo;
  67. /**
  68. * Object which is used as a key for searching the sorted result, this object
  69. * will be an instance of type described by the resource type pointed by the
  70. * request uri.
  71. *
  72. * @var mixed
  73. */
  74. private $_keyObject;
  75. /**
  76. * Creates a new instance of InternalSkipTokenInfo
  77. *
  78. * @param InternalOrderByInfo &$internalOrderByInfo Reference to an instance of InternalOrderByInfo which holds
  79. * sorter function(s) generated from orderby clause.
  80. * @param array(int(array(string,IType) $orderByValuesInSkipToken Collection of values in the skiptoken corrosponds to the
  81. * orderby path segments.
  82. * @param ResourceType &$resourceType Reference to the type of the resource pointed by the request uri.
  83. */
  84. public function __construct(InternalOrderByInfo &$internalOrderByInfo,
  85. $orderByValuesInSkipToken, ResourceType &$resourceType
  86. ) {
  87. $this->_internalOrderByInfo = $internalOrderByInfo;
  88. $this->_orderByValuesInSkipToken = $orderByValuesInSkipToken;
  89. $this->_resourceType = $resourceType;
  90. $this->_skipTokenInfo = null;
  91. $this->_keyObject = null;
  92. }
  93. /**
  94. * Gets reference to the SkipTokenInfo object holding result of
  95. * skiptoken parsing, which used by the IDSQP implementor for
  96. * custom paging.
  97. *
  98. * @return SkipTokenInfo
  99. */
  100. public function getSkipTokenInfo()
  101. {
  102. if (is_null($this->_skipTokenInfo)) {
  103. $orderbyInfo = $this->_internalOrderByInfo->getOrderByInfo();
  104. $this->_skipTokenInfo = new SkipTokenInfo(
  105. $orderbyInfo,
  106. $this->_orderByValuesInSkipToken
  107. );
  108. }
  109. return $this->_skipTokenInfo;
  110. }
  111. /**
  112. * Search the sorted array of result set for key object created from the
  113. * skip token key values and returns index of first entry in the next
  114. * page.
  115. *
  116. * @param array(mixed) &$searchArray The sorted array to search.
  117. *
  118. * @return int (1) If the array is empty then return -1,
  119. * (2) If the key object found then return index of first record
  120. * in the next page,
  121. * (3) If partial matching found (means found matching for first
  122. * m keys where m < n, where n is total number of positional
  123. * keys, then return the index of the object which has most
  124. * matching.
  125. *
  126. * @throws InvalidArgumentException
  127. */
  128. public function getIndexOfFirstEntryInTheNextPage(&$searchArray)
  129. {
  130. if (!is_array($searchArray)) {
  131. throw new \InvalidArgumentException(
  132. Messages::internalSkipTokenInfoBinarySearchRequireArray(
  133. 'searchArray'
  134. )
  135. );
  136. }
  137. if (empty($searchArray)) {
  138. return -1;
  139. }
  140. $comparer
  141. = $this->_internalOrderByInfo->getSorterFunction()->getReference();
  142. //Gets the key object initialized from skiptoken
  143. $keyObject = $this->getKeyObject();
  144. $low = 0;
  145. $searcArraySize = count($searchArray) - 1;
  146. $mid = 0;
  147. $high = $searcArraySize;
  148. do {
  149. $matchLevel = 0;
  150. $mid = $low + round(($high - $low)/2);
  151. $result = $comparer($keyObject, $searchArray[$mid]);
  152. if ($result > 0) {
  153. $low = $mid + 1;
  154. } else if ($result < 0) {
  155. $high = $mid - 1;
  156. } else {
  157. //Now we found record the matches with skiptoken value,
  158. //so first record of next page will at $mid + 1
  159. if ($mid == $searcArraySize) {
  160. //Check skiptoken points to last record, in this
  161. //case no more records available for next page
  162. return -1;
  163. }
  164. return $mid + 1;
  165. }
  166. } while ($low <= $high);
  167. if ($mid >= $searcArraySize) {
  168. //If key object does not match with last object, then
  169. //no more page
  170. return -1;
  171. } else if ($mid <= 0) {
  172. //If key object is less than first object, then paged
  173. //result start from 0
  174. return 0;
  175. }
  176. //return index of the most matching object
  177. return $mid;
  178. }
  179. /**
  180. * Gets the key object for searching, if the object is not initialized,
  181. * then do it from skiptoken positional values.
  182. *
  183. * @return mixed
  184. *
  185. * @throws ODataException If reflection exception occurs while accessing
  186. * or setting property.
  187. */
  188. public function getKeyObject()
  189. {
  190. if (is_null($this->_keyObject)) {
  191. $this->_keyObject = $this->_internalOrderByInfo->getDummyObject();
  192. $i = 0;
  193. foreach ($this->_internalOrderByInfo->getOrderByPathSegments()
  194. as $orderByPathSegment) {
  195. $index = 0;
  196. $currentObject = $this->_keyObject;
  197. $subPathSegments = $orderByPathSegment->getSubPathSegments();
  198. $subPathCount = count($subPathSegments);
  199. foreach ($subPathSegments as &$subPathSegment) {
  200. $isLastSegment = ($index == $subPathCount - 1);
  201. $dummyProperty = null;
  202. try {
  203. // if currentObject = null means, previous iteration did a
  204. // ReflectionProperty::getValue where ReflectionProperty
  205. // represents a complex/navigation, but its null, which means
  206. // the property is not set in the dummy object by OrderByParser,
  207. // an unexpected state.
  208. if (!$isLastSegment) {
  209. $dummyProperty = new \ReflectionProperty(
  210. $currentObject,
  211. $subPathSegment->getName()
  212. );
  213. $currentObject
  214. = $dummyProperty->getValue($currentObject);
  215. } else {
  216. $dummyProperty = new \ReflectionProperty(
  217. $currentObject,
  218. $subPathSegment->getName()
  219. );
  220. if ($this->_orderByValuesInSkipToken[$i][1] instanceof Null1) {
  221. $dummyProperty->setValue($currentObject, null);
  222. } else {
  223. // The Lexer's Token::Text value will be always
  224. // string, convert the string to
  225. // required type i.e. int, float, double etc..
  226. $value
  227. = $this->_orderByValuesInSkipToken[$i][1]->convert(
  228. $this->_orderByValuesInSkipToken[$i][0]
  229. );
  230. $dummyProperty->setValue($currentObject, $value);
  231. }
  232. }
  233. } catch (\ReflectionException $reflectionException) {
  234. throw ODataException::createInternalServerError(
  235. Messages::internalSkipTokenInfoFailedToAccessOrInitializeProperty(
  236. $subPathSegment->getName()
  237. )
  238. );
  239. }
  240. $index++;
  241. }
  242. $i++;
  243. }
  244. }
  245. return $this->_keyObject;
  246. }
  247. /**
  248. * Build nextpage link from the given object which will be the last object
  249. * in the page.
  250. *
  251. * @param mixed $lastObject Entity instance to build next page link from.
  252. *
  253. * @return string
  254. *
  255. * @throws ODataException If reflection exception occurs while accessing
  256. * property.
  257. */
  258. public function buildNextPageLink($lastObject)
  259. {
  260. $nextPageLink = null;
  261. foreach ($this->_internalOrderByInfo->getOrderByPathSegments()
  262. as $orderByPathSegment) {
  263. $index = 0;
  264. $currentObject = $lastObject;
  265. $subPathSegments = $orderByPathSegment->getSubPathSegments();
  266. $subPathCount = count($subPathSegments);
  267. foreach ($subPathSegments as &$subPathSegment) {
  268. $isLastSegment = ($index == $subPathCount - 1);
  269. try {
  270. $dummyProperty = new \ReflectionProperty(
  271. $currentObject,
  272. $subPathSegment->getName()
  273. );
  274. $currentObject = $dummyProperty->getValue($currentObject);
  275. if (is_null($currentObject)) {
  276. $nextPageLink .= 'null, ';
  277. break;
  278. } else if ($isLastSegment) {
  279. $type = $subPathSegment->getInstanceType();
  280. $value = $type->convertToOData($currentObject);
  281. $nextPageLink .= $value . ', ';
  282. }
  283. } catch (\ReflectionException $reflectionException) {
  284. throw ODataException::createInternalServerError(
  285. Messages::internalSkipTokenInfoFailedToAccessOrInitializeProperty(
  286. $subPathSegment->getName()
  287. )
  288. );
  289. }
  290. $index++;
  291. }
  292. }
  293. return rtrim($nextPageLink, ", ");
  294. }
  295. }
  296. ?>