PageRenderTime 35ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/src/DocBlox/Transformer/Behaviour/Inherit/Node/Abstract.php

https://github.com/hansdubois/Docblox
PHP | 301 lines | 147 code | 31 blank | 123 comment | 23 complexity | 3350347b011c6eb41c46a7defef3ec41 MD5 | raw file
  1. <?php
  2. /**
  3. * DocBlox
  4. *
  5. * PHP Version 5
  6. *
  7. * @category DocBlox
  8. * @package Transformer
  9. * @subpackage Behaviour
  10. * @author Mike van Riel <mike.vanriel@naenius.com>
  11. * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
  12. * @license http://www.opensource.org/licenses/mit-license.php MIT
  13. * @link http://docblox-project.org
  14. */
  15. /**
  16. * Base class for adding inheritance to an element.
  17. *
  18. * @category DocBlox
  19. * @package Transformer
  20. * @subpackage Behaviour
  21. * @author Mike van Riel <mike.vanriel@naenius.com>
  22. * @license http://www.opensource.org/licenses/mit-license.php MIT
  23. * @link http://docblox-project.org
  24. */
  25. abstract class DocBlox_Transformer_Behaviour_Inherit_Node_Abstract
  26. {
  27. /** @var DOMElement */
  28. protected $node = null;
  29. /** @var string[] All class tags that are inherited when none are defined */
  30. protected $inherited_tags = array(
  31. 'version',
  32. 'copyright',
  33. 'author'
  34. );
  35. /**
  36. * Initialize the inheritance for this node.
  37. *
  38. * @param DOMElement $node
  39. */
  40. public function __construct(DOMElement $node)
  41. {
  42. $this->node = $node;
  43. }
  44. /**
  45. * Returns the name of the given method or property node.
  46. *
  47. * @return string
  48. */
  49. protected function getNodeName()
  50. {
  51. return current($this->getDirectElementsByTagName($this->node, 'name'))
  52. ->nodeValue;
  53. }
  54. /**
  55. * Returns the docblock element for the given node; if none exists it will
  56. * be added.
  57. *
  58. * @return DOMElement
  59. */
  60. protected function getDocBlockElement()
  61. {
  62. $docblocks = $this->getDirectElementsByTagName($this->node, 'docblock');
  63. // if this method does not yet have a docblock; add one; even
  64. // though DocBlox throws a warning about a missing DocBlock!
  65. if (count($docblocks) < 1) {
  66. $docblock = new DOMElement('docblock');
  67. $this->node->appendChild($docblock);
  68. } else {
  69. /** @var DOMElement $docblock */
  70. $docblock = reset($docblocks);
  71. }
  72. return $docblock;
  73. }
  74. /**
  75. * Returns the elements with the given tag name that can be found
  76. * as direct children of $node.
  77. *
  78. * getElementsByTagName returns all DOMElements with the given tag name
  79. * regardless where in the DOM subtree they are. This method checks whether
  80. * the parent node matches the given node and thus determines whether it is
  81. * a direct child.
  82. *
  83. * @param DOMElement $node
  84. * @param string $element_name
  85. *
  86. * @return DOMElement[]
  87. */
  88. protected function getDirectElementsByTagName(DOMElement $node, $element_name)
  89. {
  90. $result = array();
  91. if (!$node->hasChildNodes()) {
  92. return $result;
  93. }
  94. $elements = $node->childNodes;
  95. for($i = 0; $i < $elements->length; $i++) {
  96. $element = $elements->item($i);
  97. if ($element->nodeName != $element_name) {
  98. continue;
  99. }
  100. $result[] = $element;
  101. }
  102. return $result;
  103. }
  104. /**
  105. * Copies the short description from the Super element's DocBlock to the
  106. * Sub element's DocBlock if the sub element has none.
  107. *
  108. * @param DOMElement $super_docblock
  109. * @param DOMElement $docblock
  110. *
  111. * @return void
  112. */
  113. public function copyShortDescription(DOMElement $super_docblock,
  114. DOMElement $docblock)
  115. {
  116. /** @var DOMElement $desc */
  117. $desc = current($this->getDirectElementsByTagName($docblock, 'description'));
  118. $super_desc = current(
  119. $this->getDirectElementsByTagName($super_docblock, 'description')
  120. );
  121. if ((($desc === false) || (!trim($desc->nodeValue)))
  122. && ($super_desc !== false)
  123. ) {
  124. if ($desc !== false) {
  125. $docblock->removeChild($desc);
  126. }
  127. $docblock->appendChild(clone $super_desc);
  128. } elseif ($desc && $super_desc) {
  129. // if a short description exists in both child and parent; insert the
  130. // parent's SD when the inline tag {@inheritdoc} is used.
  131. $desc->nodeValue = htmlspecialchars(
  132. str_ireplace(
  133. '{@inheritdoc}',
  134. $super_desc->nodeValue,
  135. $desc->nodeValue
  136. )
  137. );
  138. }
  139. }
  140. /**
  141. * Copies the long description from the Super element's DocBlock to the
  142. * Sub element's DocBlock if the sub element has none.
  143. *
  144. * @param DOMElement $super_docblock
  145. * @param DOMElement $docblock
  146. *
  147. * @return void
  148. */
  149. public function copyLongDescription(DOMElement $super_docblock,
  150. DOMElement $docblock)
  151. {
  152. /** @var DOMElement $desc */
  153. $desc = current(
  154. $this->getDirectElementsByTagName($docblock, 'long-description')
  155. );
  156. $short_desc = current(
  157. $this->getDirectElementsByTagName($docblock, 'description')
  158. );
  159. $super_desc = current(
  160. $this->getDirectElementsByTagName($super_docblock, 'long-description')
  161. );
  162. if ((($desc === false) || (!trim($desc->nodeValue)))
  163. && ($super_desc !== false)
  164. ) {
  165. if ($desc !== false) {
  166. $docblock->removeChild($desc);
  167. }
  168. $docblock->appendChild(clone $super_desc);
  169. } elseif ($desc && $super_desc) {
  170. // if the short description equals {@inheritdoc}, copy the long
  171. // description as well.
  172. if (strtolower($short_desc->nodeValue) == '{@inheritdoc}') {
  173. $desc->nodeValue = $super_desc->nodeValue
  174. . "\n" . $desc->nodeValue;
  175. } else {
  176. // if a long description exists in both child and parent; insert the
  177. // parent's LD when the inline tag {@inheritdoc} is used.
  178. $desc->nodeValue = htmlspecialchars(
  179. str_ireplace(
  180. '{@inheritdoc}',
  181. $super_desc->nodeValue,
  182. $desc->nodeValue
  183. )
  184. );
  185. }
  186. }
  187. }
  188. /**
  189. * Copies the tags from the super docblock to this one if it matches
  190. * the criteria.
  191. *
  192. * Criteria for copying are:
  193. *
  194. * * Tag name must be in the list of to-be-copied tag names
  195. * * No tag with that name may be in the sub element
  196. *
  197. * @param string[] $tag_types array of to-be-copied tag names.
  198. * @param DOMElement $super_docblock DocBlock of the super element.
  199. * @param DOMElement $docblock DocBlock of the sub element.
  200. *
  201. * @return void
  202. */
  203. protected function copyTags(array $tag_types, DOMElement $super_docblock,
  204. DOMElement $docblock)
  205. {
  206. // get the names of all existing tags because we should only add
  207. // parent tags if there are none in the existing docblock
  208. $existing_tag_names = array();
  209. foreach ($this->getDirectElementsByTagName($docblock, 'tag') as $tag)
  210. {
  211. $existing_tag_names[] = $tag->getAttribute('name');
  212. }
  213. $existing_tag_names = array_unique($existing_tag_names);
  214. /** @var DOMElement $tag */
  215. foreach ($this->getDirectElementsByTagName($super_docblock, 'tag') as $tag) {
  216. $tag_name = $tag->getAttribute('name');
  217. if (in_array($tag_name, $tag_types)
  218. && (!in_array($tag_name, $existing_tag_names))
  219. ) {
  220. $child = clone $tag;
  221. $child->setAttribute('line', $this->node->getAttribute('line'));
  222. $docblock->appendChild($child);
  223. }
  224. }
  225. }
  226. /**
  227. * Combines the docblock of an overridden method with this one if applicable.
  228. *
  229. * @param mixed[] $super Array containing a flat list of methods for
  230. * a tree of inherited classes.
  231. * @param string $class_name Name of the current class.
  232. *
  233. * @return void
  234. */
  235. public function apply(array &$super, $class_name)
  236. {
  237. // the name is always the first encountered child element with
  238. // tag name 'name'
  239. $node_name = $this->getNodeName();
  240. // only process if the super has a node with this name
  241. if (isset($super[$node_name])) {
  242. $docblock = $this->getDocBlockElement();
  243. /** @var DOMElement $super_object */
  244. $super_object = $super[$node_name]['object'];
  245. $super_class = $super[$node_name]['class'];
  246. // add an element which defines which class' element you override
  247. $this->node->appendChild(new DOMElement('overrides-from', $super_class));
  248. /** @var DOMElement $super_docblock */
  249. $super_docblock = current(
  250. $this->getDirectElementsByTagName($super_object, 'docblock')
  251. );
  252. // only copy the docblock info when it is present in the superclass
  253. if ($super_docblock)
  254. {
  255. // first long, then short in order for the {@inheritdoc} to
  256. // function properly
  257. $this->copyLongDescription($super_docblock, $docblock);
  258. $this->copyShortDescription($super_docblock, $docblock);
  259. $this->copyTags($this->inherited_tags, $super_docblock, $docblock);
  260. }
  261. }
  262. // store the element in the super array; we use this in the Class
  263. // inheritance to add 'inherited' methods (methods not present in this
  264. // class by definition but injected via a superclass)
  265. $super[$node_name] = array(
  266. 'class' => $class_name,
  267. 'object' => $this->node
  268. );
  269. }
  270. }