From 76bf7f6d4d1d06e3a905d2c4517dbac6ffe11679 Mon Sep 17 00:00:00 2001 From: Joschi Kuphal Date: Wed, 3 Aug 2016 21:22:42 +0900 Subject: [PATCH] Fixed canonical object URL (closes #8) --- .../Domain/Model/Object/AbstractObject.php | 6 +- .../Object/Traits/SystemPropertiesTrait.php | 49 ++++---- .../Model/Properties/AbstractProperties.php | 110 ++++++++--------- .../Model/Properties/SystemProperties.php | 20 +-- src/Object/Domain/Model/Uri/ApparatUrl.php | 13 +- src/Object/Domain/Model/Uri/Locator.php | 116 ++++++++++-------- .../Domain/Model/Uri/LocatorInterface.php | 10 +- src/Object/Domain/Model/Uri/ObjectUrl.php | 34 +++-- .../Domain/Model/Uri/RepositoryLocator.php | 32 +++-- .../Model/Uri/RepositoryLocatorInterface.php | 9 ++ src/Object/Domain/Model/Uri/Url.php | 22 ++-- src/Object/Domain/Repository/Repository.php | 3 +- src/Object/Tests/ObjectUrlTest.php | 1 + 13 files changed, 243 insertions(+), 182 deletions(-) diff --git a/src/Object/Domain/Model/Object/AbstractObject.php b/src/Object/Domain/Model/Object/AbstractObject.php index 9412374..0811f6b 100644 --- a/src/Object/Domain/Model/Object/AbstractObject.php +++ b/src/Object/Domain/Model/Object/AbstractObject.php @@ -305,7 +305,7 @@ public function getPropertyData($serialize = true) */ public function getAbsoluteUrl() { - return getenv('APPARAT_BASE_URL').rtrim($this->locator->getRepository()->getUrl(), '/').strval($this->locator); + return $this->locator->toRepositoryUrl(false, false); } /** @@ -315,9 +315,7 @@ public function getAbsoluteUrl() */ public function getCanonicalUrl() { - $canonicalLocator = $this->locator->setRevision(Revision::current()); - $canonicalUrl = ltrim($this->locator->getRepository()->getUrl(), '/').strval($canonicalLocator); - return getenv('APPARAT_BASE_URL').$canonicalUrl; + return $this->locator->toRepositoryUrl(false, true); } /** diff --git a/src/Object/Domain/Model/Object/Traits/SystemPropertiesTrait.php b/src/Object/Domain/Model/Object/Traits/SystemPropertiesTrait.php index 4cf656a..1c7f4a0 100644 --- a/src/Object/Domain/Model/Object/Traits/SystemPropertiesTrait.php +++ b/src/Object/Domain/Model/Object/Traits/SystemPropertiesTrait.php @@ -155,33 +155,11 @@ public function getLatitude() */ public function setLatitude($latitude) { + /** @var SystemProperties $this */ $this->setSystemProperties($this->systemProperties->setLatitude($latitude)); return $this; } - /** - * Set the system properties collection - * - * @param SystemProperties $systemProperties System property collection - * @param bool $overwrite Overwrite the existing collection (if present) - */ - protected function setSystemProperties(SystemProperties $systemProperties, $overwrite = false) - { - $this->systemProperties = $systemProperties; - $systemPropsState = spl_object_hash($this->systemProperties); - - // If the system property collection state has changed - if (!$overwrite - && !empty($this->collectionStates[SystemProperties::COLLECTION]) - && ($systemPropsState !== $this->collectionStates[SystemProperties::COLLECTION]) - ) { - // Flag this object as mutated - $this->setModifiedState(); - } - - $this->collectionStates[SystemProperties::COLLECTION] = $systemPropsState; - } - /** * Return the longitude * @@ -200,6 +178,7 @@ public function getLongitude() */ public function setLongitude($longitude) { + /** @var SystemProperties $this */ $this->setSystemProperties($this->systemProperties->setLongitude($longitude)); return $this; } @@ -222,10 +201,34 @@ public function getElevation() */ public function setElevation($elevation) { + /** @var SystemProperties $this */ $this->setSystemProperties($this->systemProperties->setElevation($elevation)); return $this; } + /** + * Set the system properties collection + * + * @param SystemProperties $systemProperties System property collection + * @param bool $overwrite Overwrite the existing collection (if present) + */ + protected function setSystemProperties(SystemProperties $systemProperties, $overwrite = false) + { + $this->systemProperties = $systemProperties; + $systemPropsState = spl_object_hash($this->systemProperties); + + // If the system property collection state has changed + if (!$overwrite + && !empty($this->collectionStates[SystemProperties::COLLECTION]) + && ($systemPropsState !== $this->collectionStates[SystemProperties::COLLECTION]) + ) { + // Flag this object as mutated + $this->setModifiedState(); + } + + $this->collectionStates[SystemProperties::COLLECTION] = $systemPropsState; + } + /** * Set the object state to modified */ diff --git a/src/Object/Domain/Model/Properties/AbstractProperties.php b/src/Object/Domain/Model/Properties/AbstractProperties.php index bef3175..f58f19c 100644 --- a/src/Object/Domain/Model/Properties/AbstractProperties.php +++ b/src/Object/Domain/Model/Properties/AbstractProperties.php @@ -47,18 +47,6 @@ */ abstract class AbstractProperties implements PropertiesInterface { - /** - * Property data - * - * @var array - */ - protected $data = []; - /** - * Owner object - * - * @var ObjectInterface - */ - protected $object = null; /** * Absolute URL property * @@ -71,6 +59,18 @@ abstract class AbstractProperties implements PropertiesInterface * @var string */ const PROPERTY_CANONICAL_URL = 'canonicalUrl'; + /** + * Property data + * + * @var array + */ + protected $data = []; + /** + * Owner object + * + * @var ObjectInterface + */ + protected $object = null; /** * Meta properties constructor @@ -94,6 +94,49 @@ public function getObject() return $this->object; } + /** + * Return the property values as array + * + * @param bool $serialize Serialize property objects + * @return array Property values + */ + public function toArray($serialize = true) + { + return $this->toSerializedArray($serialize, $this->data); + } + + /** + * Return the potentially serialized property values + * + * @param boolean $serialize Serialize objects + * @param array $data Property values + * @return array Serialized property values + */ + protected function toSerializedArray($serialize, array $data) + { + // Filter all empty values + $data = array_filter($data); + + // If the values should be serialized + if ($serialize) { + // Run through all properties + while (list($property, $value) = each($data)) { + // If the value is an array itself: Recurse + if (is_array($value)) { + $data[$property] = $this->toSerializedArray($serialize, $value); + // Else if the value is serializable + } elseif (is_object($value) && + (new \ReflectionClass($value))->implementsInterface(SerializablePropertyInterface::class) + ) { + /** @var $value SerializablePropertyInterface */ + $data[$property] = $value->serialize(); + } + } + } + + return $data; + } + /** * Normalize and sort a property value list * @@ -188,47 +231,4 @@ protected function mutatePropertiesProperty($property, PropertiesInterface $valu // Else: return self reference return $this; } - - /** - * Return the property values as array - * - * @param bool $serialize Serialize property objects - * @return array Property values - */ - public function toArray($serialize = true) - { - return $this->toSerializedArray($serialize, $this->data); - } - - /** - * Return the potentially serialized property values - * - * @param boolean $serialize Serialize objects - * @param array $data Property values - * @return array Serialized property values - */ - protected function toSerializedArray($serialize, array $data) - { - // Filter all empty values - $data = array_filter($data); - - // If the values should be serialized - if ($serialize) { - // Run through all properties - while (list($property, $value) = each($data)) { - // If the value is an array itself: Recurse - if (is_array($value)) { - $data[$property] = $this->toSerializedArray($serialize, $value); - // Else if the value is serializable - } elseif (is_object($value) && - (new \ReflectionClass($value))->implementsInterface(SerializablePropertyInterface::class) - ) { - /** @var $value SerializablePropertyInterface */ - $data[$property] = $value->serialize(); - } - } - } - - return $data; - } } diff --git a/src/Object/Domain/Model/Properties/SystemProperties.php b/src/Object/Domain/Model/Properties/SystemProperties.php index bcc4ec0..44c1b07 100644 --- a/src/Object/Domain/Model/Properties/SystemProperties.php +++ b/src/Object/Domain/Model/Properties/SystemProperties.php @@ -241,6 +241,16 @@ public function __construct(array $data, ObjectInterface $object) } } + /** + * Return the object draft mode + * + * @return boolean Object draft mode + */ + public function isDraft() + { + return !($this->published instanceof \DateTimeInterface); + } + /** * Return the object ID * @@ -271,16 +281,6 @@ public function getRevision() return $this->revision; } - /** - * Return the object draft mode - * - * @return boolean Object draft mode - */ - public function isDraft() - { - return !($this->published instanceof \DateTimeInterface); - } - /** * Return the object publication state * diff --git a/src/Object/Domain/Model/Uri/ApparatUrl.php b/src/Object/Domain/Model/Uri/ApparatUrl.php index 7cdef36..a227d82 100644 --- a/src/Object/Domain/Model/Uri/ApparatUrl.php +++ b/src/Object/Domain/Model/Uri/ApparatUrl.php @@ -48,13 +48,6 @@ */ class ApparatUrl extends ObjectUrl { - /** - * Valid schemes - * - * @var array - */ - protected static $schemes = [self::SCHEME_APRT => true, self::SCHEME_APRTS => true]; - /** * APRT-Schema * @@ -67,6 +60,12 @@ class ApparatUrl extends ObjectUrl * @var string */ const SCHEME_APRTS = 'aprts'; + /** + * Valid schemes + * + * @var array + */ + protected static $schemes = [self::SCHEME_APRT => true, self::SCHEME_APRTS => true]; /** * Apparat URL constructor diff --git a/src/Object/Domain/Model/Uri/Locator.php b/src/Object/Domain/Model/Uri/Locator.php index a3f1530..0e67eb6 100644 --- a/src/Object/Domain/Model/Uri/Locator.php +++ b/src/Object/Domain/Model/Uri/Locator.php @@ -164,11 +164,67 @@ public function __construct($locator = null, $datePrecision = null, &$leader = ' } /** - * Create and return the object URL locator + * Build the regular expression for matching a local object locator + * + * @param null|boolean|int $datePrecision Date precision [NULL = local default, TRUE = any precision (remote object + * URLs)] + * @return string Regular expression for matching a local object locator + * @throws InvalidArgumentException If the date precision is invalid + */ + protected function buildLocatorRegex($datePrecision) + { + $locatorPattern = null; + + // If a valid integer date precision is given + if (is_int($datePrecision) && ($datePrecision >= 0) && ($datePrecision < 7)) { + $locatorPattern = '%^(?P(/[^/]+)*)?/'. + implode( + '/', + array_slice(self::$datePattern, 0, $datePrecision) + ).($datePrecision ? '/' : ''); + + // Else if the date precision may be arbitrary + } elseif ($datePrecision === true) { + $locatorPattern = '%(?:/'.implode('(?:/', self::$datePattern); + $locatorPattern .= str_repeat(')?', count(self::$datePattern)); + $locatorPattern .= '/'; + } + + // If the date precision is invalid + if ($locatorPattern === null) { + throw new InvalidArgumentException( + sprintf( + 'Invalid date precision "%s" (%s)', + strval($datePrecision), + gettype($datePrecision) + ), + InvalidArgumentException::INVALID_DATE_PRECISION + ); + } + + $locatorPattern .= '(?P\.)?(?P\d+)\-(?P[a-z]+)(?:/(?P\.)?(.*\.)?'; + $locatorPattern .= '\\k(?:-(?P\d+))?(?P\.[a-z0-9]+)?)?$%'; + + return $locatorPattern; + } + + /** + * Serialize the object locator * * @return string Object locator */ public function __toString() + { + return $this->toUrl(false); + } + + /** + * Serialize as relative URL + * + * @param bool $canonical Canonical URL + * @return string Relative URL + */ + public function toUrl($canonical = false) { $locator = []; $datePrecision = intval(getenv('OBJECT_DATE_PRECISION')); @@ -179,11 +235,16 @@ public function __toString() } // Add the object ID and type - $locator[] = ($this->hidden ? '.' : '').$this->uid->getId().'-'.$this->type->getType(); - - // Add the ID, draft mode and revision $uid = $this->uid->getId(); - $locator[] = rtrim(($this->revision->isDraft() ? '.' : '').$uid.'-'.$this->revision->getRevision(), '-'); + $locator[] = ($this->hidden ? '.' : '').$uid; + + // If not only the canonical URL should be returned + if (!$canonical) { + $locator[count($locator) - 1] .= '-'.$this->type->getType(); + + // Add the ID, draft mode and revision + $locator[] = rtrim(($this->revision->isDraft() ? '.' : '').$uid.'-'.$this->revision->getRevision(), '-'); + } return '/'.implode('/', $locator); } @@ -302,49 +363,4 @@ public function setHidden($hidden) $locator->hidden = !!$hidden; return $locator; } - - /** - * Build the regular expression for matching a local object locator - * - * @param null|boolean|int $datePrecision Date precision [NULL = local default, TRUE = any precision (remote object - * URLs)] - * @return string Regular expression for matching a local object locator - * @throws InvalidArgumentException If the date precision is invalid - */ - protected function buildLocatorRegex($datePrecision) - { - $locatorPattern = null; - - // If a valid integer date precision is given - if (is_int($datePrecision) && ($datePrecision >= 0) && ($datePrecision < 7)) { - $locatorPattern = '%^(?P(/[^/]+)*)?/'. - implode( - '/', - array_slice(self::$datePattern, 0, $datePrecision) - ).($datePrecision ? '/' : ''); - - // Else if the date precision may be arbitrary - } elseif ($datePrecision === true) { - $locatorPattern = '%(?:/'.implode('(?:/', self::$datePattern); - $locatorPattern .= str_repeat(')?', count(self::$datePattern)); - $locatorPattern .= '/'; - } - - // If the date precision is invalid - if ($locatorPattern === null) { - throw new InvalidArgumentException( - sprintf( - 'Invalid date precision "%s" (%s)', - strval($datePrecision), - gettype($datePrecision) - ), - InvalidArgumentException::INVALID_DATE_PRECISION - ); - } - - $locatorPattern .= '(?P\.)?(?P\d+)\-(?P[a-z]+)(?:/(?P\.)?(.*\.)?'; - $locatorPattern .= '\\k(?:-(?P\d+))?(?P\.[a-z0-9]+)?)?$%'; - - return $locatorPattern; - } } diff --git a/src/Object/Domain/Model/Uri/LocatorInterface.php b/src/Object/Domain/Model/Uri/LocatorInterface.php index 3998f6e..bb3d565 100644 --- a/src/Object/Domain/Model/Uri/LocatorInterface.php +++ b/src/Object/Domain/Model/Uri/LocatorInterface.php @@ -50,12 +50,20 @@ interface LocatorInterface { /** - * Create and return the object URL locator + * Serialize the object locator * * @return string Object locator */ public function __toString(); + /** + * Serialize as relative URL + * + * @param bool $canonical Canonical URL + * @return string Relative URL + */ + public function toUrl($canonical = false); + /** * Return the object's creation date * diff --git a/src/Object/Domain/Model/Uri/ObjectUrl.php b/src/Object/Domain/Model/Uri/ObjectUrl.php index 070f0ac..2338cbc 100644 --- a/src/Object/Domain/Model/Uri/ObjectUrl.php +++ b/src/Object/Domain/Model/Uri/ObjectUrl.php @@ -285,6 +285,23 @@ public function getRepositoryUrl() return $this->getUrlInternal($override); } + /** + * Return the a complete serialized object URL + * + * @param array $override Override components + * @return string Serialized URL + */ + protected function getUrlInternal(array &$override = []) + { + parent::getUrlInternal($override); + + // Prepare the local object locator + $override['object'] = isset($override['object']) ? + $override['object'] : $this->locator->toUrl(!empty($override['canonical'])); + + return "{$override['scheme']}{$override['user']}{$override['pass']}{$override['host']}{$override['port']}". + "{$override['path']}{$override['object']}{$override['query']}{$override['fragment']}"; + } /** * Return the object hidden state @@ -309,19 +326,14 @@ public function setHidden($hidden) } /** - * Return the a complete serialized object URL + * Serialize as relative URL * - * @param array $override Override components - * @return string Serialized URL + * @param bool $canonical Canonical URL + * @return string Relative URL */ - protected function getUrlInternal(array &$override = []) + public function toUrl($canonical = false) { - parent::getUrlInternal($override); - - // Prepare the local object locator - $override['object'] = isset($override['object']) ? $override['object'] : strval($this->locator); - - return "{$override['scheme']}{$override['user']}{$override['pass']}{$override['host']}{$override['port']}". - "{$override['path']}{$override['object']}{$override['query']}{$override['fragment']}"; + $override = ['canonical' => !!$canonical]; + return $this->getUrlInternal($override); } } diff --git a/src/Object/Domain/Model/Uri/RepositoryLocator.php b/src/Object/Domain/Model/Uri/RepositoryLocator.php index 869c4d7..204bdb0 100644 --- a/src/Object/Domain/Model/Uri/RepositoryLocator.php +++ b/src/Object/Domain/Model/Uri/RepositoryLocator.php @@ -77,23 +77,37 @@ public function __construct(RepositoryInterface $repository, $locator = null) } /** - * Return the repository this locator applies to + * Return the repository relative object locator with a file extension * - * @return RepositoryInterface Repository + * @param string $extension File extension + * @return string Repository relative object locator with extension */ - public function getRepository() + public function withExtension($extension) { - return $this->repository; + return $this.'.'.strtolower($extension); } /** - * Return the repository relative object locator with a file extension + * Serialize as repository URL * - * @param string $extension File extension - * @return string Repository relative object locator with extension + * @param bool $local Local URL only + * @param bool $canonical Canonical URL only + * @return string Repository URL */ - public function withExtension($extension) + public function toRepositoryUrl($local = false, $canonical = false) { - return $this.'.'.strtolower($extension); + $repositoryUrl = $local ? '' : getenv('APPARAT_BASE_URL'); + $repositoryUrl .= $this->getRepository()->getUrl(); + return $repositoryUrl.$this->toUrl($canonical); + } + + /** + * Return the repository this locator applies to + * + * @return RepositoryInterface Repository + */ + public function getRepository() + { + return $this->repository; } } diff --git a/src/Object/Domain/Model/Uri/RepositoryLocatorInterface.php b/src/Object/Domain/Model/Uri/RepositoryLocatorInterface.php index b9b66c4..3c6b3ec 100644 --- a/src/Object/Domain/Model/Uri/RepositoryLocatorInterface.php +++ b/src/Object/Domain/Model/Uri/RepositoryLocatorInterface.php @@ -110,4 +110,13 @@ public function isHidden(); * @return LocatorInterface|Locator New object locator */ public function setHidden($hidden); + + /** + * Serialize as repository URL + * + * @param bool $local Local URL only + * @param bool $canonical Canonical URL only + * @return string Repository URL + */ + public function toRepositoryUrl($local = false, $canonical = false); } diff --git a/src/Object/Domain/Model/Uri/Url.php b/src/Object/Domain/Model/Uri/Url.php index 66f2272..a01c726 100644 --- a/src/Object/Domain/Model/Uri/Url.php +++ b/src/Object/Domain/Model/Uri/Url.php @@ -97,6 +97,17 @@ public function __construct($url) } } + /** + * Unserialize the string representation of this property + * + * @param string $str Serialized property + * @return SerializablePropertyInterface Property + */ + public static function unserialize($str) + { + return Kernel::create(static::class, [$str]); + } + /** * Return the serialized URL * @@ -478,17 +489,6 @@ public function matches(Url $url) return true; } - /** - * Unserialize the string representation of this property - * - * @param string $str Serialized property - * @return SerializablePropertyInterface Property - */ - public static function unserialize($str) - { - return Kernel::create(static::class, [$str]); - } - /** * Serialize the property * diff --git a/src/Object/Domain/Repository/Repository.php b/src/Object/Domain/Repository/Repository.php index 01ab978..2468c8f 100644 --- a/src/Object/Domain/Repository/Repository.php +++ b/src/Object/Domain/Repository/Repository.php @@ -90,7 +90,8 @@ public function __construct( */ public function findObjects(SelectorInterface $selector) { - return Kernel::create(Collection::class, [$this->adapterStrategy->findObjectResourceLocators($selector, $this)]); + return Kernel::create(Collection::class, + [$this->adapterStrategy->findObjectResourceLocators($selector, $this)]); } /** diff --git a/src/Object/Tests/ObjectUrlTest.php b/src/Object/Tests/ObjectUrlTest.php index dcfb3e6..5e1fbb6 100644 --- a/src/Object/Tests/ObjectUrlTest.php +++ b/src/Object/Tests/ObjectUrlTest.php @@ -155,6 +155,7 @@ public function testLeadedLocalUrl() $url = new ObjectUrl($pathPrefix.self::LOCATOR); $this->assertEquals($pathPrefix, $url->getPath()); $this->assertEquals(self::LOCATOR, $url->getLocator()); + $this->assertEquals($pathPrefix.strtok(self::LOCATOR, '-'), $url->toUrl(true)); } /**