PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/Admin/AdminHelper.php

http://github.com/sonata-project/SonataAdminBundle
PHP | 329 lines | 165 code | 50 blank | 114 comment | 18 complexity | b7fd9036650ef0918b08525aae552846 MD5 | raw file
Possible License(s): JSON, Apache-2.0, MIT
  1. <?php
  2. /*
  3. * This file is part of the Sonata Project package.
  4. *
  5. * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Sonata\AdminBundle\Admin;
  11. use Doctrine\Common\Inflector\Inflector;
  12. use Doctrine\Common\Util\ClassUtils;
  13. use Sonata\AdminBundle\Exception\NoValueException;
  14. use Sonata\AdminBundle\Util\FormBuilderIterator;
  15. use Sonata\AdminBundle\Util\FormViewIterator;
  16. use Symfony\Component\Form\FormBuilderInterface;
  17. use Symfony\Component\Form\FormView;
  18. use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
  19. use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
  20. use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
  21. /**
  22. * Class AdminHelper.
  23. *
  24. * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  25. */
  26. class AdminHelper
  27. {
  28. /**
  29. * @var Pool
  30. */
  31. protected $pool;
  32. /**
  33. * @param Pool $pool
  34. */
  35. public function __construct(Pool $pool)
  36. {
  37. $this->pool = $pool;
  38. }
  39. /**
  40. * @throws \RuntimeException
  41. *
  42. * @param FormBuilderInterface $formBuilder
  43. * @param string $elementId
  44. *
  45. * @return FormBuilderInterface
  46. */
  47. public function getChildFormBuilder(FormBuilderInterface $formBuilder, $elementId)
  48. {
  49. foreach (new FormBuilderIterator($formBuilder) as $name => $formBuilder) {
  50. if ($name == $elementId) {
  51. return $formBuilder;
  52. }
  53. }
  54. return;
  55. }
  56. /**
  57. * @param FormView $formView
  58. * @param string $elementId
  59. *
  60. * @return null|FormView
  61. */
  62. public function getChildFormView(FormView $formView, $elementId)
  63. {
  64. foreach (new \RecursiveIteratorIterator(new FormViewIterator($formView), \RecursiveIteratorIterator::SELF_FIRST) as $name => $formView) {
  65. if ($name === $elementId) {
  66. return $formView;
  67. }
  68. }
  69. return;
  70. }
  71. /**
  72. * @deprecated
  73. *
  74. * @param string $code
  75. *
  76. * @return AdminInterface
  77. */
  78. public function getAdmin($code)
  79. {
  80. return $this->pool->getInstance($code);
  81. }
  82. /**
  83. * Note:
  84. * This code is ugly, but there is no better way of doing it.
  85. * For now the append form element action used to add a new row works
  86. * only for direct FieldDescription (not nested one).
  87. *
  88. * @throws \RuntimeException
  89. *
  90. * @param AdminInterface $admin
  91. * @param object $subject
  92. * @param string $elementId
  93. *
  94. * @return array
  95. */
  96. public function appendFormFieldElement(AdminInterface $admin, $subject, $elementId)
  97. {
  98. // retrieve the subject
  99. $formBuilder = $admin->getFormBuilder();
  100. $form = $formBuilder->getForm();
  101. $form->setData($subject);
  102. $form->handleRequest($admin->getRequest());
  103. // get the field element
  104. $childFormBuilder = $this->getChildFormBuilder($formBuilder, $elementId);
  105. //Child form not found (probably nested one)
  106. //if childFormBuilder was not found resulted in fatal error getName() method call on non object
  107. if (!$childFormBuilder) {
  108. $propertyAccessor = $this->pool->getPropertyAccessor();
  109. $entity = $admin->getSubject();
  110. $path = $this->getElementAccessPath($elementId, $entity);
  111. $collection = $propertyAccessor->getValue($entity, $path);
  112. if ($collection instanceof \Doctrine\ORM\PersistentCollection || $collection instanceof \Doctrine\ODM\MongoDB\PersistentCollection) {
  113. //since doctrine 2.4
  114. $entityClassName = $collection->getTypeClass()->getName();
  115. } elseif ($collection instanceof \Doctrine\Common\Collections\Collection) {
  116. $entityClassName = $this->getEntityClassName($admin, explode('.', preg_replace('#\[\d*?\]#', '', $path)));
  117. } else {
  118. throw new \Exception('unknown collection class');
  119. }
  120. $collection->add(new $entityClassName());
  121. $propertyAccessor->setValue($entity, $path, $collection);
  122. $fieldDescription = null;
  123. } else {
  124. // retrieve the FieldDescription
  125. $fieldDescription = $admin->getFormFieldDescription($childFormBuilder->getName());
  126. try {
  127. $value = $fieldDescription->getValue($form->getData());
  128. } catch (NoValueException $e) {
  129. $value = null;
  130. }
  131. // retrieve the posted data
  132. $data = $admin->getRequest()->get($formBuilder->getName());
  133. if (!isset($data[$childFormBuilder->getName()])) {
  134. $data[$childFormBuilder->getName()] = array();
  135. }
  136. $objectCount = count($value);
  137. $postCount = count($data[$childFormBuilder->getName()]);
  138. $fields = array_keys($fieldDescription->getAssociationAdmin()->getFormFieldDescriptions());
  139. // for now, not sure how to do that
  140. $value = array();
  141. foreach ($fields as $name) {
  142. $value[$name] = '';
  143. }
  144. // add new elements to the subject
  145. while ($objectCount < $postCount) {
  146. // append a new instance into the object
  147. $this->addNewInstance($form->getData(), $fieldDescription);
  148. ++$objectCount;
  149. }
  150. $this->addNewInstance($form->getData(), $fieldDescription);
  151. }
  152. $finalForm = $admin->getFormBuilder()->getForm();
  153. $finalForm->setData($subject);
  154. // bind the data
  155. $finalForm->setData($form->getData());
  156. return array($fieldDescription, $finalForm);
  157. }
  158. /**
  159. * Add a new instance to the related FieldDescriptionInterface value.
  160. *
  161. * @param object $object
  162. * @param FieldDescriptionInterface $fieldDescription
  163. *
  164. * @throws \RuntimeException
  165. */
  166. public function addNewInstance($object, FieldDescriptionInterface $fieldDescription)
  167. {
  168. $instance = $fieldDescription->getAssociationAdmin()->getNewInstance();
  169. $mapping = $fieldDescription->getAssociationMapping();
  170. $method = sprintf('add%s', Inflector::classify($mapping['fieldName']));
  171. if (!method_exists($object, $method)) {
  172. $method = rtrim($method, 's');
  173. if (!method_exists($object, $method)) {
  174. $method = sprintf('add%s', Inflector::classify(Inflector::singularize($mapping['fieldName'])));
  175. if (!method_exists($object, $method)) {
  176. throw new \RuntimeException(sprintf('Please add a method %s in the %s class!', $method, ClassUtils::getClass($object)));
  177. }
  178. }
  179. }
  180. $object->$method($instance);
  181. }
  182. /**
  183. * Camelize a string.
  184. *
  185. * @static
  186. *
  187. * @param string $property
  188. *
  189. * @return string
  190. *
  191. * @deprecated Deprecated since version 3.1. Use \Doctrine\Common\Inflector\Inflector::classify() instead
  192. */
  193. public function camelize($property)
  194. {
  195. @trigger_error(
  196. sprintf(
  197. 'The %s method is deprecated since 3.1 and will be removed in 4.0. '.
  198. 'Use \Doctrine\Common\Inflector\Inflector::classify() instead.',
  199. __METHOD__
  200. ),
  201. E_USER_DEPRECATED
  202. );
  203. return Inflector::classify($property);
  204. }
  205. /**
  206. * Get access path to element which works with PropertyAccessor.
  207. *
  208. * @param string $elementId expects string in format used in form id field. (uniqueIdentifier_model_sub_model or uniqueIdentifier_model_1_sub_model etc.)
  209. * @param mixed $entity
  210. *
  211. * @return string
  212. *
  213. * @throws \Exception
  214. */
  215. public function getElementAccessPath($elementId, $entity)
  216. {
  217. $propertyAccessor = $this->pool->getPropertyAccessor();
  218. $idWithoutIdentifier = preg_replace('/^[^_]*_/', '', $elementId);
  219. $initialPath = preg_replace('#(_(\d+)_)#', '[$2]_', $idWithoutIdentifier);
  220. $parts = explode('_', $initialPath);
  221. $totalPath = '';
  222. $currentPath = '';
  223. foreach ($parts as $part) {
  224. $currentPath .= empty($currentPath) ? $part : '_'.$part;
  225. $separator = empty($totalPath) ? '' : '.';
  226. if ($this->pathExists($propertyAccessor, $entity, $totalPath.$separator.$currentPath)) {
  227. $totalPath .= $separator.$currentPath;
  228. $currentPath = '';
  229. }
  230. }
  231. if (!empty($currentPath)) {
  232. throw new \Exception(sprintf('Could not get element id from %s Failing part: %s', $elementId, $currentPath));
  233. }
  234. return $totalPath;
  235. }
  236. /**
  237. * Recursively find the class name of the admin responsible for the element at the end of an association chain.
  238. *
  239. * @param AdminInterface $admin
  240. * @param array $elements
  241. *
  242. * @return string
  243. */
  244. protected function getEntityClassName(AdminInterface $admin, $elements)
  245. {
  246. $element = array_shift($elements);
  247. $associationAdmin = $admin->getFormFieldDescription($element)->getAssociationAdmin();
  248. if (count($elements) == 0) {
  249. return $associationAdmin->getClass();
  250. }
  251. return $this->getEntityClassName($associationAdmin, $elements);
  252. }
  253. /**
  254. * Check if given path exists in $entity.
  255. *
  256. * @param PropertyAccessorInterface $propertyAccessor
  257. * @param mixed $entity
  258. * @param string $path
  259. *
  260. * @return bool
  261. *
  262. * @throws \RuntimeException
  263. */
  264. private function pathExists(PropertyAccessorInterface $propertyAccessor, $entity, $path)
  265. {
  266. // Symfony <= 2.3 did not have isReadable method for PropertyAccessor
  267. if (method_exists($propertyAccessor, 'isReadable')) {
  268. return $propertyAccessor->isReadable($entity, $path);
  269. }
  270. try {
  271. $propertyAccessor->getValue($entity, $path);
  272. return true;
  273. } catch (NoSuchPropertyException $e) {
  274. return false;
  275. } catch (UnexpectedTypeException $e) {
  276. return false;
  277. }
  278. }
  279. }