vendor/symfony/src/Symfony/Bridge/Twig/Extension/FormExtension.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * This file is part of the Symfony package.
       
     5  *
       
     6  * (c) Fabien Potencier <fabien@symfony.com>
       
     7  *
       
     8  * For the full copyright and license information, please view the LICENSE
       
     9  * file that was distributed with this source code.
       
    10  */
       
    11 
       
    12 namespace Symfony\Bridge\Twig\Extension;
       
    13 
       
    14 use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
       
    15 use Symfony\Component\Form\FormView;
       
    16 use Symfony\Component\Form\Exception\FormException;
       
    17 use Symfony\Component\Form\Util\FormUtil;
       
    18 
       
    19 /**
       
    20  * FormExtension extends Twig with form capabilities.
       
    21  *
       
    22  * @author Fabien Potencier <fabien@symfony.com>
       
    23  * @author Bernhard Schussek <bernhard.schussek@symfony.com>
       
    24  */
       
    25 class FormExtension extends \Twig_Extension
       
    26 {
       
    27     protected $resources;
       
    28     protected $blocks;
       
    29     protected $environment;
       
    30     protected $themes;
       
    31     protected $varStack;
       
    32     protected $template;
       
    33 
       
    34     public function __construct(array $resources = array())
       
    35     {
       
    36         $this->themes = new \SplObjectStorage();
       
    37         $this->varStack = array();
       
    38         $this->blocks = new \SplObjectStorage();
       
    39         $this->resources = $resources;
       
    40     }
       
    41 
       
    42     /**
       
    43      * {@inheritdoc}
       
    44      */
       
    45     public function initRuntime(\Twig_Environment $environment)
       
    46     {
       
    47         $this->environment = $environment;
       
    48     }
       
    49 
       
    50     /**
       
    51      * Sets a theme for a given view.
       
    52      *
       
    53      * @param FormView $view      A FormView instance
       
    54      * @param array    $resources An array of resources
       
    55      */
       
    56     public function setTheme(FormView $view, array $resources)
       
    57     {
       
    58         $this->themes->attach($view, $resources);
       
    59         $this->blocks = new \SplObjectStorage();
       
    60     }
       
    61 
       
    62     /**
       
    63      * Returns the token parser instance to add to the existing list.
       
    64      *
       
    65      * @return array An array of Twig_TokenParser instances
       
    66      */
       
    67     public function getTokenParsers()
       
    68     {
       
    69         return array(
       
    70             // {% form_theme form "SomeBungle::widgets.twig" %}
       
    71             new FormThemeTokenParser(),
       
    72         );
       
    73     }
       
    74 
       
    75     public function getFunctions()
       
    76     {
       
    77         return array(
       
    78             'form_enctype'             => new \Twig_Function_Method($this, 'renderEnctype', array('is_safe' => array('html'))),
       
    79             'form_widget'              => new \Twig_Function_Method($this, 'renderWidget', array('is_safe' => array('html'))),
       
    80             'form_errors'              => new \Twig_Function_Method($this, 'renderErrors', array('is_safe' => array('html'))),
       
    81             'form_label'               => new \Twig_Function_Method($this, 'renderLabel', array('is_safe' => array('html'))),
       
    82             'form_row'                 => new \Twig_Function_Method($this, 'renderRow', array('is_safe' => array('html'))),
       
    83             'form_rest'                => new \Twig_Function_Method($this, 'renderRest', array('is_safe' => array('html'))),
       
    84             '_form_is_choice_group'    => new \Twig_Function_Method($this, 'isChoiceGroup', array('is_safe' => array('html'))),
       
    85             '_form_is_choice_selected' => new \Twig_Function_Method($this, 'isChoiceSelected', array('is_safe' => array('html'))),
       
    86         );
       
    87     }
       
    88 
       
    89     public function isChoiceGroup($label)
       
    90     {
       
    91         return FormUtil::isChoiceGroup($label);
       
    92     }
       
    93 
       
    94     public function isChoiceSelected(FormView $view, $choice)
       
    95     {
       
    96         return FormUtil::isChoiceSelected($choice, $view->get('value'));
       
    97     }
       
    98 
       
    99     /**
       
   100      * Renders the HTML enctype in the form tag, if necessary
       
   101      *
       
   102      * Example usage in Twig templates:
       
   103      *
       
   104      *     <form action="..." method="post" {{ form_enctype(form) }}>
       
   105      *
       
   106      * @param FormView $view  The view for which to render the encoding type
       
   107      *
       
   108      * @return string The html markup
       
   109      */
       
   110     public function renderEnctype(FormView $view)
       
   111     {
       
   112         return $this->render($view, 'enctype');
       
   113     }
       
   114 
       
   115     /**
       
   116      * Renders a row for the view.
       
   117      *
       
   118      * @param FormView $view      The view to render as a row
       
   119      * @param array    $variables An array of variables
       
   120      *
       
   121      * @return string The html markup
       
   122      */
       
   123     public function renderRow(FormView $view, array $variables = array())
       
   124     {
       
   125         return $this->render($view, 'row', $variables);
       
   126     }
       
   127 
       
   128     /**
       
   129      * Renders views which have not already been rendered.
       
   130      *
       
   131      * @param FormView $view      The parent view
       
   132      * @param array    $variables An array of variables
       
   133      *
       
   134      * @return string The html markup
       
   135      */
       
   136     public function renderRest(FormView $view, array $variables = array())
       
   137     {
       
   138         return $this->render($view, 'rest', $variables);
       
   139     }
       
   140 
       
   141     /**
       
   142      * Renders the HTML for a given view
       
   143      *
       
   144      * Example usage in Twig:
       
   145      *
       
   146      *     {{ form_widget(view) }}
       
   147      *
       
   148      * You can pass options during the call:
       
   149      *
       
   150      *     {{ form_widget(view, {'attr': {'class': 'foo'}}) }}
       
   151      *
       
   152      *     {{ form_widget(view, {'separator': '+++++'}) }}
       
   153      *
       
   154      * @param FormView        $view      The view to render
       
   155      * @param array           $variables Additional variables passed to the template
       
   156      *
       
   157      * @return string The html markup
       
   158      */
       
   159     public function renderWidget(FormView $view, array $variables = array())
       
   160     {
       
   161         return $this->render($view, 'widget', $variables);
       
   162     }
       
   163 
       
   164     /**
       
   165      * Renders the errors of the given view
       
   166      *
       
   167      * @param FormView $view The view to render the errors for
       
   168      *
       
   169      * @return string The html markup
       
   170      */
       
   171     public function renderErrors(FormView $view)
       
   172     {
       
   173         return $this->render($view, 'errors');
       
   174     }
       
   175 
       
   176     /**
       
   177      * Renders the label of the given view
       
   178      *
       
   179      * @param FormView $view  The view to render the label for
       
   180      * @param string   $label Label name
       
   181      * @param array    $variables Additional variables passed to the template
       
   182      *
       
   183      * @return string The html markup
       
   184      */
       
   185     public function renderLabel(FormView $view, $label = null, array $variables = array())
       
   186     {
       
   187         if ($label !== null) {
       
   188             $variables += array('label' => $label);
       
   189         }
       
   190 
       
   191         return $this->render($view, 'label', $variables);
       
   192     }
       
   193 
       
   194     /**
       
   195      * Renders a template.
       
   196      *
       
   197      * 1. This function first looks for a block named "_<view id>_<section>",
       
   198      * 2. if such a block is not found the function will look for a block named
       
   199      *    "<type name>_<section>",
       
   200      * 3. the type name is recursively replaced by the parent type name until a
       
   201      *    corresponding block is found
       
   202      *
       
   203      * @param FormView  $view       The form view
       
   204      * @param string    $section    The section to render (i.e. 'row', 'widget', 'label', ...)
       
   205      * @param array     $variables  Additional variables
       
   206      *
       
   207      * @return string The html markup
       
   208      *
       
   209      * @throws FormException if no template block exists to render the given section of the view
       
   210      */
       
   211     protected function render(FormView $view, $section, array $variables = array())
       
   212     {
       
   213         $mainTemplate = in_array($section, array('widget', 'row'));
       
   214         if ($mainTemplate && $view->isRendered()) {
       
   215 
       
   216                 return '';
       
   217         }
       
   218 
       
   219         if (null === $this->template) {
       
   220             $this->template = reset($this->resources);
       
   221             if (!$this->template instanceof \Twig_Template) {
       
   222                 $this->template = $this->environment->loadTemplate($this->template);
       
   223             }
       
   224         }
       
   225 
       
   226         $custom = '_'.$view->get('id');
       
   227         $rendering = $custom.$section;
       
   228         $blocks = $this->getBlocks($view);
       
   229 
       
   230         if (isset($this->varStack[$rendering])) {
       
   231             $typeIndex = $this->varStack[$rendering]['typeIndex'] - 1;
       
   232             $types = $this->varStack[$rendering]['types'];
       
   233             $this->varStack[$rendering]['variables'] = array_replace_recursive($this->varStack[$rendering]['variables'], $variables);
       
   234         } else {
       
   235             $types = $view->get('types');
       
   236             $types[] = $custom;
       
   237             $typeIndex = count($types) - 1;
       
   238             $this->varStack[$rendering] = array (
       
   239                 'variables' => array_replace_recursive($view->all(), $variables),
       
   240                 'types'     => $types,
       
   241             );
       
   242         }
       
   243 
       
   244         do {
       
   245             $types[$typeIndex] .= '_'.$section;
       
   246 
       
   247             if (isset($blocks[$types[$typeIndex]])) {
       
   248 
       
   249                 $this->varStack[$rendering]['typeIndex'] = $typeIndex;
       
   250 
       
   251                 // we do not call renderBlock here to avoid too many nested level calls (XDebug limits the level to 100 by default)
       
   252                 ob_start();
       
   253                 $this->template->displayBlock($types[$typeIndex], $this->varStack[$rendering]['variables'], $blocks);
       
   254                 $html = ob_get_clean();
       
   255 
       
   256                 if ($mainTemplate) {
       
   257                     $view->setRendered();
       
   258                 }
       
   259 
       
   260                 unset($this->varStack[$rendering]);
       
   261 
       
   262                 return $html;
       
   263             }
       
   264         } while (--$typeIndex >= 0);
       
   265 
       
   266         throw new FormException(sprintf(
       
   267             'Unable to render the form as none of the following blocks exist: "%s".',
       
   268             implode('", "', array_reverse($types))
       
   269         ));
       
   270     }
       
   271 
       
   272     /**
       
   273      * Returns the name of the extension.
       
   274      *
       
   275      * @return string The extension name
       
   276      */
       
   277     public function getName()
       
   278     {
       
   279         return 'form';
       
   280     }
       
   281 
       
   282     /**
       
   283      * Returns the blocks used to render the view.
       
   284      *
       
   285      * Templates are looked for in the resources in the following order:
       
   286      *   * resources from the themes (and its parents)
       
   287      *   * resources from the themes of parent views (up to the root view)
       
   288      *   * default resources
       
   289      *
       
   290      * @param FormView $view The view
       
   291      *
       
   292      * @return array An array of Twig_TemplateInterface instances
       
   293      */
       
   294     protected function getBlocks(FormView $view)
       
   295     {
       
   296         if (!$this->blocks->contains($view)) {
       
   297 
       
   298             $rootView = !$view->hasParent();
       
   299 
       
   300             $templates = $rootView ? $this->resources : array();
       
   301 
       
   302             if (isset($this->themes[$view])) {
       
   303                 $templates = array_merge($templates, $this->themes[$view]);
       
   304             }
       
   305 
       
   306             $blocks = array();
       
   307 
       
   308             foreach ($templates as $template) {
       
   309                 if (!$template instanceof \Twig_Template) {
       
   310                     $template = $this->environment->loadTemplate($template);
       
   311                 }
       
   312                 $templateBlocks = array();
       
   313                 do {
       
   314                     $templateBlocks = array_merge($template->getBlocks(), $templateBlocks);
       
   315                 } while (false !== $template = $template->getParent(array()));
       
   316                 $blocks = array_merge($blocks, $templateBlocks);
       
   317             }
       
   318 
       
   319             if (!$rootView) {
       
   320                 $blocks = array_merge($this->getBlocks($view->getParent()), $blocks);
       
   321             }
       
   322 
       
   323             $this->blocks->attach($view, $blocks);
       
   324         } else {
       
   325             $blocks = $this->blocks[$view];
       
   326         }
       
   327 
       
   328         return $blocks;
       
   329     }
       
   330 }