I'm looping through users in my controller function and sending an email to each user, but there's a special user who will sometimes be in the list who shouldn't receive emails.
I'd like to put an if statement in to skip that loop iteration if it's that user, but when I add return, return null etc, or just nothing in my if/else in the mailable class build function I get
InvalidArgumentException
Invalid view.
I could add the conditional in the controller and I'm sure that would be fine, except I have these emails in a whole load of my controllers so would be writing the conditional many times. If I can get it into a central location (and the mailable class is the only one I'm aware of) then I can write it once.
EDIT: the loop code has been requested so added below, but this is just one of many email loops in my controllers so NOT where I'd like to add my conditional.
$objNotification = new \stdClass();
$objNotification->message_body = "stuff";
$user_ids = DB::select('select user_id from users_to_things where thing_id = ?', [$thing->id]);
foreach($user_ids as $user_id) {
$user = User::find($user_id->user_id);
$objNotification->receiver = $user->name;
Mail::to($user->email)->send(new NotificationEmail($objNotification));
}
Here's my mailable class's build function (where I DO want to add the conditional if at all possible):
public function build()
{
if($this->notification->receiver == 'the special user') {
return;
} else {
return $this->from('somebody#somewhere.com', 'Sender Name')
->subject('some subject')
->view('emails.notification')
->text('emails.notification_plain');
}
}
What you want would conflict with the Single Responsibility Principle. The class is only suppose to send the email.
The build() in your mailable class returns error cause it expect you to return a message ( subject ,body, template or view, etc) for your mail.
1.) maybe instead you can assert a fake mail in your condition;
2.) Create a function in email class
public function needSend($mail)
{
... loads of conditions
return (condition) ? true : false;
}
then
if ($mail->needSend($mail)) {
\Mail::send($mail);
}
3.) Or the easiest but dirtiest way is you can put the condition inside your Controller in your for loop
foreach($user_ids as $user_id) {
$user = User::find($user_id->user_id);
$objNotification->receiver = $user->name;
if(condition == true) continue;
Mail::to($user->email)->send(new NotificationEmail($objNotification));
}
You can also read this thread on github. https://github.com/laravel/ideas/issues/519
Using the notification class, check your condition like a suppression list or maybe runtime condition. Return an empty array and it'll exit.
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return $notifiable->notificationSuppression()->exists() ? [] : ['mail'];
}
I use Laravel 5.2
I wanted to change this error :
Whoops, looks like something went wrong.
first please see : Laravel
I created a new file resources/views/errors/404.blade.php but my app error didn't change !
it change just when not found url at route but when insert url injection in to $_GET it show "whoops .." yet
for example work for this link : http://domain.com/dgdgergehrhddg54d6g8
but not working for this injection : http://domain.com/listmanage=8 insert 9 instens of 8
error message when debug is true :
ErrorException: file.php line 215
Trying to get property of non-object
You are viewing that error page because of the environment you are into.
As default, for local environments, the "Whoops" format is shown.
For Production environments, the error/x.blade.php files are used.
To customize this you simply go to: ./app/Exceptions/Handler.php
And modify the render function. You can do something like this:
public function render($request, Exception $e)
{
// If an ErrorException is received and this enviroment is local
if ($e instanceof \ErrorException && app()->environment() == 'local') {
// Show customized page
return response()->view('errors.404', [], $e->getCode());
}
return parent::render($request, $e);
}
Cheers :)
on Larvel 5.2 on your app/exceptions/handler.php just extend this method renderHttpException ie add this method to handler.php
/**
* Render the given HttpException.
*
* #param \Symfony\Component\HttpKernel\Exception\HttpException $e
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function renderHttpException(HttpException $e)
{
// to get status code ie 404, 503, 500
$status = $e->getStatusCode();
if (view()->exists("errors.{$status}")) {
return response()->view("errors.{$status}", ['exception' => $e], $status, $e->getHeaders());
} else {
return $this->convertExceptionToResponse($e);
}
}
Hope that helps.
I've created a widget with a CActiveForm in it. Everything works ok, but now i want to enable ajax validation for it.
The problem is that the output of my ajax validation is containing, besides the validation JSON string, all (well a part of it, since Yii::app()->end() stops the rest) of my html as well. Not weird, because i'm using it within a widget and the validation request is done to the controller/action where i've placed this widget on.
Is there some way to prevent outputting all the html, so a valid JSON string is returned?
I've already tried to set the validationUrl in the CActiveForm to another controller/action, but the problem is that i have to send the model with it and this model is determined in my widget and not on the validationUrl.
Widget:
public function run()
{
$model = new User;
$model->scenario = 'create';
$this->performAjaxValidation($model);
if (isset($_POST['User'])) {
$model->attributes = $_POST['User'];
if ($model->save()) {
}
}
$this->render('register-form', array(
'model' => $model
));
}
/**
* Performs the AJAX validation.
* #param User $model the model to be validated
*/
protected function performAjaxValidation($model)
{
if(isset($_POST['ajax']))
{
echo CActiveForm::validate($model);
Yii::app()->end();
}
}
Output of performAjaxValidation() (the ajax call):
.. more html here ..
<section class="box">
<h1>Register form simple</h1>
{"UserPartialSignup_email":["Email is geen geldig emailadres."]}
I solved it this way:
I've created an AJAX controller where the validation is done:
AjaxController:
/**
* Validates a model.
*
* Validates a model, POST containing the data. This method is usually used for widget based forms.
*
* #param $m model name which have to be validated
* #param $s scenario for this model, optional.
* #return string JSON containing the validation data
*/
public function actionValidate($m, $s = null)
{
if ($this->checkValidationData($m, $s) && isset($_POST['ajax']))
{
$model = new $m;
$model->scenario = $s;
echo CActiveForm::validate($model);
Yii::app()->end();
} else {
throw new CHttpException(500, 'No valid validation combination used');
}
}
You can give the model name and a scenario as GET parameters with it, i'm checking if this combination is allowed by the checkValidationData() method.
In the view of my widget where the CActiveForm is placed, i've added the validationUrl, referring to ajax/validate:
widgets/views/registerform.php:
<?php $form = $this->beginWidget('CActiveForm', array(
'id'=>'signup-form-advanced',
'enableAjaxValidation'=>true,
'clientOptions' => array(
'validationUrl' => array('ajax/validate', 'm' => get_class($model), 's' => 'create')
)
//'enableClientValidation'=>true,
)); ?>
How can we validate form fields that are arrays? Take a look at the following code
UserPhone Model:
public static $rules= array(
'phonenumber'=>'required|numeric',
'isPrimary'=>'in:0,1'
)
...........
UserController:
$validation = UserPhone::validate(Input::only('phonenumber')));
if($validation->passes())
{
$allInputs = Input::only('phonenumber','tid');
$loopSize = sizeOf($allInputs);
for($i=0;$i<$loopSize;$i++)
{
$phone = UserPhone::find($allInputs['tid'][$i]);
$phone->phonenumber = $allInputs['phonenumber'][$i];
$phone->save();
}
return Redirect::to('myprofile')->with('message','Update OK');
}
else
{
return Redirect::to('editPhone')->withErrors($validation);
}
}
the $validation comes from a BaseModel which extends Eloquent.
In my view:
<?php $counter=1; ?>
#foreach($phones as $thephone)
<section class="col col-12">
<label class="label">Phone Number {{$counter++}}</label>
<label class="input">
<i class="icon-append icon-phone"></i>
{{Form::text('phonenumber[]',$thephone->phonenumber)}}
{{Form::hidden('tid[]',$thephone->id)}}
</label>
</section>
#endforeach
Everything is working fine and I get all the phone numbers I want in the Update Form, but I cannot update the model because the validation fails with the message "Phonenumber must be a number".
I know that there is not a simple solution for validating array form fields and I tried to extend the validator class but with no success.
How can I validate this kind of fields?
Here's the solution I use:
Usage
Simply transform your usual rules by prefixing each. For example:
'names' => 'required|array|each:exists,users,name'
Note that the each rule assumes your field is an array, so don't forget to use the array rule before as shown here.
Error Messages
Error messages will be automatically calculated by the singular form (using Laravel's str_singular() helper) of your field. In the previous example, the attribute is name.
Nested Arrays
This method works out of the box with nested arrays of any depth in dot notation. For example, this works:
'members.names' => 'required|array|each:exists,users,name'
Again, the attribute used for error messages here will be name.
Custom Rules
This method supports any of your custom rules out of the box.
Implementation
1. Extend the validator class
class ExtendedValidator extends Illuminate\Validation\Validator {
public function validateEach($attribute, $value, $parameters)
{
// Transform the each rule
// For example, `each:exists,users,name` becomes `exists:users,name`
$ruleName = array_shift($parameters);
$rule = $ruleName.(count($parameters) > 0 ? ':'.implode(',', $parameters) : '');
foreach ($value as $arrayKey => $arrayValue)
{
$this->validate($attribute.'.'.$arrayKey, $rule);
}
// Always return true, since the errors occur for individual elements.
return true;
}
protected function getAttribute($attribute)
{
// Get the second to last segment in singular form for arrays.
// For example, `group.names.0` becomes `name`.
if (str_contains($attribute, '.'))
{
$segments = explode('.', $attribute);
$attribute = str_singular($segments[count($segments) - 2]);
}
return parent::getAttribute($attribute);
}
}
2. Register your validator extension
Anywhere in your usual bootstrap locations, add the following code:
Validator::resolver(function($translator, $data, $rules, $messages)
{
return new ExtendedValidator($translator, $data, $rules, $messages);
});
And that's it! Enjoy!
Bonus: Size rules with arrays
As a comment pointed out, there's seems to be no easy way to validate array sizes. However, the Laravel documentation is lacking for size rules: it doesn't mention that it can count array elements. This means you're actually allowed to use size, min, max and between rules to count array elements.
It works best to extend the Validator class and re-use the existing Validator functions:
Validator::resolver(function($translator, $data, $rules, $messages)
{
return new Validation($translator, $data, $rules, $messages);
});
class Validation extends Illuminate\Validation\Validator {
/**
* Magically adds validation methods. Normally the Laravel Validation methods
* only support single values to be validated like 'numeric', 'alpha', etc.
* Here we copy those methods to work also for arrays, so we can validate
* if a value is OR an array contains only 'numeric', 'alpha', etc. values.
*
* $rules = array(
* 'row_id' => 'required|integerOrArray', // "row_id" must be an integer OR an array containing only integer values
* 'type' => 'inOrArray:foo,bar' // "type" must be 'foo' or 'bar' OR an array containing nothing but those values
* );
*
* #param string $method Name of the validation to perform e.g. 'numeric', 'alpha', etc.
* #param array $parameters Contains the value to be validated, as well as additional validation information e.g. min:?, max:?, etc.
*/
public function __call($method, $parameters)
{
// Convert method name to its non-array counterpart (e.g. validateNumericArray converts to validateNumeric)
if (substr($method, -7) === 'OrArray')
$method = substr($method, 0, -7);
// Call original method when we are dealing with a single value only, instead of an array
if (! is_array($parameters[1]))
return call_user_func_array(array($this, $method), $parameters);
$success = true;
foreach ($parameters[1] as $value) {
$parameters[1] = $value;
$success &= call_user_func_array(array($this, $method), $parameters);
}
return $success;
}
/**
* All ...OrArray validation functions can use their non-array error message counterparts
*
* #param mixed $attribute The value under validation
* #param string $rule Validation rule
*/
protected function getMessage($attribute, $rule)
{
if (substr($rule, -7) === 'OrArray')
$rule = substr($rule, 0, -7);
return parent::getMessage($attribute, $rule);
}
}
each()
It's not in the docs, but the 4.2 branch may have a simple solution around line 220.
Just like the sometimes($attribute, $rules, callable $callback) function, there is now an each($attribute, $rules) function.
To use it, the code would be something simpler than a sometimes() call:
$v->each('array_attribute',array('rule','anotherRule')); //$v is your validator
Caveats
sometimes() and each() don't seem to be easily chainable with each other so if you want to do specifically conditioned rules on array values, you're better off with the magic solutions in other answers for now.
each() only goes one level deep which isn't that different from other solutions. The nice thing about the magic solutions is that they will go 0 or 1 level deep as needed by calling the base rules as appropriate so I suppose if you wanted to go 1 to 2 levels deep, you could simply merge the two approaches by calling each() and passing it a magic rule from the other answers.
each() only takes one attribute, not an array of attributes as sometimes() does, but adding this feature to each() wouldn't be a massive change to the each() function - just loop through the $attribute and array_merge() $data and the array_get() result. Someone can make it a pull request on master if they see it as desirable and it hasn't already been done and we can see if it makes it into a future build.
Here's an update to the code of Ronald, because my custom rules wouldn't work with the array extension. Tested with Laravel 4.1, default rules, extended rules, …
public function __call($method, $parameters) {
$isArrayRule = FALSE;
if(substr($method, -5) === 'Array') {
$method = substr($method, 0, -5);
$isArrayRule = TRUE;
}
//
$rule = snake_case(substr($method, 8));
// Default or custom rule
if(!$isArrayRule) {
// And we have a default value (not an array)
if(!is_array($parameters[1])) {
// Try getting the custom validation rule
if(isset($this->extensions[$rule])) {
return $this->callExtension($rule, $parameters);
}
// None found
throw new \BadMethodCallException("Method [$method] does not exist.");
} // Array given for default rule; cannot be!
else return FALSE;
}
// Array rules
$success = TRUE;
foreach($parameters[1] as $value) {
$parameters[1] = $value;
// Default rule exists, use it
if(is_callable("parent::$method")) {
$success &= call_user_func_array(array($this, $method), $parameters);
} else {
// Try a custom rule
if(isset($this->extensions[$rule])) {
$success &= $this->callExtension($rule, $parameters);
}
// No custom rule found
throw new \BadMethodCallException("Method [$method] does not exist.");
}
}
// Did any of them (array rules) fail?
return $success;
}
There are now array validation rules in case this helps anybody. It doesn't appear that these have been written up in the docs yet.
https://github.com/laravel/laravel/commit/6a2ad475cfb21d12936cbbb544d8a136fc73be97
I have this code in my controller (admin):
function save(){
$model = $this->getModel('mymodel');
if ($model->store($post)) {
$msg = JText::_( 'Yes!' );
} else {
$msg = JText::_( 'Error :(' );
}
$link = 'index.php?option=com_mycomponent&view=myview';
$this->setRedirect($link, $msg);
}
In model I have:
function store(){
$row =& $this->getTable();
$data = JRequest::get('post');
if(strlen($data['fl'])!=0){
return false;
}
[...]
And this is working - generate error message, but it return to items list view. I want to stay in edit view with entered data. How to do it?
In your controller you can:
if ($model->store($post)) {
$msg = JText::_( 'Yes!' );
} else {
// stores the data in your session
$app->setUserState('com_mycomponent.edit.mymodel.data', $validData);
// Redirect to the edit view
$msg = JText::_( 'Error :(' );
$this->setError('Save failed', $model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(JRoute::_('index.php?option=com_mycomponent&view=myview&id=XX'), false));
}
then, you will need to load the data from session with something like:
JFactory::getApplication()->getUserState('com_mycomponent.edit.mymodel.data', array());
normally this is loaded in the method "loadFormData" in your model. Where to load that data will depend on how are you implementing your component. If you are using the Joomla's form api then you can add the following method to your model.
protected function loadFormData()
{
// Check the session for previously entered form data.
$data = JFactory::getApplication()->getUserState('com_mycomponent.edit.mymodel.data', array());
if (empty($data)) {
$data = $this->getItem();
}
return $data;
}
EDIT:
BUT please note, that Joomla's API already can do all this for you if you controller inherits from "JControllerForm", you don't need to rewrite the save method. The best way to create your component is copying what is in Joomla's core components, com_content for example
It is not recommended to rewrite save or any method.
If you really want to override something and want to update something before or after save, you should use JTable file.
For Example:
/**
* Example table
*/
class HelloworldTableExample extends JTable
{
/**
* Method to store a node in the database table.
*
* #param boolean $updateNulls True to update fields even if they are null.
*
* #return boolean True on success.
*/
public function store($updateNulls = false)
{
// This change is before save
$this->name = str_replace(' ', '_', $this->name);
if (!parent::store($updateNulls))
{
return false;
}
// This function will be called after saving table
AnotherClass::functionIsCallingAfterSaving();
}
}
You can extends any method using JTable class and that's the recommended way to doing it.