<p>I suspect the real answer will reveal itself by reducing your test case to the simplest possible implementation—but I wanted to double-check some of my own assumptions about how this worked, so I cobbled together a basic Module, Controller, Model, and Template from existing material…</p>
<h1>Validation Example</h1>
<h2>Routing</h2>
<p>This just provides a nicer way for us to hit the landing &quot;form&quot; template, at <code>/validation-demo</code>. The key is the URI, and the value is an action path, with the segments being the Module ID (as it's registered in <code>app.php</code>), the Controller handle (kebab-cased), and the Action ID (also kebab-cased):</p>
<pre class="lang-php prettyprint-override"><code>return [
    'validation-demo' =&gt; 'playground/example/configure',
];
</code></pre>
<h2>Module</h2>
<p>Dime-a-dozen Module class—nothing special here.</p>
<pre class="lang-php prettyprint-override"><code>namespace modules\playground;

class Playground extends \yii\base\Module
{
    public function init()
    {
        $this-&gt;controllerNamespace = 'modules\\playground\\controllers';

        parent::init();
    }
}
</code></pre>
<h2>Controller</h2>
<p>This one's a little longer! There are two Actions—one responsible for rendering the form (<code>configure</code>) and one for receiving data (<code>validate</code>).</p>
<p>Neither make any changes to the database—they're just here to demonstrate the lifecycle of a model when dealing with route params.</p>
<pre class="lang-php prettyprint-override"><code>namespace modules\playground\controllers;

use Craft;
use craft\web\Controller as BaseController;
use craft\web\Response;

use modules\playground\models\Thing;

class ExampleController extends BaseController
{
    protected $allowAnonymous = self::ALLOW_ANONYMOUS_LIVE;

    /**
     * This is how we first encounter the view—see `routes.php`!
     * 
     * @param Thing|null $thing An existing Thing instance, when passed via route params.
     */
    public function actionConfigure(?Thing $thing): Response
    {
        if (is_null($thing)) {
            $thing = new Thing;
        }

        return $this-&gt;renderTemplate('_validation/form', [
            'thing' =&gt; $thing,
        ]);
    }

    /**
     * The action that receives our POST request and actually processes data.
     */
    public function actionValidate(): ?Response
    {
        $this-&gt;requirePostRequest();

        // We're creating this virtually, but it could also be loaded from the database by sending an ID from the front-end:
        $thing = new Thing;
        $thing-&gt;size = $this-&gt;request-&gt;getBodyParam('size', $thing-&gt;size);

        // Using a &quot;happy path,&quot; we bail early if it fails validation:
        if (!$thing-&gt;validate()) {
            // Set a notice that we can display in the front-end...
            Craft::$app-&gt;getSession()-&gt;setNotice('Thing was misconfigured! Please try again.');

            // ...and include our Thing in &quot;route params&quot; that get applied when we fall back through to the action above (or the original template). Note that the key in this array has to correspond to the argument name in `actionConfigure`!
            Craft::$app-&gt;getUrlManager()-&gt;setRouteParams(['thing' =&gt; $thing]);

            // Don't forget to actually exit early!
            return null;
        }

        Craft::$app-&gt;getSession()-&gt;setNotice('Thing was properly configured!');

        return $this-&gt;redirectToPostedUrl();
    }
}
</code></pre>
<h2>Model</h2>
<p>The model just has a single property, and validates when a <code>size</code> greater than 100 is assigned.</p>
<pre class="lang-php prettyprint-override"><code>namespace modules\playground\models;

use craft\base\Model;

class Thing extends Model
{
    public $size;

    public function rules(): array
    {
        $rules = [];

        $rules[] = [['size'], 'required'];
        $rules[] = [['size'], 'number', 'min' =&gt; 100];

        return $rules;
    }
}
</code></pre>
<h2>Template</h2>
<p>This is stored at <code>templates/_validation/form.twig</code> (your main templates folder, not that of your module), and contains only the code <em>absolutely required</em> to demonstrate the concepts.</p>
<p>The combination of <code>routes.php</code> and <code>ExampleController</code> handles instantiating a <code>Thing</code> instance, and rendering this when accessing <code>/validation-demo</code>. Note that <code>thing</code> isn't defined in the template—it's set in the Action, above, and passed to the template explicitly!</p>
<pre class="lang-twig prettyprint-override"><code>{% set flashes = craft.app.session.getAllFlashes() %}

{% for type, flash in flashes %}
    &lt;div class=&quot;flash {{ type }}&quot;&gt;{{ flash }}&lt;/div&gt;
{% endfor %}

&lt;form method=&quot;POST&quot;&gt;
    {{ actionInput('playground/example/validate') }}
    {{ csrfInput() }}

    &lt;fieldset&gt;
        &lt;label for=&quot;size&quot;&gt;Thing Size&lt;/label&gt;
        &lt;input
            type=&quot;number&quot;
            id=&quot;size&quot;
            name=&quot;size&quot;
            step=&quot;0.01&quot;
            value=&quot;{{ thing.size }}&quot;
            required&gt;

        {% if thing.hasErrors('size') %}
            &lt;p&gt;{{ thing.getErrors('size') | join(', ') }}&lt;/p&gt;
        {% endif %}
    &lt;/fieldset&gt;

    &lt;button&gt;Validate&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<blockquote>
<p>This can also be exposed as a regular template based <a href="https://craftcms.com/docs/3.x/routing.html" rel="nofollow noreferrer">Craft's routing behavior</a> (item four in the list).</p>
</blockquote>
<h1>Putting it All Together</h1>
<p>Once the files are organized in your Craft installation / module, you should be able to visit <code>/validation-demo</code> in your browser, and see a form. Submitting the form with no information should present a validation error. Submitting with valid info (a number greater than 100) should display a message, and reset the form.</p>
<p>Note that the first time you visit <code>/validation-demo</code>, the Controller takes care of populating a <code>Thing</code> for you, as one is not yet available in Route params.</p>
<p>When processing the subsequent POST request, a new <code>Thing</code> is created as a container for the data + validation. When validation <em>fails</em>, we call <code>-&gt;setRouteParams(['key' =&gt; $model])</code> which keeps track of the model in-memory, and tries to match it up with and argument of the Action that the request &quot;falls through&quot; to, after a <code>null</code> value comes back from the original Action.</p>
<h2>Bonus: Can we do this without <code>ExampleController::actionConfigure()</code>?</h2>
<p>If your original form appears in a normal template (without vanity route mapping via <code>routes.php</code> to a Controller), anything set as a route param will be available in the template—although on the first view, it won't have any idea what <code>thing</code> is, so you would have to code the form “defensively,” i.e. gratuitous use of the null coalescing operator: <code>{% set errors = thing.getErrors('size') ?? [] %}</code>.</p>
<p>Try deleting your route from <code>routes.php</code> and moving the template to <code>templates/validation-demo.twig</code>. Replace any presumptive use of the <code>thing</code> variable in the template with <code>if</code> statements or the null-coalesce operator, and start from the top. There should be no difference in overall behavior, except that <code>thing</code> is a little more volatile to work with, when you're not sure if it'll exist.</p>
<blockquote>
<p>I'd like to publish this as a ready-made bundle, but I don't know how useful it is without the AR/storage portion. Let me know if you have ideas!</p>
</blockquote>

CC BY-SA 4.0
2021-10-25T17:26:33.020Z
thanks a lot for this generous explanation of how Modules work. I think what Craft is sorely missing is this kind of tutorials for intermediate and advanced features. In their docs. I always find one line description of what things do, and, at best, partial examples that don't show the full picture. 
In my edit I wrote what the issue was. Comparing your examples against mine helped me out figure the error. 
Regarding your question, I would publish this as it is. If later you want to add the storage portion, even better. Clear and complete examples are needed badly.
mauricio
CC BY-SA 4.0
2021-10-25T19:29:34.873Z
Keep in mind that Craft is built on top of Yii, and shares _a lot_ of its DNA—in fact, this demo is predominantly Yii functionality! So, when in doubt, be sure and look at the Yii 2 knowledge-base. I leaned heavily on the [validating input](https://www.yiiframework.com/doc/guide/2.0/en/input-validation) and [Actions](https://www.yiiframework.com/doc/guide/2.0/en/structure-controllers#action-parameters) articles. I'll consider how to package this up, and talk to Matt about what aspects if any would be useful to PR into the documentation.
August Miller
CC BY-SA 4.0
2021-10-25T04:33:11.380Z
2021-10-25T04:33:11.380Z
August Miller
<p>In the twig template, <code>myModel</code> should be available as a regular twig variable.</p>
<p>Try this:</p>
<pre><code>{{ myModel.id }}
</code></pre>

CC BY-SA 4.0
2021-10-23T23:12:59.053Z
thank you for the feedback. Unfortunately, I'm not able to produce any results that way. If I try this ```{% if myModel is defined %}
    {{ 'myModel.action }}{% endif %}``` nothing is outputted.
mauricio
CC BY-SA 4.0
2021-10-24T02:08:47.583Z
Ah, must be something else then. Will need to see more context. For example, controller action itself needs to return null on validation errors, so are you doing `return $this->validationProblems($myModel)` or `$this->validationProblems($myModel); return null`?
Sam Hernandez
CC BY-SA 4.0
2021-10-24T03:32:37.833Z
thanks so much for chcking on this. I edited my question to include the controller action.
mauricio
CC BY-SA 4.0
2021-10-23T19:23:16.167Z
2021-10-23T19:23:16.167Z
Sam Hernandez
<p>I created a module that adds a password protection to a site I'm testing craft on (I'm new to Craft and Yii, so please bear with me). In the front end there's a form where users enter a password and send it for validation.</p>
<p>I followed craft's recommendations for setting up the form: action attribute is empty, and there is a hidden field with the controller's route. The issue is that when there are validation errors, the page is reloaded (which is expected), but I cannot, or don't know how to, access the model nor its errors.</p>
<p>My controller has a function that is called when there's a validation error, and works like this:</p>
<pre class="lang-php prettyprint-override"><code>    Form action:
    
    &lt;input type=&quot;hidden&quot; name=&quot;action&quot; value=&quot;password-global/default/knock-knock&quot;&gt;
</code></pre>
<pre class="lang-php prettyprint-override"><code>    DefaultController.php:
    
    public function actionKnockKnock(
        // control var
        $validated = false;      
        // creates model instance
        $model = new PasswordGlobalModel();
        // gets form's posted data
        $params = Craft::$app-&gt;request-&gt;getBodyParams();
        // passes form's data to model
        $model-&gt;attributes = $params;
    
        // verifies data validity
        if ( $model-&gt;validate() ):
            $validated = true;      
            // some other logic
        endif;    
    
       // ready to send response
       if ($validated):
            // redirection stuff
       else:
            Craft::$app-&gt;urlManager-&gt;setRouteParams(['myModel' =&gt; $model], true);
            return null; 
       endif;
    
    )
</code></pre>
<p>If I do a <code>print_r</code> of $model before doing the <code>setRouteParams(..)</code> part, I see the data model object as I expected... beginning like this:</p>
<pre class="lang-php prettyprint-override"><code>    abc\passwordglobal\models\PasswordGlobalModel Object
    (
        [theDate] =&gt; 
        [theGreeting] =&gt; 
        [action] =&gt; password-global/default/knock-knock
        [theCurrentPage] =&gt; /contact
        [_errors:yii\base\Model:private] =&gt; Array
            (
                [theGreeting] =&gt; Array
                    (
                        [0] =&gt; The password cannot be validated.
                    )
    
            )
        ...
    )
</code></pre>
<pre class="lang-php prettyprint-override"><code>    // In the reloaded form I have:
    craft.app.request.getParam('myModel') // returns null
    craft.app.request.getBodyParams() // returns array of data entered and submitted in the form's fields
</code></pre>
<p>The answer to this <a href="https://craftcms.stackexchange.com/questions/30060/craft-3-controller-redirect-with-errors">question</a> helped me clarify things, but the proposed solution doesn't work for me.</p>
<p>Any suggestions on how to get the model/or its errors are greatly appreciated. Thank you!</p>
<p><strong>EDIT 1, THE PROBLEM:</strong>
I did several changes, and now things are working. The issue was that my module had a form template that was rendered (using <code>renderTemplage</code>) when a twig extension function was called from within the front end. I assume that doing it this way was preventing the rendered form from reading the model passed with <code>setRouteParams</code> in the controller.</p>
<p>If I include the form as a front end template, then I have full access  to the model.</p>
<p>I'm marking @August Miller's solution as the correct one, because it is the <strong>best</strong> and most generous overview of how Craft modules work. Thank you!</p>

CC BY-SA 4.0
2021-10-24T00:17:13.683Z
Could you post more of your Controller—specifically, the part of the action that calls this private method?
August Miller
CC BY-SA 4.0
2021-10-24T13:35:26.577Z
@AugustMiller, thank you, I updated my question. To simplify things I removed the private method.
mauricio
<controller><modules><validation>
How to access 'setRouteParams' model in form validation
# How to access 'setRouteParams' model in form validation

(Asked by: username_0 on 2021-10-23)

I created a module that adds a password protection to a site I'm testing craft on (I'm new to Craft and Yii, so please bear with me). In the front end there's a form where users enter a password and send it for validation.
I followed craft's recommendations for setting up the form: action attribute is empty, and there is a hidden field with the controller's route. The issue is that when there are validation errors, the page is reloaded (which is expected), but I cannot, or don't know how to, access the model nor its errors.
My controller has a function that is called when there's a validation error, and works like this:
    Form action:
    
    

    DefaultController.php:
    
    public function actionKnockKnock(
        // control var
        $validated = false;      
        // creates model instance
        $model = new PasswordGlobalModel();
        // gets form's posted data
        $params = Craft::$app->request->getBodyParams();
        // passes form's data to model
        $model->attributes = $params;
    
        // verifies data validity
        if ( $model->validate() ):
            $validated = true;      
            // some other logic
        endif;    
    
       // ready to send response
       if ($validated):
            // redirection stuff
       else:
            Craft::$app->urlManager->setRouteParams(['myModel' => $model], true);
            return null; 
       endif;
    
    )

If I do a print_r of $model before doing the setRouteParams(..) part, I see the data model object as I expected... beginning like this:
    abc\passwordglobal\models\PasswordGlobalModel Object
    (
        [theDate] => 
        [theGreeting] => 
        [action] => password-global/default/knock-knock
        [theCurrentPage] => /contact
        [_errors:yii\base\Model:private] => Array
            (
                [theGreeting] => Array
                    (
                        [0] => The password cannot be validated.
                    )
    
            )
        ...
    )

    // In the reloaded form I have:
    craft.app.request.getParam('myModel') // returns null
    craft.app.request.getBodyParams() // returns array of data entered and submitted in the form's fields

The answer to this question helped me clarify things, but the proposed solution doesn't work for me.
Any suggestions on how to get the model/or its errors are greatly appreciated. Thank you!
EDIT 1, THE PROBLEM:
I did several changes, and now things are working. The issue was that my module had a form template that was rendered (using renderTemplage) when a twig extension function was called from within the front end. I assume that doing it this way was preventing the rendered form from reading the model passed with setRouteParams in the controller.
If I include the form as a front end template, then I have full access  to the model.
I'm marking @August Miller's solution as the correct one, because it is the best and most generous overview of how Craft modules work. Thank you!

**Tags:** controller, modules, validation
> **Comments:**
> **username_1**: Could you post more of your Controller—specifically, the part of the action that calls this private method?  
> **username_0**: @AugustMiller, thank you, I updated my question. To simplify things I removed the private method.  

---

## 2 answers

### Answer by username_1 on 2021-10-25. Score: 1 | Accepted answer

I suspect the real answer will reveal itself by reducing your test case to the simplest possible implementation—but I wanted to double-check some of my own assumptions about how this worked, so I cobbled together a basic Module, Controller, Model, and Template from existing material…
Validation Example
Routing
This just provides a nicer way for us to hit the landing "form" template, at /validation-demo. The key is the URI, and the value is an action path, with the segments being the Module ID (as it's registered in app.php), the Controller handle (kebab-cased), and the Action ID (also kebab-cased):
return [
    'validation-demo' => 'playground/example/configure',
];

Module
Dime-a-dozen Module class—nothing special here.
namespace modules\playground;

class Playground extends \yii\base\Module
{
    public function init()
    {
        $this->controllerNamespace = 'modules\\playground\\controllers';

        parent::init();
    }
}

Controller
This one's a little longer! There are two Actions—one responsible for rendering the form (configure) and one for receiving data (validate).
Neither make any changes to the database—they're just here to demonstrate the lifecycle of a model when dealing with route params.
namespace modules\playground\controllers;

use Craft;
use craft\web\Controller as BaseController;
use craft\web\Response;

use modules\playground\models\Thing;

class ExampleController extends BaseController
{
    protected $allowAnonymous = self::ALLOW_ANONYMOUS_LIVE;

    /**
     * This is how we first encounter the view—see `routes.php`!
     * 
     * @param Thing|null $thing An existing Thing instance, when passed via route params.
     */
    public function actionConfigure(?Thing $thing): Response
    {
        if (is_null($thing)) {
            $thing = new Thing;
        }

        return $this->renderTemplate('_validation/form', [
            'thing' => $thing,
        ]);
    }

    /**
     * The action that receives our POST request and actually processes data.
     */
    public function actionValidate(): ?Response
    {
        $this->requirePostRequest();

        // We're creating this virtually, but it could also be loaded from the database by sending an ID from the front-end:
        $thing = new Thing;
        $thing->size = $this->request->getBodyParam('size', $thing->size);

        // Using a "happy path," we bail early if it fails validation:
        if (!$thing->validate()) {
            // Set a notice that we can display in the front-end...
            Craft::$app->getSession()->setNotice('Thing was misconfigured! Please try again.');

            // ...and include our Thing in "route params" that get applied when we fall back through to the action above (or the original template). Note that the key in this array has to correspond to the argument name in `actionConfigure`!
            Craft::$app->getUrlManager()->setRouteParams(['thing' => $thing]);

            // Don't forget to actually exit early!
            return null;
        }

        Craft::$app->getSession()->setNotice('Thing was properly configured!');

        return $this->redirectToPostedUrl();
    }
}

Model
The model just has a single property, and validates when a size greater than 100 is assigned.
namespace modules\playground\models;

use craft\base\Model;

class Thing extends Model
{
    public $size;

    public function rules(): array
    {
        $rules = [];

        $rules[] = [['size'], 'required'];
        $rules[] = [['size'], 'number', 'min' => 100];

        return $rules;
    }
}

Template
This is stored at templates/_validation/form.twig (your main templates folder, not that of your module), and contains only the code absolutely required to demonstrate the concepts.
The combination of routes.php and ExampleController handles instantiating a Thing instance, and rendering this when accessing /validation-demo. Note that thing isn't defined in the template—it's set in the Action, above, and passed to the template explicitly!
{% set flashes = craft.app.session.getAllFlashes() %}

{% for type, flash in flashes %}
    {{ flash }}
{% endfor %}


    {{ actionInput('playground/example/validate') }}
    {{ csrfInput() }}

    
        Thing Size
        

        {% if thing.hasErrors('size') %}
            {{ thing.getErrors('size') | join(', ') }}
        {% endif %}
    

    Validate



This can also be exposed as a regular template based Craft's routing behavior (item four in the list).

Putting it All Together
Once the files are organized in your Craft installation / module, you should be able to visit /validation-demo in your browser, and see a form. Submitting the form with no information should present a validation error. Submitting with valid info (a number greater than 100) should display a message, and reset the form.
Note that the first time you visit /validation-demo, the Controller takes care of populating a Thing for you, as one is not yet available in Route params.
When processing the subsequent POST request, a new Thing is created as a container for the data + validation. When validation fails, we call ->setRouteParams(['key' => $model]) which keeps track of the model in-memory, and tries to match it up with and argument of the Action that the request "falls through" to, after a null value comes back from the original Action.
Bonus: Can we do this without ExampleController::actionConfigure()?
If your original form appears in a normal template (without vanity route mapping via routes.php to a Controller), anything set as a route param will be available in the template—although on the first view, it won't have any idea what thing is, so you would have to code the form “defensively,” i.e. gratuitous use of the null coalescing operator: {% set errors = thing.getErrors('size') ?? [] %}.
Try deleting your route from routes.php and moving the template to templates/validation-demo.twig. Replace any presumptive use of the thing variable in the template with if statements or the null-coalesce operator, and start from the top. There should be no difference in overall behavior, except that thing is a little more volatile to work with, when you're not sure if it'll exist.

I'd like to publish this as a ready-made bundle, but I don't know how useful it is without the AR/storage portion. Let me know if you have ideas!

> **Comments:**
> **username_0**: thanks a lot for this generous explanation of how Modules work. I think what Craft is sorely missing is this kind of tutorials for intermediate and advanced features. In their docs. I always find one line description of what things do, and, at best, partial examples that don't show the full picture.  In my edit I wrote what the issue was. Comparing your examples against mine helped me out figure the error.  Regarding your question, I would publish this as it is. If later you want to add the storage portion, even better. Clear and complete examples are needed badly.  
> **username_1**: Keep in mind that Craft is built on top of Yii, and shares _a lot_ of its DNA—in fact, this demo is predominantly Yii functionality! So, when in doubt, be sure and look at the Yii 2 knowledge-base. I leaned heavily on the [validating input](https://www.yiiframework.com/doc/guide/2.0/en/input-validation) and [Actions](https://www.yiiframework.com/doc/guide/2.0/en/structure-controllers#action-parameters) articles. I'll consider how to package this up, and talk to Matt about what aspects if any would be useful to PR into the documentation.  

---

### Answer by username_2 on 2021-10-23. Score: 2

In the twig template, myModel should be available as a regular twig variable.
Try this:
{{ myModel.id }}

> **Comments:**
> **username_0**: thank you for the feedback. Unfortunately, I'm not able to produce any results that way. If I try this ```{% if myModel is defined %}     {{ 'myModel.action }}{% endif %}``` nothing is outputted.  
> **username_2**: Ah, must be something else then. Will need to see more context. For example, controller action itself needs to return null on validation errors, so are you doing `return $this->validationProblems($myModel)` or `$this->validationProblems($myModel); return null`?  
> **username_0**: thanks so much for chcking on this. I edited my question to include the controller action.  

---

