Form.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  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 Symfony\Component\DomCrawler;
  11. use Symfony\Component\DomCrawler\Field\FormField;
  12. /**
  13. * Form represents an HTML form.
  14. *
  15. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  16. */
  17. class Form implements \ArrayAccess
  18. {
  19. protected $document;
  20. protected $button;
  21. protected $node;
  22. protected $fields;
  23. protected $method;
  24. protected $host;
  25. protected $path;
  26. protected $base;
  27. /**
  28. * Constructor.
  29. *
  30. * @param \DOMNode $node A \DOMNode instance
  31. * @param string $method The method to use for the link (if null, it defaults to the method defined by the form)
  32. * @param string $host The base URI to use for absolute links (like http://localhost)
  33. * @param string $path The base path for relative links (/ by default)
  34. * @param string $base An optional base href for generating the submit uri
  35. *
  36. * @throws \LogicException if the node is not a button inside a form tag
  37. */
  38. public function __construct(\DOMNode $node, $method = null, $host = null, $path = '/', $base = null)
  39. {
  40. $this->button = $node;
  41. if ('button' == $node->nodeName || ('input' == $node->nodeName && in_array($node->getAttribute('type'), array('submit', 'button', 'image')))) {
  42. do {
  43. // use the ancestor form element
  44. if (null === $node = $node->parentNode) {
  45. throw new \LogicException('The selected node does not have a form ancestor.');
  46. }
  47. } while ('form' != $node->nodeName);
  48. } else {
  49. throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName));
  50. }
  51. $this->node = $node;
  52. $this->method = $method;
  53. $this->host = $host;
  54. $this->path = empty($path) ? '/' : $path;
  55. $this->base = $base;
  56. $this->initialize();
  57. }
  58. /**
  59. * Gets the form node associated with this form.
  60. *
  61. * @return \DOMNode A \DOMNode instance
  62. */
  63. public function getFormNode()
  64. {
  65. return $this->node;
  66. }
  67. /**
  68. * Sets the value of the fields.
  69. *
  70. * @param array $values An array of field values
  71. */
  72. public function setValues(array $values)
  73. {
  74. foreach ($values as $name => $value) {
  75. $this[$name] = $value;
  76. }
  77. return $this;
  78. }
  79. /**
  80. * Gets the field values.
  81. *
  82. * The returned array does not include file fields (@see getFiles).
  83. *
  84. * @return array An array of field values.
  85. */
  86. public function getValues()
  87. {
  88. $values = array();
  89. foreach ($this->fields as $name => $field) {
  90. if (!$field instanceof Field\FileFormField && $field->hasValue()) {
  91. $values[$name] = $field->getValue();
  92. }
  93. }
  94. return $values;
  95. }
  96. /**
  97. * Gets the file field values.
  98. *
  99. * @return array An array of file field values.
  100. */
  101. public function getFiles()
  102. {
  103. if (!in_array($this->getMethod(), array('post', 'put', 'delete'))) {
  104. return array();
  105. }
  106. $files = array();
  107. foreach ($this->fields as $name => $field) {
  108. if ($field instanceof Field\FileFormField) {
  109. $files[$name] = $field->getValue();
  110. }
  111. }
  112. return $files;
  113. }
  114. /**
  115. * Gets the field values as PHP.
  116. *
  117. * This method converts fields with th array notation
  118. * (like foo[bar] to arrays) like PHP does.
  119. *
  120. * @return array An array of field values.
  121. */
  122. public function getPhpValues()
  123. {
  124. $qs = http_build_query($this->getValues());
  125. parse_str($qs, $values);
  126. return $values;
  127. }
  128. /**
  129. * Gets the file field values as PHP.
  130. *
  131. * This method converts fields with th array notation
  132. * (like foo[bar] to arrays) like PHP does.
  133. *
  134. * @return array An array of field values.
  135. */
  136. public function getPhpFiles()
  137. {
  138. $qs = http_build_query($this->getFiles());
  139. parse_str($qs, $values);
  140. return $values;
  141. }
  142. /**
  143. * Gets the URI of the form.
  144. *
  145. * The returned URI is not the same as the form "action" attribute.
  146. * This method merges the value if the method is GET to mimics
  147. * browser behavior.
  148. *
  149. * @param Boolean $absolute Whether to return an absolute URI or not (this only works if a base URI has been provided)
  150. *
  151. * @return string The URI
  152. */
  153. public function getUri($absolute = true)
  154. {
  155. $uri = $this->node->getAttribute('action');
  156. $urlHaveScheme = 'http' === substr($uri, 0, 4);
  157. if (!$uri || '#' === $uri) {
  158. $uri = $this->path;
  159. }
  160. if (!in_array($this->getMethod(), array('post', 'put', 'delete')) && $queryString = http_build_query($this->getValues(), null, '&')) {
  161. $sep = false === strpos($uri, '?') ? '?' : '&';
  162. $uri .= $sep.$queryString;
  163. }
  164. $path = $this->path;
  165. if ('?' !== substr($uri, 0, 1) && '/' !== substr($path, -1)) {
  166. $path = substr($path, 0, strrpos($path, '/') + 1);
  167. }
  168. if (!$this->base && $uri && '/' !== $uri[0] && !$urlHaveScheme) {
  169. $uri = $path.$uri;
  170. } elseif ($this->base) {
  171. $uri = $this->base.$uri;
  172. }
  173. if (!$this->base && $absolute && null !== $this->host && !$urlHaveScheme) {
  174. return $this->host.$uri;
  175. }
  176. return $uri;
  177. }
  178. /**
  179. * Gets the form method.
  180. *
  181. * If no method is defined in the form, GET is returned.
  182. *
  183. * @return string The method
  184. */
  185. public function getMethod()
  186. {
  187. if (null !== $this->method) {
  188. return $this->method;
  189. }
  190. return $this->node->getAttribute('method') ? strtolower($this->node->getAttribute('method')) : 'get';
  191. }
  192. /**
  193. * Returns true if the named field exists.
  194. *
  195. * @param string $name The field name
  196. *
  197. * @return Boolean true if the field exists, false otherwise
  198. */
  199. public function has($name)
  200. {
  201. return isset($this->fields[$name]);
  202. }
  203. /**
  204. * Gets a named field.
  205. *
  206. * @param string $name The field name
  207. *
  208. * @return FormField The field instance
  209. *
  210. * @throws \InvalidArgumentException When field is not present in this form
  211. */
  212. public function get($name)
  213. {
  214. if (!$this->has($name)) {
  215. throw new \InvalidArgumentException(sprintf('The form has no "%s" field', $name));
  216. }
  217. return $this->fields[$name];
  218. }
  219. /**
  220. * Sets a named field.
  221. *
  222. * @param string $name The field name
  223. *
  224. * @return FormField The field instance
  225. */
  226. public function set(Field\FormField $field)
  227. {
  228. $this->fields[$field->getName()] = $field;
  229. }
  230. /**
  231. * Gets all fields.
  232. *
  233. * @return array An array of fields
  234. */
  235. public function all()
  236. {
  237. return $this->fields;
  238. }
  239. protected function initialize()
  240. {
  241. $this->fields = array();
  242. $document = new \DOMDocument('1.0', 'UTF-8');
  243. $node = $document->importNode($this->node, true);
  244. $button = $document->importNode($this->button, true);
  245. $root = $document->appendChild($document->createElement('_root'));
  246. $root->appendChild($node);
  247. $root->appendChild($button);
  248. $xpath = new \DOMXPath($document);
  249. foreach ($xpath->query('descendant::input | descendant::textarea | descendant::select', $root) as $node) {
  250. if ($node->hasAttribute('disabled') || !$node->hasAttribute('name')) {
  251. continue;
  252. }
  253. $nodeName = $node->nodeName;
  254. if ($node === $button) {
  255. $this->set(new Field\InputFormField($node));
  256. } elseif ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == $node->getAttribute('type')) {
  257. $this->set(new Field\ChoiceFormField($node));
  258. } elseif ('input' == $nodeName && 'radio' == $node->getAttribute('type')) {
  259. if ($this->has($node->getAttribute('name'))) {
  260. $this->get($node->getAttribute('name'))->addChoice($node);
  261. } else {
  262. $this->set(new Field\ChoiceFormField($node));
  263. }
  264. } elseif ('input' == $nodeName && 'file' == $node->getAttribute('type')) {
  265. $this->set(new Field\FileFormField($node));
  266. } elseif ('input' == $nodeName && !in_array($node->getAttribute('type'), array('submit', 'button', 'image'))) {
  267. $this->set(new Field\InputFormField($node));
  268. } elseif ('textarea' == $nodeName) {
  269. $this->set(new Field\TextareaFormField($node));
  270. }
  271. }
  272. }
  273. /**
  274. * Returns true if the named field exists.
  275. *
  276. * @param string $name The field name
  277. *
  278. * @return Boolean true if the field exists, false otherwise
  279. */
  280. public function offsetExists($name)
  281. {
  282. return $this->has($name);
  283. }
  284. /**
  285. * Gets the value of a field.
  286. *
  287. * @param string $name The field name
  288. *
  289. * @return FormField The associated Field instance
  290. *
  291. * @throws \InvalidArgumentException if the field does not exist
  292. */
  293. public function offsetGet($name)
  294. {
  295. if (!$this->has($name)) {
  296. throw new \InvalidArgumentException(sprintf('The form field "%s" does not exist', $name));
  297. }
  298. return $this->fields[$name];
  299. }
  300. /**
  301. * Sets the value of a field.
  302. *
  303. * @param string $name The field name
  304. * @param string|array $value The value of the field
  305. *
  306. * @throws \InvalidArgumentException if the field does not exist
  307. */
  308. public function offsetSet($name, $value)
  309. {
  310. if (!$this->has($name)) {
  311. throw new \InvalidArgumentException(sprintf('The form field "%s" does not exist', $name));
  312. }
  313. $this->fields[$name]->setValue($value);
  314. }
  315. /**
  316. * Unimplemented.
  317. *
  318. * @param string $name The field name
  319. */
  320. public function offsetUnset($name)
  321. {
  322. throw new \LogicException('The Form fields cannot be removed.');
  323. }
  324. }