mirror of
https://github.com/KevinMidboe/linguist.git
synced 2026-01-09 02:45:33 +00:00
Building an army
This commit is contained in:
1066
test/fixtures/php/Application.php
vendored
Normal file
1066
test/fixtures/php/Application.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
492
test/fixtures/php/Client.php
vendored
Normal file
492
test/fixtures/php/Client.php
vendored
Normal file
@@ -0,0 +1,492 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit;
|
||||
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\DomCrawler\Link;
|
||||
use Symfony\Component\DomCrawler\Form;
|
||||
use Symfony\Component\Process\PhpProcess;
|
||||
|
||||
/**
|
||||
* Client simulates a browser.
|
||||
*
|
||||
* To make the actual request, you need to implement the doRequest() method.
|
||||
*
|
||||
* If you want to be able to run requests in their own process (insulated flag),
|
||||
* you need to also implement the getScript() method.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
abstract class Client
|
||||
{
|
||||
protected $history;
|
||||
protected $cookieJar;
|
||||
protected $server;
|
||||
protected $request;
|
||||
protected $response;
|
||||
protected $crawler;
|
||||
protected $insulated;
|
||||
protected $redirect;
|
||||
protected $followRedirects;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $server The server parameters (equivalent of $_SERVER)
|
||||
* @param History $history A History instance to store the browser history
|
||||
* @param CookieJar $cookieJar A CookieJar instance to store the cookies
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function __construct(array $server = array(), History $history = null, CookieJar $cookieJar = null)
|
||||
{
|
||||
$this->setServerParameters($server);
|
||||
$this->history = null === $history ? new History() : $history;
|
||||
$this->cookieJar = null === $cookieJar ? new CookieJar() : $cookieJar;
|
||||
$this->insulated = false;
|
||||
$this->followRedirects = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to automatically follow redirects or not.
|
||||
*
|
||||
* @param Boolean $followRedirect Whether to follow redirects
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function followRedirects($followRedirect = true)
|
||||
{
|
||||
$this->followRedirects = (Boolean) $followRedirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the insulated flag.
|
||||
*
|
||||
* @param Boolean $insulated Whether to insulate the requests or not
|
||||
*
|
||||
* @throws \RuntimeException When Symfony Process Component is not installed
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function insulate($insulated = true)
|
||||
{
|
||||
if ($insulated && !class_exists('Symfony\\Component\\Process\\Process')) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \RuntimeException('Unable to isolate requests as the Symfony Process Component is not installed.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$this->insulated = (Boolean) $insulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets server parameters.
|
||||
*
|
||||
* @param array $server An array of server parameters
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function setServerParameters(array $server)
|
||||
{
|
||||
$this->server = array_merge(array(
|
||||
'HTTP_HOST' => 'localhost',
|
||||
'HTTP_USER_AGENT' => 'Symfony2 BrowserKit',
|
||||
), $server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets single server parameter.
|
||||
*
|
||||
* @param string $key A key of the parameter
|
||||
* @param string $value A value of the parameter
|
||||
*/
|
||||
public function setServerParameter($key, $value)
|
||||
{
|
||||
$this->server[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets single server parameter for specified key.
|
||||
*
|
||||
* @param string $key A key of the parameter to get
|
||||
* @param string $default A default value when key is undefined
|
||||
*
|
||||
* @return string A value of the parameter
|
||||
*/
|
||||
public function getServerParameter($key, $default = '')
|
||||
{
|
||||
return (isset($this->server[$key])) ? $this->server[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the History instance.
|
||||
*
|
||||
* @return History A History instance
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getHistory()
|
||||
{
|
||||
return $this->history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CookieJar instance.
|
||||
*
|
||||
* @return CookieJar A CookieJar instance
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getCookieJar()
|
||||
{
|
||||
return $this->cookieJar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Crawler instance.
|
||||
*
|
||||
* @return Crawler A Crawler instance
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getCrawler()
|
||||
{
|
||||
return $this->crawler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Response instance.
|
||||
*
|
||||
* @return Response A Response instance
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Request instance.
|
||||
*
|
||||
* @return Request A Request instance
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on a given link.
|
||||
*
|
||||
* @param Link $link A Link instance
|
||||
*
|
||||
* @return Crawler
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function click(Link $link)
|
||||
{
|
||||
if ($link instanceof Form) {
|
||||
return $this->submit($link);
|
||||
}
|
||||
|
||||
return $this->request($link->getMethod(), $link->getUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a form.
|
||||
*
|
||||
* @param Form $form A Form instance
|
||||
* @param array $values An array of form field values
|
||||
*
|
||||
* @return Crawler
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function submit(Form $form, array $values = array())
|
||||
{
|
||||
$form->setValues($values);
|
||||
|
||||
return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a URI.
|
||||
*
|
||||
* @param string $method The request method
|
||||
* @param string $uri The URI to fetch
|
||||
* @param array $parameters The Request parameters
|
||||
* @param array $files The files
|
||||
* @param array $server The server parameters (HTTP headers are referenced with a HTTP_ prefix as PHP does)
|
||||
* @param string $content The raw body data
|
||||
* @param Boolean $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
||||
*
|
||||
* @return Crawler
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function request($method, $uri, array $parameters = array(), array $files = array(), array $server = array(), $content = null, $changeHistory = true)
|
||||
{
|
||||
$uri = $this->getAbsoluteUri($uri);
|
||||
|
||||
$server = array_merge($this->server, $server);
|
||||
if (!$this->history->isEmpty()) {
|
||||
$server['HTTP_REFERER'] = $this->history->current()->getUri();
|
||||
}
|
||||
$server['HTTP_HOST'] = parse_url($uri, PHP_URL_HOST);
|
||||
$server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME);
|
||||
|
||||
$request = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content);
|
||||
|
||||
$this->request = $this->filterRequest($request);
|
||||
|
||||
if (true === $changeHistory) {
|
||||
$this->history->add($request);
|
||||
}
|
||||
|
||||
if ($this->insulated) {
|
||||
$this->response = $this->doRequestInProcess($this->request);
|
||||
} else {
|
||||
$this->response = $this->doRequest($this->request);
|
||||
}
|
||||
|
||||
$response = $this->filterResponse($this->response);
|
||||
|
||||
$this->cookieJar->updateFromResponse($response);
|
||||
|
||||
$this->redirect = $response->getHeader('Location');
|
||||
|
||||
if ($this->followRedirects && $this->redirect) {
|
||||
return $this->crawler = $this->followRedirect();
|
||||
}
|
||||
|
||||
return $this->crawler = $this->createCrawlerFromContent($request->getUri(), $response->getContent(), $response->getHeader('Content-Type'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request in another process.
|
||||
*
|
||||
* @param Request $request A Request instance
|
||||
*
|
||||
* @return Response A Response instance
|
||||
*
|
||||
* @throws \RuntimeException When processing returns exit code
|
||||
*/
|
||||
protected function doRequestInProcess($request)
|
||||
{
|
||||
// We set the TMPDIR (for Macs) and TEMP (for Windows), because on these platforms the temp directory changes based on the user.
|
||||
$process = new PhpProcess($this->getScript($request), null, array('TMPDIR' => sys_get_temp_dir(), 'TEMP' => sys_get_temp_dir()));
|
||||
$process->run();
|
||||
|
||||
if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) {
|
||||
throw new \RuntimeException('OUTPUT: '.$process->getOutput().' ERROR OUTPUT: '.$process->getErrorOutput());
|
||||
}
|
||||
|
||||
return unserialize($process->getOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request.
|
||||
*
|
||||
* @param Request $request A Request instance
|
||||
*
|
||||
* @return Response A Response instance
|
||||
*/
|
||||
abstract protected function doRequest($request);
|
||||
|
||||
/**
|
||||
* Returns the script to execute when the request must be insulated.
|
||||
*
|
||||
* @param Request $request A Request instance
|
||||
*
|
||||
* @throws \LogicException When this abstract class is not implemented
|
||||
*/
|
||||
protected function getScript($request)
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \LogicException('To insulate requests, you need to override the getScript() method.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the request.
|
||||
*
|
||||
* @param Request $request The request to filter
|
||||
*
|
||||
* @return Request
|
||||
*/
|
||||
protected function filterRequest(Request $request)
|
||||
{
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the Response.
|
||||
*
|
||||
* @param Response $response The Response to filter
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function filterResponse($response)
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a crawler.
|
||||
*
|
||||
* This method returns null if the DomCrawler component is not available.
|
||||
*
|
||||
* @param string $uri A uri
|
||||
* @param string $content Content for the crawler to use
|
||||
* @param string $type Content type
|
||||
*
|
||||
* @return Crawler|null
|
||||
*/
|
||||
protected function createCrawlerFromContent($uri, $content, $type)
|
||||
{
|
||||
if (!class_exists('Symfony\Component\DomCrawler\Crawler')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$crawler = new Crawler(null, $uri);
|
||||
$crawler->addContent($content, $type);
|
||||
|
||||
return $crawler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes back in the browser history.
|
||||
*
|
||||
* @return Crawler
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function back()
|
||||
{
|
||||
return $this->requestFromRequest($this->history->back(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes forward in the browser history.
|
||||
*
|
||||
* @return Crawler
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function forward()
|
||||
{
|
||||
return $this->requestFromRequest($this->history->forward(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the current browser.
|
||||
*
|
||||
* @return Crawler
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function reload()
|
||||
{
|
||||
return $this->requestFromRequest($this->history->current(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow redirects?
|
||||
*
|
||||
* @return Crawler
|
||||
*
|
||||
* @throws \LogicException If request was not a redirect
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function followRedirect()
|
||||
{
|
||||
if (empty($this->redirect)) {
|
||||
throw new \LogicException('The request was not redirected.');
|
||||
}
|
||||
|
||||
return $this->request('get', $this->redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the client.
|
||||
*
|
||||
* It flushes history and all cookies.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function restart()
|
||||
{
|
||||
$this->cookieJar->clear();
|
||||
$this->history->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a URI and converts it to absolute if it is not already absolute.
|
||||
*
|
||||
* @param string $uri A uri
|
||||
*
|
||||
* @return string An absolute uri
|
||||
*/
|
||||
protected function getAbsoluteUri($uri)
|
||||
{
|
||||
// already absolute?
|
||||
if (0 === strpos($uri, 'http')) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
if (!$this->history->isEmpty()) {
|
||||
$currentUri = $this->history->current()->getUri();
|
||||
} else {
|
||||
$currentUri = sprintf('http%s://%s/',
|
||||
isset($this->server['HTTPS']) ? 's' : '',
|
||||
isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : 'localhost'
|
||||
);
|
||||
}
|
||||
|
||||
// anchor?
|
||||
if (!$uri || '#' == $uri[0]) {
|
||||
return preg_replace('/#.*?$/', '', $currentUri).$uri;
|
||||
}
|
||||
|
||||
if ('/' !== $uri[0]) {
|
||||
$path = parse_url($currentUri, PHP_URL_PATH);
|
||||
|
||||
if ('/' !== substr($path, -1)) {
|
||||
$path = substr($path, 0, strrpos($path, '/') + 1);
|
||||
}
|
||||
|
||||
$uri = $path.$uri;
|
||||
}
|
||||
|
||||
return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request from a Request object directly.
|
||||
*
|
||||
* @param Request $request A Request instance
|
||||
* @param Boolean $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
||||
*
|
||||
* @return Crawler
|
||||
*/
|
||||
protected function requestFromRequest(Request $request, $changeHistory = true)
|
||||
{
|
||||
return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory);
|
||||
}
|
||||
}
|
||||
1230
test/fixtures/php/Controller.php
vendored
Normal file
1230
test/fixtures/php/Controller.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
586
test/fixtures/php/Form.php
vendored
Normal file
586
test/fixtures/php/Form.php
vendored
Normal file
@@ -0,0 +1,586 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DomCrawler;
|
||||
|
||||
use Symfony\Component\DomCrawler\Field\FormField;
|
||||
|
||||
/**
|
||||
* Form represents an HTML form.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class Form extends Link implements \ArrayAccess
|
||||
{
|
||||
/**
|
||||
* @var \DOMNode
|
||||
*/
|
||||
private $button;
|
||||
/**
|
||||
* @var Field\FormField[]
|
||||
*/
|
||||
private $fields;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \DOMNode $node A \DOMNode instance
|
||||
* @param string $currentUri The URI of the page where the form is embedded
|
||||
* @param string $method The method to use for the link (if null, it defaults to the method defined by the form)
|
||||
*
|
||||
* @throws \LogicException if the node is not a button inside a form tag
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function __construct(\DOMNode $node, $currentUri, $method = null)
|
||||
{
|
||||
parent::__construct($node, $currentUri, $method);
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the form node associated with this form.
|
||||
*
|
||||
* @return \DOMNode A \DOMNode instance
|
||||
*/
|
||||
public function getFormNode()
|
||||
{
|
||||
return $this->node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the fields.
|
||||
*
|
||||
* @param array $values An array of field values
|
||||
*
|
||||
* @return Form
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function setValues(array $values)
|
||||
{
|
||||
foreach ($values as $name => $value) {
|
||||
$this->fields->set($name, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field values.
|
||||
*
|
||||
* The returned array does not include file fields (@see getFiles).
|
||||
*
|
||||
* @return array An array of field values.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
$values = array();
|
||||
foreach ($this->fields->all() as $name => $field) {
|
||||
if ($field->isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$field instanceof Field\FileFormField && $field->hasValue()) {
|
||||
$values[$name] = $field->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file field values.
|
||||
*
|
||||
* @return array An array of file field values.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getFiles()
|
||||
{
|
||||
if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$files = array();
|
||||
|
||||
foreach ($this->fields->all() as $name => $field) {
|
||||
if ($field->isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($field instanceof Field\FileFormField) {
|
||||
$files[$name] = $field->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field values as PHP.
|
||||
*
|
||||
* This method converts fields with the array notation
|
||||
* (like foo[bar] to arrays) like PHP does.
|
||||
*
|
||||
* @return array An array of field values.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getPhpValues()
|
||||
{
|
||||
$qs = http_build_query($this->getValues(), '', '&');
|
||||
parse_str($qs, $values);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file field values as PHP.
|
||||
*
|
||||
* This method converts fields with the array notation
|
||||
* (like foo[bar] to arrays) like PHP does.
|
||||
*
|
||||
* @return array An array of field values.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getPhpFiles()
|
||||
{
|
||||
$qs = http_build_query($this->getFiles(), '', '&');
|
||||
parse_str($qs, $values);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URI of the form.
|
||||
*
|
||||
* The returned URI is not the same as the form "action" attribute.
|
||||
* This method merges the value if the method is GET to mimics
|
||||
* browser behavior.
|
||||
*
|
||||
* @return string The URI
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getUri()
|
||||
{
|
||||
$uri = parent::getUri();
|
||||
|
||||
if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH')) && $queryString = http_build_query($this->getValues(), null, '&')) {
|
||||
$sep = false === strpos($uri, '?') ? '?' : '&';
|
||||
$uri .= $sep.$queryString;
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
protected function getRawUri()
|
||||
{
|
||||
return $this->node->getAttribute('action');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the form method.
|
||||
*
|
||||
* If no method is defined in the form, GET is returned.
|
||||
*
|
||||
* @return string The method
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
if (null !== $this->method) {
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the named field exists.
|
||||
*
|
||||
* @param string $name The field name
|
||||
*
|
||||
* @return Boolean true if the field exists, false otherwise
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return $this->fields->has($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a field from the form.
|
||||
*
|
||||
* @param string $name The field name
|
||||
*
|
||||
* @throws \InvalidArgumentException when the name is malformed
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function remove($name)
|
||||
{
|
||||
$this->fields->remove($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a named field.
|
||||
*
|
||||
* @param string $name The field name
|
||||
*
|
||||
* @return FormField The field instance
|
||||
*
|
||||
* @throws \InvalidArgumentException When field is not present in this form
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
return $this->fields->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a named field.
|
||||
*
|
||||
* @param FormField $field The field
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function set(FormField $field)
|
||||
{
|
||||
$this->fields->add($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all fields.
|
||||
*
|
||||
* @return array An array of fields
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->fields->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the named field exists.
|
||||
*
|
||||
* @param string $name The field name
|
||||
*
|
||||
* @return Boolean true if the field exists, false otherwise
|
||||
*/
|
||||
public function offsetExists($name)
|
||||
{
|
||||
return $this->has($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a field.
|
||||
*
|
||||
* @param string $name The field name
|
||||
*
|
||||
* @return FormField The associated Field instance
|
||||
*
|
||||
* @throws \InvalidArgumentException if the field does not exist
|
||||
*/
|
||||
public function offsetGet($name)
|
||||
{
|
||||
return $this->fields->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a field.
|
||||
*
|
||||
* @param string $name The field name
|
||||
* @param string|array $value The value of the field
|
||||
*
|
||||
* @throws \InvalidArgumentException if the field does not exist
|
||||
*/
|
||||
public function offsetSet($name, $value)
|
||||
{
|
||||
$this->fields->set($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a field from the form.
|
||||
*
|
||||
* @param string $name The field name
|
||||
*/
|
||||
public function offsetUnset($name)
|
||||
{
|
||||
$this->fields->remove($name);
|
||||
}
|
||||
|
||||
protected function setNode(\DOMNode $node)
|
||||
{
|
||||
$this->button = $node;
|
||||
if ('button' == $node->nodeName || ('input' == $node->nodeName && in_array($node->getAttribute('type'), array('submit', 'button', 'image')))) {
|
||||
do {
|
||||
// use the ancestor form element
|
||||
if (null === $node = $node->parentNode) {
|
||||
throw new \LogicException('The selected node does not have a form ancestor.');
|
||||
}
|
||||
} while ('form' != $node->nodeName);
|
||||
} elseif ('form' != $node->nodeName) {
|
||||
throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName));
|
||||
}
|
||||
|
||||
$this->node = $node;
|
||||
}
|
||||
|
||||
private function initialize()
|
||||
{
|
||||
$this->fields = new FormFieldRegistry();
|
||||
|
||||
$document = new \DOMDocument('1.0', 'UTF-8');
|
||||
$node = $document->importNode($this->node, true);
|
||||
$button = $document->importNode($this->button, true);
|
||||
$root = $document->appendChild($document->createElement('_root'));
|
||||
$root->appendChild($node);
|
||||
$root->appendChild($button);
|
||||
$xpath = new \DOMXPath($document);
|
||||
|
||||
foreach ($xpath->query('descendant::input | descendant::textarea | descendant::select', $root) as $node) {
|
||||
if (!$node->hasAttribute('name')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nodeName = $node->nodeName;
|
||||
|
||||
if ($node === $button) {
|
||||
$this->set(new Field\InputFormField($node));
|
||||
} elseif ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == $node->getAttribute('type')) {
|
||||
$this->set(new Field\ChoiceFormField($node));
|
||||
} elseif ('input' == $nodeName && 'radio' == $node->getAttribute('type')) {
|
||||
if ($this->has($node->getAttribute('name'))) {
|
||||
$this->get($node->getAttribute('name'))->addChoice($node);
|
||||
} else {
|
||||
$this->set(new Field\ChoiceFormField($node));
|
||||
}
|
||||
} elseif ('input' == $nodeName && 'file' == $node->getAttribute('type')) {
|
||||
$this->set(new Field\FileFormField($node));
|
||||
} elseif ('input' == $nodeName && !in_array($node->getAttribute('type'), array('submit', 'button', 'image'))) {
|
||||
$this->set(new Field\InputFormField($node));
|
||||
} elseif ('textarea' == $nodeName) {
|
||||
$this->set(new Field\TextareaFormField($node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FormFieldRegistry
|
||||
{
|
||||
private $fields = array();
|
||||
|
||||
private $base;
|
||||
|
||||
/**
|
||||
* Adds a field to the registry.
|
||||
*
|
||||
* @param FormField $field The field
|
||||
*
|
||||
* @throws \InvalidArgumentException when the name is malformed
|
||||
*/
|
||||
public function add(FormField $field)
|
||||
{
|
||||
$segments = $this->getSegments($field->getName());
|
||||
|
||||
$target =& $this->fields;
|
||||
while ($segments) {
|
||||
if (!is_array($target)) {
|
||||
$target = array();
|
||||
}
|
||||
$path = array_shift($segments);
|
||||
if ('' === $path) {
|
||||
$target =& $target[];
|
||||
} else {
|
||||
$target =& $target[$path];
|
||||
}
|
||||
}
|
||||
$target = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a field and its children from the registry.
|
||||
*
|
||||
* @param string $name The fully qualified name of the base field
|
||||
*
|
||||
* @throws \InvalidArgumentException when the name is malformed
|
||||
*/
|
||||
public function remove($name)
|
||||
{
|
||||
$segments = $this->getSegments($name);
|
||||
$target =& $this->fields;
|
||||
while (count($segments) > 1) {
|
||||
$path = array_shift($segments);
|
||||
if (!array_key_exists($path, $target)) {
|
||||
return;
|
||||
}
|
||||
$target =& $target[$path];
|
||||
}
|
||||
unset($target[array_shift($segments)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the field and its children.
|
||||
*
|
||||
* @param string $name The fully qualified name of the field
|
||||
*
|
||||
* @return mixed The value of the field
|
||||
*
|
||||
* @throws \InvalidArgumentException when the name is malformed
|
||||
* @throws \InvalidArgumentException if the field does not exist
|
||||
*/
|
||||
public function &get($name)
|
||||
{
|
||||
$segments = $this->getSegments($name);
|
||||
$target =& $this->fields;
|
||||
while ($segments) {
|
||||
$path = array_shift($segments);
|
||||
if (!array_key_exists($path, $target)) {
|
||||
throw new \InvalidArgumentException(sprintf('Unreachable field "%s"', $path));
|
||||
}
|
||||
$target =& $target[$path];
|
||||
}
|
||||
|
||||
return $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the form has the given field.
|
||||
*
|
||||
* @param string $name The fully qualified name of the field
|
||||
*
|
||||
* @return Boolean Whether the form has the given field
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
try {
|
||||
$this->get($name);
|
||||
|
||||
return true;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a field and its children.
|
||||
*
|
||||
* @param string $name The fully qualified name of the field
|
||||
* @param mixed $value The value
|
||||
*
|
||||
* @throws \InvalidArgumentException when the name is malformed
|
||||
* @throws \InvalidArgumentException if the field does not exist
|
||||
*/
|
||||
public function set($name, $value)
|
||||
{
|
||||
$target =& $this->get($name);
|
||||
if (is_array($value)) {
|
||||
$fields = self::create($name, $value);
|
||||
foreach ($fields->all() as $k => $v) {
|
||||
$this->set($k, $v);
|
||||
}
|
||||
} else {
|
||||
$target->setValue($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of field with their value.
|
||||
*
|
||||
* @return array The list of fields as array((string) Fully qualified name => (mixed) value)
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->walk($this->fields, $this->base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the class.
|
||||
*
|
||||
* This function is made private because it allows overriding the $base and
|
||||
* the $values properties without any type checking.
|
||||
*
|
||||
* @param string $base The fully qualified name of the base field
|
||||
* @param array $values The values of the fields
|
||||
*
|
||||
* @return FormFieldRegistry
|
||||
*/
|
||||
static private function create($base, array $values)
|
||||
{
|
||||
$registry = new static();
|
||||
$registry->base = $base;
|
||||
$registry->fields = $values;
|
||||
|
||||
return $registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a PHP array in a list of fully qualified name / value.
|
||||
*
|
||||
* @param array $array The PHP array
|
||||
* @param string $base The name of the base field
|
||||
* @param array $output The initial values
|
||||
*
|
||||
* @return array The list of fields as array((string) Fully qualified name => (mixed) value)
|
||||
*/
|
||||
private function walk(array $array, $base = '', array &$output = array())
|
||||
{
|
||||
foreach ($array as $k => $v) {
|
||||
$path = empty($base) ? $k : sprintf("%s[%s]", $base, $k);
|
||||
if (is_array($v)) {
|
||||
$this->walk($v, $path, $output);
|
||||
} else {
|
||||
$output[$path] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a field name into segments as a web browser would do.
|
||||
*
|
||||
* <code>
|
||||
* getSegments('base[foo][3][]') = array('base', 'foo, '3', '');
|
||||
* </code>
|
||||
*
|
||||
* @param string $name The name of the field
|
||||
*
|
||||
* @return array The list of segments
|
||||
*
|
||||
* @throws \InvalidArgumentException when the name is malformed
|
||||
*/
|
||||
private function getSegments($name)
|
||||
{
|
||||
if (preg_match('/^(?P<base>[^[]+)(?P<extra>(\[.*)|$)/', $name, $m)) {
|
||||
$segments = array($m['base']);
|
||||
while (preg_match('/^\[(?P<segment>.*?)\](?P<extra>.*)$/', $m['extra'], $m)) {
|
||||
$segments[] = $m['segment'];
|
||||
}
|
||||
|
||||
return $segments;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Malformed field path "%s"', $name));
|
||||
}
|
||||
}
|
||||
3640
test/fixtures/php/Model.php
vendored
Normal file
3640
test/fixtures/php/Model.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user