ObjectId.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2020 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
11 namespace wcmf\lib\persistence;
12 
15 
16 /**
17  * ObjectId is the unique identifier of an object.
18  *
19  * @note The ObjectId must provide enough information to select the appropriate mapper for the object.
20  * This may be achived by different strategies, e.g. coding the object type into the ObjectId or
21  * having a global registry which maps ObjectIds to objects. wCMF uses the first method.
22  * Serialized ObjectIds have the following notation: prefix:type:id1:id2:... where type is the object type
23  * and id1, id2, .. are the values of the primary key columns (in case of simple keys only one).
24  * Serialization is done using the __toString method (using the ObjectId instance in a string context).
25  *
26  * @author ingo herwig <ingo@wemove.com>
27  */
28 class ObjectId implements \Serializable, \JsonSerializable {
29 
30  const DELIMITER = ':';
31 
32  private $prefix;
33  private $fqType;
34  private $id;
35  private $strVal = null;
36 
37  private static $dummyIdPattern = 'wcmf[A-Za-z0-9]{32}';
38  private static $idPattern = null;
39  private static $delimiterPattern = null;
40  private static $numPkKeys = [];
41 
42  private static $nullOID = null;
43 
44  /**
45  * Constructor.
46  * @param $type The type name of the object (either fully qualified or simple, if not ambiguous)
47  * @param $id Either a single value or an array of values (for compound primary keys) identifying
48  * the object between others of the same type. If not given, a dummy id will be
49  * assigned (optional, default: empty array)
50  * @param $prefix A prefix for identifying a set of objects belonging to one storage in a
51  * distributed environment (optional, default: _null_)
52  * @note If id is an array, the order of the values must match the order of the primary key names given
53  * by PersistenceMapper::getPkNames().
54  */
55  public function __construct($type, $id=[], $prefix=null) {
56  $this->prefix = $prefix;
57  $persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
58  $this->fqType = $type != 'NULL' ? $persistenceFacade->getFullyQualifiedType($type) : 'NULL';
59 
60  // get given primary keys
61  $this->id = !is_array($id) ? [$id] : $id;
62 
63  // add dummy ids for missing primary key values
64  $numPKs = self::getNumberOfPKs($type);
65  while (sizeof($this->id) < $numPKs) {
66  $this->id[] = self::getDummyId();
67  }
68 
69  // set strVal immediatly otherwise object comparison will fail in
70  // cases where __toString was only called on one instance
71  $this->strVal = $this->__toString();
72  }
73 
74  /**
75  * Get the NULL instance
76  * @return String
77  */
78  public static function NULL_OID() {
79  if (self::$nullOID == null) {
80  self::$nullOID = new ObjectId('NULL');
81  }
82  return self::$nullOID;
83  }
84 
85  /**
86  * Get the prefix
87  * @return String
88  */
89  public function getPrefix() {
90  return $this->prefix;
91  }
92 
93  /**
94  * Get the type (including namespace)
95  * @return String
96  */
97  public function getType() {
98  return $this->fqType;
99  }
100 
101  /**
102  * Get the id
103  * @return Array
104  */
105  public function getId() {
106  return $this->id;
107  }
108 
109  /**
110  * Get the first id. This is especially useful, when you know that this id only consists of one id.
111  * @return String
112  */
113  public function getFirstId() {
114  return $this->id[0];
115  }
116 
117  /**
118  * Check if a serialized ObjectId has a valid syntax, the type is known and
119  * if the number of primary keys match the type.
120  * @param $oid The serialized ObjectId.
121  * @return Boolean
122  */
123  public static function isValid($oid) {
124  if (self::parse($oid) == null) {
125  return false;
126  }
127  return true;
128  }
129 
130  /**
131  * Parse a serialized object id string into an ObjectId instance.
132  * @param $oid The string
133  * @return ObjectId or null, if the id cannot be parsed
134  */
135  public static function parse($oid) {
136  // fast checks first
137  if ($oid instanceof ObjectId) {
138  return $oid;
139  }
140 
141  $oidParts = self::parseOidString($oid);
142  if (!$oidParts) {
143  return null;
144  }
145 
146  $type = $oidParts['type'];
147  $ids = $oidParts['id'];
148  $prefix = $oidParts['prefix'];
149 
150  // check the type
151  if (!ObjectFactory::getInstance('persistenceFacade')->isKnownType($type)) {
152  return null;
153  }
154 
155  // check if number of ids match the type
156  $numPks = self::getNumberOfPKs($type);
157  if ($numPks == null || $numPks != sizeof($ids)) {
158  return null;
159  }
160 
161  return new ObjectID($type, $ids, $prefix);
162  }
163 
164  /**
165  * Parse the given object id string into it's parts
166  * @param $oid The string
167  * @return Associative array with keys 'type', 'id', 'prefix'
168  */
169  private static function parseOidString($oid) {
170  if (strlen($oid) == 0) {
171  return null;
172  }
173 
174  $oidParts = preg_split(self::getDelimiterPattern(), $oid);
175  if (sizeof($oidParts) < 2) {
176  return null;
177  }
178 
179  if (self::$idPattern == null) {
180  self::$idPattern = '/^[0-9]*$|^'.self::$dummyIdPattern.'$/';
181  }
182 
183  // get the ids from the oid
184  $ids = [];
185  $nextPart = array_pop($oidParts);
186  while($nextPart !== null && preg_match(self::$idPattern, $nextPart) == 1) {
187  $intNextPart = (int)$nextPart;
188  if ($nextPart == (string)$intNextPart) {
189  $ids[] = $intNextPart;
190  }
191  else {
192  $ids[] = $nextPart;
193  }
194  $nextPart = array_pop($oidParts);
195  }
196  $ids = array_reverse($ids);
197 
198  // get the type
199  $type = $nextPart;
200 
201  // get the prefix
202  $prefix = join(self::DELIMITER, $oidParts);
203 
204  return [
205  'type' => $type,
206  'id' => $ids,
207  'prefix' => $prefix
208  ];
209  }
210 
211  /**
212  * Get a string representation of the object id.
213  * @return String
214  */
215  public function __toString() {
216  if ($this->strVal == null) {
217  $oidStr = $this->fqType.self::DELIMITER.join(self::DELIMITER, $this->id);
218  if (strlen(trim($this->prefix)) > 0) {
219  $oidStr = $this->prefix.self::DELIMITER.$oidStr;
220  }
221  $this->strVal = $oidStr;
222  }
223  return $this->strVal;
224  }
225 
226  /**
227  * Get a dummy id ("wcmf" + unique 32 character string).
228  * @return String
229  */
230  public static function getDummyId() {
231  return 'wcmf'.str_replace('-', '', StringUtil::guidv4());
232  }
233 
234  /**
235  * Check if a given id is a dummy id.
236  * @param $id The id to check
237  * @return Boolean
238  */
239  public static function isDummyId($id) {
240  return (strlen($id) == 36 && strpos($id, 'wcmf') === 0);
241  }
242 
243  /**
244  * Check if this object id contains a dummy id.
245  * @return Boolean
246  */
247  public function containsDummyIds() {
248  foreach ($this->getId() as $id) {
249  if (self::isDummyId($id)) {
250  return true;
251  }
252  }
253  return false;
254  }
255 
256  /**
257  * Get the number of primary keys a type has.
258  * @param $type The type
259  * @return Integer (1 if the type is unknown)
260  */
261  private static function getNumberOfPKs($type) {
262  if (!isset(self::$numPkKeys[$type])) {
263  $numPKs = 1;
264  $persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
265  if ($persistenceFacade->isKnownType($type)) {
266  $mapper = $persistenceFacade->getMapper($type);
267  $numPKs = sizeof($mapper->getPKNames());
268  }
269  self::$numPkKeys[$type] = $numPKs;
270  }
271  return self::$numPkKeys[$type];
272  }
273 
274  private static function getDelimiterPattern() {
275  if (self::$delimiterPattern == null) {
276  self::$delimiterPattern = '/'.self::DELIMITER.'/';
277  }
278  return self::$delimiterPattern;
279  }
280 
281  public function serialize() {
282  return $this->__toString();
283  }
284 
285  public function unserialize($data) {
286  $oidParts = self::parseOidString($data);
287  $this->prefix = $oidParts['prefix'];
288  $this->fqType = $oidParts['type'];
289  $this->id = $oidParts['id'];
290  $this->strVal = $this->__toString();
291  }
292 
293  public function jsonSerialize() {
294  return $this->__toString();
295  }
296 }
297 ?>
static NULL_OID()
Get the NULL instance.
Definition: ObjectId.php:78
containsDummyIds()
Check if this object id contains a dummy id.
Definition: ObjectId.php:247
static getDummyId()
Get a dummy id ("wcmf" + unique 32 character string).
Definition: ObjectId.php:230
static isDummyId($id)
Check if a given id is a dummy id.
Definition: ObjectId.php:239
Persistence layer related interfaces and classes.
Definition: namespaces.php:42
__toString()
Get a string representation of the object id.
Definition: ObjectId.php:215
StringUtil provides support for string manipulation.
Definition: StringUtil.php:18
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:28
getPrefix()
Get the prefix.
Definition: ObjectId.php:89
getFirstId()
Get the first id.
Definition: ObjectId.php:113
static parse($oid)
Parse a serialized object id string into an ObjectId instance.
Definition: ObjectId.php:135
getType()
Get the type (including namespace)
Definition: ObjectId.php:97
static getInstance($name, $dynamicConfiguration=[])
static guidv4()
Generate a v4 UUID Code from https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-...
Definition: StringUtil.php:433
static isValid($oid)
Check if a serialized ObjectId has a valid syntax, the type is known and if the number of primary key...
Definition: ObjectId.php:123
__construct($type, $id=[], $prefix=null)
Constructor.
Definition: ObjectId.php:55
ObjectFactory implements the service locator pattern by wrapping a Factory instance and providing sta...