<table role="table">
<thead>
<tr>
<th>Q</th>
<th>A</th>
</tr>
</thead>
<tbody>
<tr>
<td>Bug report?</td>
<td>Maybe</td>
</tr>
<tr>
<td>Feature request?</td>
<td>no</td>
</tr>
<tr>
<td>BC Break report?</td>
<td>no</td>
</tr>
<tr>
<td>RFC?</td>
<td>no</td>
</tr>
<tr>
<td>Symfony version</td>
<td>4.0.3</td>
</tr>
</tbody>
</table>
<p dir="auto">hello everybody,</p>
<p dir="auto">I need my CDN service cache my website, so I set</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="    /**
     * @Route(&quot;/&quot;, defaults={&quot;page&quot;: 1}, name=&quot;homepage&quot;)
     * @Method(&quot;GET&quot;)
     * @Cache(smaxage=&quot;3600&quot;)
     */
    public function indexAction(ArticleRepository $articles, int $page): Response
    {
        $articles = $articles-&gt;findLatest($page);

        return $this-&gt;render('article/index.html.twig', [
            'articles' =&gt; $articles
        ]);
    }"><pre class="notranslate">    <span class="pl-c">/**</span>
<span class="pl-c">     * @Route("/", defaults={"page": 1}, name="homepage")</span>
<span class="pl-c">     * @Method("GET")</span>
<span class="pl-c">     * @Cache(smaxage="3600")</span>
<span class="pl-c">     */</span>
    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">indexAction</span>(<span class="pl-smi"><span class="pl-smi">ArticleRepository</span></span> <span class="pl-s1"><span class="pl-c1">$</span>articles</span>, <span class="pl-smi">int</span> <span class="pl-s1"><span class="pl-c1">$</span>page</span>): <span class="pl-smi"><span class="pl-smi">Response</span></span>
    {
        <span class="pl-s1"><span class="pl-c1">$</span>articles</span> = <span class="pl-s1"><span class="pl-c1">$</span>articles</span>-&gt;<span class="pl-en">findLatest</span>(<span class="pl-s1"><span class="pl-c1">$</span>page</span>);

        <span class="pl-k">return</span> <span class="pl-s1"><span class="pl-c1">$</span><span class="pl-smi">this</span></span>-&gt;<span class="pl-en">render</span>(<span class="pl-s">'article/index.html.twig'</span>, [
            <span class="pl-s">'articles'</span> =&gt; <span class="pl-s1"><span class="pl-c1">$</span>articles</span>
        ]);
    }</pre></div>
<p dir="auto">then the <code class="notranslate">cache-control</code> header is <code class="notranslate">cache-control:max-age=0, must-revalidate, private, s-maxage=3600</code></p>
<p dir="auto">It contains the <code class="notranslate">max-age=0, must-revalidate, private</code> is not what I want.</p>
<p dir="auto">What should I do?</p>
<p dir="auto">Because the response header, so my CDN cannot cache my pages.</p>
<p dir="auto">thx.</p>
<p dir="auto">Hi,</p>
<p dir="auto">For background, I am attempting to write a <code class="notranslate">FormTypeExtension</code> that serialises form field constraints into data attributes on the input elements so that it is possible to write a generalised javascript library capable of validating most typical symfony forms client side.</p>
<p dir="auto">One place that breaks down is when validation rules depend on submitted data - I've seen and have been using <a href="http://symfony.com/doc/current/book/forms.html#groups-based-on-the-submitted-data" rel="nofollow">this</a> to successfully validate on the server side, but without the ability to define field interdependencies declaratively, it becomes impossible to bake these rules into data attributes: there is no way to serialise the decision that happens in that closure.</p>
<p dir="auto">With that in mind, I've been attempting to build another <code class="notranslate">FormTypeExtension</code> that introduces the notion of dependencies, see comments inline:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&lt;?php

namespace App\Form\Extension;

use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use App\Form\EventListener\DependsListener;

class DependsExtension extends AbstractTypeExtension
{
    public function getExtendedType()
    {
        return 'form';
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver-&gt;setOptional(array(
            'depends'
        ));
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if (array_key_exists('depends', $options)) {

            // should builder be needed here? is this even correct?
            $builder-&gt;addEventSubscriber(new DependsListener($builder, $options['depends']));
        }
    }

    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        // bake dependencies into data attributes for client side js to act on
        if (array_key_exists('depends', $options)) {
            $view-&gt;vars['attr']['data-depends'] = json_encode($options['depends']);
        }
    }
}"><pre class="notranslate"><span class="pl-ent">&lt;?php</span>

<span class="pl-k">namespace</span> <span class="pl-v">App</span>\<span class="pl-v">Form</span>\<span class="pl-v">Extension</span>;

<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">Form</span>\<span class="pl-v">FormBuilderInterface</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">Form</span>\<span class="pl-v">FormView</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">Form</span>\<span class="pl-v">FormInterface</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">Form</span>\<span class="pl-v">AbstractTypeExtension</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">OptionsResolver</span>\<span class="pl-v">OptionsResolverInterface</span>;
<span class="pl-k">use</span> <span class="pl-v">App</span>\<span class="pl-v">Form</span>\<span class="pl-v">EventListener</span>\<span class="pl-v">DependsListener</span>;

<span class="pl-k">class</span> <span class="pl-v">DependsExtension</span> <span class="pl-k">extends</span> <span class="pl-v">AbstractTypeExtension</span>
{
    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">getExtendedType</span>()
    {
        <span class="pl-k">return</span> <span class="pl-s">'form'</span>;
    }

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">setDefaultOptions</span>(<span class="pl-smi"><span class="pl-smi">OptionsResolverInterface</span></span> <span class="pl-s1"><span class="pl-c1">$</span>resolver</span>)
    {
        <span class="pl-s1"><span class="pl-c1">$</span>resolver</span>-&gt;<span class="pl-en">setOptional</span>(<span class="pl-en">array</span>(
            <span class="pl-s">'depends'</span>
        ));
    }

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">buildForm</span>(<span class="pl-smi"><span class="pl-smi">FormBuilderInterface</span></span> <span class="pl-s1"><span class="pl-c1">$</span>builder</span>, <span class="pl-smi">array</span> <span class="pl-s1"><span class="pl-c1">$</span>options</span>)
    {
        <span class="pl-k">if</span> (array_key_exists(<span class="pl-s">'depends'</span>, <span class="pl-s1"><span class="pl-c1">$</span>options</span>)) {

            <span class="pl-c">// should builder be needed here? is this even correct?</span>
<span class="pl-c"></span>            <span class="pl-s1"><span class="pl-c1">$</span>builder</span>-&gt;<span class="pl-en">addEventSubscriber</span>(<span class="pl-k">new</span> <span class="pl-v">DependsListener</span>(<span class="pl-s1"><span class="pl-c1">$</span>builder</span>, <span class="pl-s1"><span class="pl-c1">$</span>options</span>[<span class="pl-s">'depends'</span>]));
        }
    }

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">buildView</span>(<span class="pl-smi"><span class="pl-smi">FormView</span></span> <span class="pl-s1"><span class="pl-c1">$</span>view</span>, <span class="pl-smi"><span class="pl-smi">FormInterface</span></span> <span class="pl-s1"><span class="pl-c1">$</span>form</span>, <span class="pl-smi">array</span> <span class="pl-s1"><span class="pl-c1">$</span>options</span>)
    {
        <span class="pl-c">// bake dependencies into data attributes for client side js to act on</span>
        <span class="pl-k">if</span> (array_key_exists(<span class="pl-s">'depends'</span>, <span class="pl-s1"><span class="pl-c1">$</span>options</span>)) {
            <span class="pl-s1"><span class="pl-c1">$</span>view</span>-&gt;<span class="pl-c1">vars</span>[<span class="pl-s">'attr'</span>][<span class="pl-s">'data-depends'</span>] = json_encode(<span class="pl-s1"><span class="pl-c1">$</span>options</span>[<span class="pl-s">'depends'</span>]);
        }
    }
}</pre></div>
<p dir="auto">And the listener:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&lt;?php

namespace App\Form\EventListener;

use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\PropertyAccess\PropertyAccess;


class DependsListener implements EventSubscriberInterface
{
    /**
     *
     * @var FormBuilderInterface
     */
    private $builder;
    private $depends;

    public function __construct($builder, $depends)
    {
        $this-&gt;builder = $builder;
        $this-&gt;depends = $depends;
    }

    public static function getSubscribedEvents()
    {
        return array(
            FormEvents::PRE_SET_DATA =&gt; 'preSetData',
            FormEvents::PRE_SUBMIT =&gt; 'preSubmit'
        );
    }

    public function preSetData(FormEvent $event)
    {
        // TODO probably have to do something here when loading the form
    }

    public function preSubmit(FormEvent $event)
    {
        $root = $event-&gt;getForm()-&gt;getRoot();
        $accessor = PropertyAccess::createPropertyAccessor();

        $removeMe = true;
        foreach ($this-&gt;depends as $fieldPath =&gt; $value) {

            // TODO: use sf constraints for this? allows for more options than simple equality
            if ($accessor-&gt;getValue($root, $fieldPath)-&gt;getData() != $value) {
                $removeMe = $removeMe &amp;&amp; true;
            }
            else {
                $removeMe = false;
            }
        }

        if ($removeMe) {

            // this is where I'd like to somehow unset / interact with the constraints defined on the field
            // 
            // note it is possible to just straight up remove the field:
            $event-&gt;getForm()-&gt;getParent()-&gt;remove($event-&gt;getForm()-&gt;getName());
            // but this is not ideal. ideally we'd disable server side validation and hide this field with js
        }
    }
}"><pre class="notranslate"><span class="pl-ent">&lt;?php</span>

<span class="pl-k">namespace</span> <span class="pl-v">App</span>\<span class="pl-v">Form</span>\<span class="pl-v">EventListener</span>;

<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">Form</span>\<span class="pl-v">FormBuilderInterface</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">EventDispatcher</span>\<span class="pl-v">EventSubscriberInterface</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">Form</span>\<span class="pl-v">FormEvent</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">Form</span>\<span class="pl-v">FormEvents</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">PropertyAccess</span>\<span class="pl-v">PropertyAccess</span>;


<span class="pl-k">class</span> <span class="pl-v">DependsListener</span> <span class="pl-k">implements</span> <span class="pl-v">EventSubscriberInterface</span>
{
    <span class="pl-c">/**</span>
<span class="pl-c">     *</span>
<span class="pl-c">     * @var FormBuilderInterface</span>
<span class="pl-c">     */</span>
    <span class="pl-k">private</span> <span class="pl-c1"><span class="pl-c1">$</span>builder</span>;
    <span class="pl-k">private</span> <span class="pl-c1"><span class="pl-c1">$</span>depends</span>;

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">__construct</span>(<span class="pl-s1"><span class="pl-c1">$</span>builder</span>, <span class="pl-s1"><span class="pl-c1">$</span>depends</span>)
    {
        <span class="pl-s1"><span class="pl-c1">$</span><span class="pl-smi">this</span></span>-&gt;<span class="pl-c1">builder</span> = <span class="pl-s1"><span class="pl-c1">$</span>builder</span>;
        <span class="pl-s1"><span class="pl-c1">$</span><span class="pl-smi">this</span></span>-&gt;<span class="pl-c1">depends</span> = <span class="pl-s1"><span class="pl-c1">$</span>depends</span>;
    }

    <span class="pl-k">public</span> <span class="pl-k">static</span> <span class="pl-k">function</span> <span class="pl-en">getSubscribedEvents</span>()
    {
        <span class="pl-k">return</span> <span class="pl-en">array</span>(
            <span class="pl-v">FormEvents</span>::<span class="pl-c1">PRE_SET_DATA</span> =&gt; <span class="pl-s">'preSetData'</span>,
            <span class="pl-v">FormEvents</span>::<span class="pl-c1">PRE_SUBMIT</span> =&gt; <span class="pl-s">'preSubmit'</span>
        );
    }

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">preSetData</span>(<span class="pl-smi"><span class="pl-smi">FormEvent</span></span> <span class="pl-s1"><span class="pl-c1">$</span>event</span>)
    {
        <span class="pl-c">// TODO probably have to do something here when loading the form</span>
    }

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">preSubmit</span>(<span class="pl-smi"><span class="pl-smi">FormEvent</span></span> <span class="pl-s1"><span class="pl-c1">$</span>event</span>)
    {
        <span class="pl-s1"><span class="pl-c1">$</span>root</span> = <span class="pl-s1"><span class="pl-c1">$</span>event</span>-&gt;<span class="pl-en">getForm</span>()-&gt;<span class="pl-en">getRoot</span>();
        <span class="pl-s1"><span class="pl-c1">$</span>accessor</span> = <span class="pl-v">PropertyAccess</span>::<span class="pl-en">createPropertyAccessor</span>();

        <span class="pl-s1"><span class="pl-c1">$</span>removeMe</span> = <span class="pl-c1">true</span>;
        <span class="pl-k">foreach</span> (<span class="pl-s1"><span class="pl-c1">$</span><span class="pl-smi">this</span></span>-&gt;<span class="pl-c1">depends</span> <span class="pl-k">as</span> <span class="pl-s1"><span class="pl-c1">$</span>fieldPath</span> =&gt; <span class="pl-s1"><span class="pl-c1">$</span>value</span>) {

            <span class="pl-c">// TODO: use sf constraints for this? allows for more options than simple equality</span>
            <span class="pl-k">if</span> (<span class="pl-s1"><span class="pl-c1">$</span>accessor</span>-&gt;<span class="pl-en">getValue</span>(<span class="pl-s1"><span class="pl-c1">$</span>root</span>, <span class="pl-s1"><span class="pl-c1">$</span>fieldPath</span>)-&gt;<span class="pl-en">getData</span>() != <span class="pl-s1"><span class="pl-c1">$</span>value</span>) {
                <span class="pl-s1"><span class="pl-c1">$</span>removeMe</span> = <span class="pl-s1"><span class="pl-c1">$</span>removeMe</span> &amp;&amp; <span class="pl-c1">true</span>;
            }
            <span class="pl-k">else</span> {
                <span class="pl-s1"><span class="pl-c1">$</span>removeMe</span> = <span class="pl-c1">false</span>;
            }
        }

        <span class="pl-k">if</span> (<span class="pl-s1"><span class="pl-c1">$</span>removeMe</span>) {

            <span class="pl-c">// this is where I'd like to somehow unset / interact with the constraints defined on the field</span>
            <span class="pl-c">// </span>
            <span class="pl-c">// note it is possible to just straight up remove the field:</span>
            <span class="pl-s1"><span class="pl-c1">$</span>event</span>-&gt;<span class="pl-en">getForm</span>()-&gt;<span class="pl-en">getParent</span>()-&gt;<span class="pl-en">remove</span>(<span class="pl-s1"><span class="pl-c1">$</span>event</span>-&gt;<span class="pl-en">getForm</span>()-&gt;<span class="pl-en">getName</span>());
            <span class="pl-c">// but this is not ideal. ideally we'd disable server side validation and hide this field with js</span>
        }
    }
}</pre></div>
<p dir="auto">As you can see, it seems impossible to unset / interact with the constraints associated with the field, even though it's possible to simply outright delete the field.</p>
<p dir="auto">Could anyone shed any light on if what I'm trying to do is possible / desired / insane / stupid and help point me in the direction of a solution?</p>
<p dir="auto">Thanks,<br>
Luke Cawood</p>