Annotation.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. <?php
  2. namespace Gedmo\Sluggable\Mapping\Driver;
  3. use Gedmo\Mapping\Annotation\SlugHandler;
  4. use Gedmo\Mapping\Annotation\SlugHandlerOption;
  5. use Gedmo\Mapping\Driver\AbstractAnnotationDriver;
  6. use Gedmo\Exception\InvalidMappingException;
  7. /**
  8. * This is an annotation mapping driver for Sluggable
  9. * behavioral extension. Used for extraction of extended
  10. * metadata from Annotations specifically for Sluggable
  11. * extension.
  12. *
  13. * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  14. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  15. */
  16. class Annotation extends AbstractAnnotationDriver
  17. {
  18. /**
  19. * Annotation to identify field as one which holds the slug
  20. * together with slug options
  21. */
  22. const SLUG = 'Gedmo\\Mapping\\Annotation\\Slug';
  23. /**
  24. * SlugHandler extension annotation
  25. */
  26. const HANDLER = 'Gedmo\\Mapping\\Annotation\\SlugHandler';
  27. /**
  28. * SlugHandler option annotation
  29. */
  30. const HANDLER_OPTION = 'Gedmo\\Mapping\\Annotation\\SlugHandlerOption';
  31. /**
  32. * List of types which are valid for slug and sluggable fields
  33. *
  34. * @var array
  35. */
  36. protected $validTypes = array(
  37. 'string',
  38. 'text',
  39. 'integer',
  40. 'int',
  41. 'datetime',
  42. 'citext',
  43. );
  44. /**
  45. * {@inheritDoc}
  46. */
  47. public function readExtendedMetadata($meta, array &$config)
  48. {
  49. $class = $this->getMetaReflectionClass($meta);
  50. // property annotations
  51. foreach ($class->getProperties() as $property) {
  52. if ($meta->isMappedSuperclass && !$property->isPrivate() ||
  53. $meta->isInheritedField($property->name) ||
  54. isset($meta->associationMappings[$property->name]['inherited'])
  55. ) {
  56. continue;
  57. }
  58. $config = $this->retrieveSlug($meta, $config, $property, '');
  59. }
  60. // Embedded entity
  61. if (property_exists($meta, 'embeddedClasses') && $meta->embeddedClasses) {
  62. foreach ($meta->embeddedClasses as $propertyName => $embeddedClassInfo) {
  63. $embeddedClass = new \ReflectionClass($embeddedClassInfo['class']);
  64. foreach ($embeddedClass->getProperties() as $embeddedProperty) {
  65. $config = $this->retrieveSlug($meta, $config, $embeddedProperty, $propertyName);
  66. }
  67. }
  68. }
  69. return $config;
  70. }
  71. /**
  72. * @param $meta
  73. * @param array $config
  74. * @param $property
  75. * @param $fieldNamePrefix
  76. * @return array
  77. */
  78. private function retrieveSlug($meta, array &$config, $property, $fieldNamePrefix)
  79. {
  80. $fieldName = $fieldNamePrefix ? ($fieldNamePrefix . '.' . $property->getName()) : $property->getName();
  81. // slug property
  82. if ($slug = $this->reader->getPropertyAnnotation($property, self::SLUG)) {
  83. if (!$meta->hasField($fieldName)) {
  84. throw new InvalidMappingException("Unable to find slug [{$fieldName}] as mapped property in entity - {$meta->name}");
  85. }
  86. if (!$this->isValidField($meta, $fieldName)) {
  87. throw new InvalidMappingException("Cannot use field - [{$fieldName}] for slug storage, type is not valid and must be 'string' or 'text' in class - {$meta->name}");
  88. }
  89. // process slug handlers
  90. $handlers = array();
  91. if (is_array($slug->handlers) && $slug->handlers) {
  92. foreach ($slug->handlers as $handler) {
  93. if (!$handler instanceof SlugHandler) {
  94. throw new InvalidMappingException("SlugHandler: {$handler} should be instance of SlugHandler annotation in entity - {$meta->name}");
  95. }
  96. if (!strlen($handler->class)) {
  97. throw new InvalidMappingException("SlugHandler class: {$handler->class} should be a valid class name in entity - {$meta->name}");
  98. }
  99. $class = $handler->class;
  100. $handlers[$class] = array();
  101. foreach ((array)$handler->options as $option) {
  102. if (!$option instanceof SlugHandlerOption) {
  103. throw new InvalidMappingException("SlugHandlerOption: {$option} should be instance of SlugHandlerOption annotation in entity - {$meta->name}");
  104. }
  105. if (!strlen($option->name)) {
  106. throw new InvalidMappingException("SlugHandlerOption name: {$option->name} should be valid name in entity - {$meta->name}");
  107. }
  108. $handlers[$class][$option->name] = $option->value;
  109. }
  110. $class::validate($handlers[$class], $meta);
  111. }
  112. }
  113. // process slug fields
  114. if (empty($slug->fields) || !is_array($slug->fields)) {
  115. throw new InvalidMappingException("Slug must contain at least one field for slug generation in class - {$meta->name}");
  116. }
  117. foreach ($slug->fields as $slugField) {
  118. $slugFieldWithPrefix = $fieldNamePrefix ? ($fieldNamePrefix . '.' . $slugField) : $slugField;
  119. if (!$meta->hasField($slugFieldWithPrefix)) {
  120. throw new InvalidMappingException("Unable to find slug [{$slugFieldWithPrefix}] as mapped property in entity - {$meta->name}");
  121. }
  122. if (!$this->isValidField($meta, $slugFieldWithPrefix)) {
  123. throw new InvalidMappingException("Cannot use field - [{$slugFieldWithPrefix}] for slug storage, type is not valid and must be 'string' or 'text' in class - {$meta->name}");
  124. }
  125. }
  126. if (!is_bool($slug->updatable)) {
  127. throw new InvalidMappingException("Slug annotation [updatable], type is not valid and must be 'boolean' in class - {$meta->name}");
  128. }
  129. if (!is_bool($slug->unique)) {
  130. throw new InvalidMappingException("Slug annotation [unique], type is not valid and must be 'boolean' in class - {$meta->name}");
  131. }
  132. if (!empty($meta->identifier) && $meta->isIdentifier($fieldName) && !(bool)$slug->unique) {
  133. throw new InvalidMappingException("Identifier field - [{$fieldName}] slug must be unique in order to maintain primary key in class - {$meta->name}");
  134. }
  135. if ($slug->unique === false && $slug->unique_base) {
  136. throw new InvalidMappingException("Slug annotation [unique_base] can not be set if unique is unset or 'false'");
  137. }
  138. if ($slug->unique_base && !$meta->hasField($slug->unique_base) && !$meta->hasAssociation($slug->unique_base)) {
  139. throw new InvalidMappingException("Unable to find [{$slug->unique_base}] as mapped property in entity - {$meta->name}");
  140. }
  141. $sluggableFields = array();
  142. foreach ($slug->fields as $field) {
  143. $sluggableFields[] = $fieldNamePrefix ? ($fieldNamePrefix . '.' . $field) : $field;
  144. }
  145. // set all options
  146. $config['slugs'][$fieldName] = array(
  147. 'fields' => $sluggableFields,
  148. 'slug' => $fieldName,
  149. 'style' => $slug->style,
  150. 'dateFormat' => $slug->dateFormat,
  151. 'updatable' => $slug->updatable,
  152. 'unique' => $slug->unique,
  153. 'unique_base' => $slug->unique_base,
  154. 'separator' => $slug->separator,
  155. 'prefix' => $slug->prefix,
  156. 'suffix' => $slug->suffix,
  157. 'handlers' => $handlers,
  158. );
  159. }
  160. return $config;
  161. }
  162. }