The best way to collect all Yii model validation errors - yii

Is there any inline method to collect all validation errors?
Now I'm doing this:
$errors = '';
foreach($model->attributeNames() as $attr)
{
$errors .= $model->hasErrors($attr) ? $model->getErrors($attr) : '';
}
I think this is redundant.

You could simply use getErrors without param, it will returns the errors for all attribute.
You could also use Chtml::errorSummary, it will displays a summary of validation errors for one or several models.

Related

Efficient way to bring parameters into controller action URL's

In ASP.Net Core you have multiple ways to generate an URL for controller action, the newest being tag helpers.
Using tag-helpers for GET-requests asp-route is used to specify route parameters. It is from what I understand not supported to use complex objects in route request. And sometimes a page could have many different links pointing to itself, possible with minor addition to the URL for each link.
To me it seems wrong that any modification to controller action signature requires changing all tag-helpers using that action. I.e. if one adds string query to controller, one must add query to model and add asp-route-query="#Model.Query" 20 different places spread across cshtml-files. Using this approach is setting the code up for future bugs.
Is there a more elegant way of handling this? For example some way of having a Request object? (I.e. request object from controller can be put into Model and fed back into action URL.)
In my other answer I found a way to provide request object through Model.
From the SO article #tseng provided I found a smaller solution. This one does not use a request object in Model, but retains all route parameters unless explicitly overridden. It won't allow you to specify route through an request object, which is most often not what you want anyway. But it solved problem in OP.
<a asp-controller="Test" asp-action="HelloWorld" asp-all-route-data="#Context.GetQueryParameters()" asp-route-somestring="optional override">Link</a>
This requires an extension method to convert query parameters into a dictionary.
public static Dictionary GetQueryParameters(this HttpContext context)
{
return context.Request.Query.ToDictionary(d => d.Key, d => d.Value.ToString());
}
There's a rationale here that I don't think you're getting. GET requests are intentionally simplistic. They are supposed to describe a specific resource. They do no have bodies, because you're not supposed to be passing complex data objects in the first place. That's not how the HTTP protocol is designed.
Additionally, query string params should generally be optional. If some bit of data is required in order to identify the resource, it should be part of the main URI (i.e. the path). As such, neglecting to add something like a query param, should simply result in the full data set being returned instead of some subset defined by the query. Or in the case of something like a search page, it generally will result in a form being presented to the user to collect the query. In other words, you action should account for that param being missing and handle that situation accordingly.
Long and short, no, there is no way "elegant" way to handle this, I suppose, but the reason for that is that there doesn't need to be. If you're designing your routes and actions correctly, it's generally not an issue.
To solve this I'd like to have a request object used as route parameters for anchor TagHelper. This means that all route links are defined in only one location, not throughout solution. Changes made to request object model automatically propagates to URL for <a asp-action>-tags.
The benefit of this is reducing number of places in the code we need to change when changing method signature for a controller action. We localize change to model and action only.
I thought writing a tag-helper for a custom asp-object-route could help. I looked into chaining Taghelpers so mine could run before AnchorTagHelper, but that does not work. Creating instance and nesting them requires me to hardcode all properties of ASP.Net Cores AnchorTagHelper, which may require maintenance in the future. Also considered using a custom method with UrlHelper to build URL, but then TagHelper would not work.
The solution I landed on is to use asp-all-route-data as suggested by #kirk-larkin along with an extension method for serializing to Dictionary. Any asp-all-route-* will override values in asp-all-route-data.
<a asp-controller="Test" asp-action="HelloWorld" asp-all-route-data="#Model.RouteParameters.ToDictionary()" asp-route-somestring="optional override">Link</a>
ASP.Net Core can deserialize complex objects (including lists and child objects).
public IActionResult HelloWorld(HelloWorldRequest request) { }
In the request object (when used) would typically have only a few simple properties. But I thought it would be nice if it supported child objects as well. Serializing object into a Dictionary is usually done using reflection, which can be slow. I figured Newtonsoft.Json would be more optimized than writing simple reflection code myself, and found this implementation ready to go:
public static class ExtensionMethods
{
public static IDictionary ToDictionary(this object metaToken)
{
// From https://geeklearning.io/serialize-an-object-to-an-url-encoded-string-in-csharp/
if (metaToken == null)
{
return null;
}
JToken token = metaToken as JToken;
if (token == null)
{
return ToDictionary(JObject.FromObject(metaToken));
}
if (token.HasValues)
{
var contentData = new Dictionary();
foreach (var child in token.Children().ToList())
{
var childContent = child.ToDictionary();
if (childContent != null)
{
contentData = contentData.Concat(childContent)
.ToDictionary(k => k.Key, v => v.Value);
}
}
return contentData;
}
var jValue = token as JValue;
if (jValue?.Value == null)
{
return null;
}
var value = jValue?.Type == JTokenType.Date ?
jValue?.ToString("o", CultureInfo.InvariantCulture) :
jValue?.ToString(CultureInfo.InvariantCulture);
return new Dictionary { { token.Path, value } };
}
}

CodeIgniter and the Model-View-Controller – your experience / your meaning?

I have a “simple” question about the principle from the CodeIgniter MVC.
If I take a look in the manual from CI (Models) I see for example this:
function insert_entry()
{
$this->title = $_POST['title']; // please read the below note
$this->content = $_POST['content'];
$this->date = time();
$this->db->insert('entries', $this);
}
Well, ok – to put in data this way is bad I know :) but also if we user “$this->input->post()” … for me it doesn’t look better. Isn’t it better to handle the data in the controller before I use a function from a model? Maybe the model part looks so:
function insert_entry($data)
{
$this->db->insert('entries', $data);
}
And in the controller such like this:
$this->load->model('Blog');
$data = array();
$data['title'] = $this->input->post('title');
$data['content'] = $this->input->post('content');
$this->Blog->insert_entry($data);
But where i run the validation etc. … model or controller?
Maybe someone understand what I would like to know. Maybe you have some more experience, links or whatever. Thanks!
If you are trying to implement proper MVC or MVC-inspired design pattern with CodeIgniter, you have already failed. CodeIgniter does not follow the ideas of MVC and related patterns. It actually just clones the pattern used in Rails (I can elaborate in comments section, if you want to know why and how).
That said ...
The reason why $this->input->post() is used in controllers is to provide some abstraction and separate your code from PHP's superglobals. What you call a "controller" should collect data from the user's request and pass it to the model layer’s structures. The model layer should be completely unaware of the front-end. The domain business logic for creating an invoice does not change just because you renamed the <input/> for invoice number from "innr" to "number".
The data validation should happen in the model layer. When done properly, the code for validation is part of domain objects and data integrity checks would be handled by storage abstraction (for example, a data mapper), but in CodeIgniter people usually lump both domain and storage logic together and call it: "models". Of course that violated SRP, but CI users don't care and are even unaware of such principles. So basically, when writing for CI, the validation should happen in "models".
If you want to read more about the whole subject, you might find this post relevant.
hi you would have something like
class new_controller extends CI_Controller {
function __construct()
{
parent::__construct();
}
function insert_db_entry() {
$this->load->model('Blog');
$data = array();
if($this->input->post("submit")) {
$this->load->library("form_validation");
//create the form validation rules
if($this->form_validation->run() === TRUE) {
$data['title'] = $this->input->post('title');
$data['content'] = $this->input->post('content');
$this->Blog->insert_entry($data);
}
else {
$errors = validation_errors();
}
}
}
}
you use the form validation library to handle the validation when the form submit is detected.

Yii model is validating but data could not be saved

I have a yii application. Data is validated properly. the $model->validate() returns true but data is not being saved. Is there any way that I know about the error. It does nothing. neither prints error nor any warning.
if (isset($_POST['Invoice'])) {
$model->validate();
$model->attributes = $_POST['Invoice'];
if (!$model->validate()) {
die(CVarDumper::dump($model->errors,10,true));
}
if ($model->save()) {
die("Data saved");
$this->redirect(array('view', 'id' => $model->id));
} else {
CVarDumper::dump($model->attributes,10,true);
CVarDumper::dump($model->errors,10,true);
}
}
if you override beforeSave or afterFind method in your model,
public function beforeSave() {
return true; //don't forget this
}
public function afterFind() {
return true; //don't forget this
}
make sure you return true for those function
If save() is returning true and there are no errors as such in your database and queries. Only thing, thats possible is you haven't marked some of the column safe for mass assignment via "$model->attributes".
Make sure the column you are trying to save are marked safe in the "rules" function in your model. You can mark columns safe via adding the following rule in "rules" function in the model.
array ( "column_name1, column_name2 ....." , "safe" )
I've just ran into something similar to this. Everything was validating correctly, and $model->save() was returning true, but no data was saved in the database.
The problem and solution was that I was creating the $model object like so:
$model = ClassName::model();
but you need to create the object like so:
$model = new ClassName;
If you have this problem, you replace this:
$model->save(false)
This solves your problem.
If you use $model->save(); the filters is running that is not good for you.
Fire up some logging and see what going on...
I got the same error when I was using reCaptcha. I just did this and it worked:
$model->scenario = NULL;
Make sure you do this AFTER validation.
I had the same issue, my mistake was with the post name in the controller, where I used $model->save. I had given wrong - if(isset($_POST['postname']))
If I am not wrong, you are doing an AR save() in the $model->save() method. You do not get any error, but the data is not saved as well.
If this is the case you would like to do a:
die(CVarDumper::dump($arObj->errors,10,true));
after the $arObj->save(); call. Most of the time this happens because of the Database rejecting the values provided for insert or update.
Also do not override your model constructor:
function __construct() { } // don't do this
The issue for me was that I had a property for the column name in the ActiveRecord class, so it wasn't saving.
You should not declare properties for column names as I guess the magic methods __get() and __set() are used to save data, I guess by checking if there are column changes when you click the save() method to avoid useless SQL queries. In my case, because the column was a user-declared property, it wasn't in the columns list and therefore changes to it were not detected.
Hope this helps other people

Doctrine 2 ArrayCollection filter method

Can I filter out results from an arrayCollection in Doctrine 2 while using lazy loading? For example,
// users = ArrayCollection with User entities containing an "active" property
$customer->users->filter('active' => TRUE)->first()
It's unclear for me how the filter method is actually used.
Doctrine now has Criteria which offers a single API for filtering collections with SQL and in PHP, depending on the context.
https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html#filtering-collections
Update
This will achieve the result in the accepted answer, without getting everything from the database.
use Doctrine\Common\Collections\Criteria;
/**
* #ORM\Entity
*/
class Member {
// ...
public function getCommentsFiltered($ids) {
$criteria = Criteria::create()->where(Criteria::expr()->in("id", $ids));
return $this->getComments()->matching($criteria);
}
}
The Boris Guéry answer's at this post, may help you:
Doctrine 2, query inside entities
$idsToFilter = array(1,2,3,4);
$member->getComments()->filter(
function($entry) use ($idsToFilter) {
return in_array($entry->getId(), $idsToFilter);
}
);
Your use case would be :
$ArrayCollectionOfActiveUsers = $customer->users->filter(function($user) {
return $user->getActive() === TRUE;
});
if you add ->first() you'll get only the first entry returned, which is not what you want.
# Sjwdavies
You need to put () around the variable you pass to USE. You can also shorten as in_array return's a boolean already:
$member->getComments()->filter( function($entry) use ($idsToFilter) {
return in_array($entry->getId(), $idsToFilter);
});
The following code will resolve your need:
//$customer = ArrayCollection of customers;
$customer->getUsers()->filter(
function (User $user) {
return $user->getActive() === true;
}
);
The Collection#filter method really does eager load all members.
Filtering at the SQL level will be added in doctrine 2.3.

PDO bind paramaters with an array

I am trying to create a method in PHP, that will dynamically bind parameters to a PDO query statement. Unfortunately, in my code below, i can only bind 1 parameter, because adding more parameters will override the previous parameters. Nevertheless, is there a good way to fix this problem?
I hope someone can help. Thanks!
function executeQuery($query, $critArr = null) {
$rows = array();
try {
$stmt=$this->pdo->prepare($query);
if (!empty($critArr)) {
foreach($critArr as $cKey=>$cValue) {
$stmt->bindParam($cKey, $cValue); //!!
}
}
$stmt->execute();
You don't need to do that. The execute method already takes an array of parameters:
function executeQuery($query, $critArr = null) {
$rows = array();
try {
$stmt=$this->pdo->prepare($query);
$stmt->execute($critArr);
// ...
The original problem is that bindParam works by reference, and foreach simply reuses the same variables over and over rather than destroy them at the bottom of the loop and (re)create them at the top. You were effectively re-binding the same variable over and over. (Incidentally, this is the same problem that the mysqli extension has, while it also lacks the convenient already-takes-an-array execute method.)
Your function improved with passing foreach $cValue by reference &$cValue. This should solve your problem.
function executeQuery($query, $critArr = null) {
$rows = array();
try {
$stmt=$this->pdo->prepare($query);
if (!empty($critArr)) {
foreach($critArr as $cKey=>&$cValue) {
$stmt->bindParam($cKey, $cValue); //!!
}
}
$stmt->execute();