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