What's the basic difference between Laravel's foreach and forelse? How does forelse work?
I know that foreach duplicates your given array and works on it while in forelse it checks whether it exists and loops on it if it exists but if not it uses your provided else condition.
I believe the answer to your question is that, essentially, ForElse is a ForEach loop, but with extra handling for empty array.
From the Laravel 5 docs on Blade Templates, an example illustrating both loops with the same list of Users as Input:
#foreach ($users as $user)
<p>This is user {{ $user->id }}</p>
#endforeach
#forelse ($users as $user)
<li>{{ $user->name }}</li>
#empty
<p>No users</p>
#endforelse
Now if we compare this to the source code for the Laravel Framework to see how the loops are compiled (all Blades constructs are compiled into HTML when being rendered by PHP on the web page):
/**
* Compile the for-each statements into valid PHP.
*
* #param string $expression
* #return string
*/
protected function compileForeach($expression)
{
preg_match('/\( *(.*) +as *(.*)\)$/is', $expression, $matches);
$iteratee = trim($matches[1]);
$iteration = trim($matches[2]);
$initLoop = "\$__currentLoopData = {$iteratee}; \$__env->addLoop(\$__currentLoopData);";
$iterateLoop = '$__env->incrementLoopIndices(); $loop = $__env->getLastLoop();';
return "<?php {$initLoop} foreach(\$__currentLoopData as {$iteration}): {$iterateLoop} ?>";
}
/**
* Compile the for-else statements into valid PHP.
*
* #param string $expression
* #return string
*/
protected function compileForelse($expression)
{
$empty = '$__empty_'.++$this->forElseCounter;
preg_match('/\( *(.*) +as *(.*)\)$/is', $expression, $matches);
$iteratee = trim($matches[1]);
$iteration = trim($matches[2]);
$initLoop = "\$__currentLoopData = {$iteratee}; \$__env->addLoop(\$__currentLoopData);";
$iterateLoop = '$__env->incrementLoopIndices(); $loop = $__env->getLastLoop();';
return "<?php {$empty} = true; {$initLoop} foreach(\$__currentLoopData as {$iteration}): {$iterateLoop} {$empty} = false; ?>";
}
Essentially, the ForElse loop has a built-in code and compile check for an empty input, in which case it optionally outputs an alternate display template for the empty input.
I would imagine you can use a ForEach loop in every case that you might use a ForElse loop, adding that you include and empty checks that ForElse might catch for you.
Links for reference:
Blade Templates
Blade View Compile Functions
The foreach loop as you described loops over each element in an array, the foresee loop in essence is exactly the same with one extra element as you already noticed, the else statement. The primary use for this to use when you have an empty array.
Thus looping over all elements and for example rendering a row of a table but putting an empty notice when you do not have any data.
See the basic difference is in case of no data found
foreach loop you have to check count condition and then print as per output. This will increase little bit of effort but in forelse it auto check and using #empty you have to echo no data found..
Related
I want to validate the array inputs value to already selected or not. In my case, I am using Laravel Livewire and I have dynamic inputs such as I can add new options like Size, Color. But I do not want to select the same value in two inputs like the select size in two inputs. Basically, If select the same value in the second input, I want to show it already selected.
Below code livewire controller,
public $i = -1;
public $inputs = [];
public $options = ['Size', 'Color', 'Material', 'Style'];
public $option_name;
function rules()
{
$rules = [];
if ($this->inputs) {
foreach ($this->inputs as $key => $val) {
$rules['option_name.' . $val] = ['required'];
}
return $rules;
}
}
public function addLine($i)
{
$i = $i + 1;
$this->i = $i;
array_push($this->inputs, $i);
}
public function updated($propertyName)
{
if ($this->inputs) {
$this->validateOnly($propertyName);
}
}
Below code livewire view code,
#forelse ($inputs as $key => $value)
<x-native-select label="Option name" :options="$options" wire:model="option_name.{{ $value }}"/> // I am using livewire-wireui
#empty
#endforelse
<a wire:click="addLine({{ $i }})" class="mt-4 text-sm cursor-pointer">
+ Add another option
</a>
Any idea how to validate this?
Try this and let me see how it goes.
public function updatedOptionName($selectedOption) {
//You can use laravel collection filter as well depending on how commplicated you want it
$this->options = array_filter($this->options, function ($value) {
return $value != $selectedOption;
}, ARRAY_FILTER_USE_BOTH));
}
The above function would filter out the selected option so that the option is not available when it is already selected. It might be overkill and subject to improvement from the community. All reviews are welcome. One thing to note is that you might want a sort of reset method in case the user decides to start all over again. Something along the lines of public $originalOptions that you would want to set on mount and then a reset function that assigns this->options to it. Let me know if it works.
More info on those functions Lifecycle hooks
I am displaying my data using datatable, if empty array then datatable will not be shown.
I tried
#if(count($results) > 0)
//data show
#foreach($results as $r)
<p>{{$r->name}}</p>
#endfor
#else
No result found.
#endif
//
// $results is an array
This will return error if empty $result. How can i make it if $result is empty then show the custom message and not to display DataTable.
You should try this:
#if(isset($results) && !empty($results))
<?php
print('<pre style="color:red;">');
print_r($results);
print('</pre>');
?>
#foreach($results as $r)
<p>{{$r->name}}</p>
#endforeach
#else
No result found.
#endif
We do have a shorthand in blade for the foreach/empty scenarios.
#forelse ($results as $r)
<p>{{ $r->name }}</p>
#empty
No result found
#endforelse
From the code you have and the comment you have of how you get $results, $results should be an array of stdClass objects, which means that error should not be happening. Since the code you have is limited, I can't be sure the error is coming from that code at all.
My guess is that $results isn't an array but is an object. count(new stdClass) === 1 count(new App\User) === 1, etc ...
You are ending the foreach loop with endfor. It should be ended with endforeach.
If this do not fix your error then please update your question with your controller code. Is your results variable a collection object?
Maybe is safer to check at the controller level and return 0 if $result is NULL
After you get the collection just check this:
$result = $result ? $result : 0
And return $result.
Use isEmpty() to check object empty or not-
#if (!$result->isEmpty())
Ok, I am totally re-writing this question, now that I am a bit more familiar with larval.
Here is my situation: I have a guitar lessons site based on larval 5.2.36, where each lesson belongs to a category, and within a lesson are several exercises. An exercise table does not have a category id as it is linked to a lesson which has a category.
Goal What I am trying to figure out is how to pass the category of the currently displayed lesson or exercise to a menu sidebar view that displays the categories, so that the category of the lesson or exercise is highlighted. For this, I need to understand how to do such a task in laravel.
From what I gathered, this is often done via controllers. However, there is no menu controller, but rather a menu composer. It contains a function
class MenuComposer
{
public function compose(View $view)
{
$minutes = 6 * 60;
$value = Cache::remember('menu-categories', $minutes, function() {
return \App\Category::with('parent')->with('children')->get();
});
$view->with('categories', $value);
}
}
Then in the menu blade file we have
#foreach ($categories as $category)
<?php $category = $category->present(); ?>
#if ($category->parent == null)
<li>{{ $category->title }}</li>
#foreach ($category->children as $child)
<?php $child = $child->present() ?>
<li class="level1">{{ $child->title }}</li>
<?php
/*
#foreach ($child->children as $grandChild)
<?php $grandChild = $grandChild->present() ?>
<li class="level2">{{ $grandChild->title }}</li>
#endforeach
*/
?>
#endforeach
#endif
#endforeach
So this is clear. I see that I can use the menu composer to pass additional data with a $view->with() call.
The question is how do I get the current category? For exercises and lessons, the routes don't have category data. They are of form
lesson/lessonid/lessontitle
or
exercise/exid/extitle
So I know I could do some sort of query of the model. But seems that wouldn't make sense, since I know there are other places in the process flow where the current cat is being passed. For instance, on an exercise page, the view is retrieving category as
$exercise->lesson->category->title
It is being passed this in exercise controller as
public function index($id, $name = null)
{
//$this->hit($id);
$exercise = $this->apiController->get($id);
$authorized = $this->isUserAuthorized();
return view('exercise/index', [
'exercise' => $exercise->present(),
'authorized' => $authorized,
]);
}
Similarly, a lesson controller passes $lesson object to lesson view as
public function index($id, $name = null)
{
//$this->hit($id);
$lesson = $this->apiController->get($id);
$subscribed = $this->request->user() && $this->request->user()->subscribed('premium');
return view('lesson/index', [
'lesson' => $lesson->present(),
'subscribed' => $subscribed,
]);
}
Based on above, seems I could modify the return statements in the lesson and exercise controller to pass the category to the menu view, but I don't see in the documentation how to do that, and I suspect the menu view is rendered before the lesson and exercise controller are called...
Also read about using service providers. middleware, etc, here: How to pass data to all views in Laravel 5?
But all these approaches seem overkill. I don't need every view to have the data. Seems to me, I need to do this somehow in the menu composer. But I don't know what method to use from the menu composer to retrieve the current lesson or exercise category. In the menu composer after debugging in phpstorm I see that the $view object for a lesson has $view->$data->$lesson->$entity.
So what I did was edited the menu composer to pass category to view:
$d=$view->getdata();
$s=array_key_exists ('lesson' , $d );
if ($s ==1) $attr = collect($d)->get('lesson');
$cat=$attr->cat();
This works since in the LessonPresenter I added function
public function cat()
{
$cat = $this->entity->category['attributes']['title'];
return $cat;
}
This works, but I feel like it is a hack. And I will have to do this for the Exercise Presenter as well. Being new to larval I suspect there has to be a more elegant way to do this. So can someone please explain how this should be done?
thanks,
Brian
You can use Facades of Laravel directly in blade templates.
Just use {! !} syntax to try and echo it. e.g: {!! Route::current() !!}
There are also similar functions of Route facade you can use.
Then, you can check your category with #if() ... #endif blocks and add something like class name within it.
Note: Don't put lots of logic in your blade files. Do it in your controller file (even in your other service classes) and pass simplest variables (e.g $isCurrentCategory) as an array to your template files using View::make() function's 2nd parameter.
Maybe this can help you
<a href="#" class="{{ (\Request::route()->getName() == 'routename') ? 'active' : '' }}">
You can also get the route prefix for example, you can check this out here:
Laravel API Docs Routing
I try to show two flash messages in Laravel using with:
return redirect('cabinet/result')->with('user', $client->unique_code)->with('fio', $client->name.' '.$client->secondname. ' '.$client->patronymic);
Then I display this as:
{{ session('fio') }} {{ session('unique_code') }}
It shows me nothing
Firstly, when you pass data with the method 'with' to a view, it does not get stored in the session, it is just made available as a variable with the same name to the view that gets loaded after the redirect takes place.
You have two options:
Passing an array of key value pairs to the view
You may pass an array of data to views:
return view('greetings', ['name' => 'Victoria', 'last_name' => 'Queen']);
As you can see by the way the method is implemented in {root}/vendor/laravel/framework/src/Illuminate/View/View.php
/**
* Add a piece of data to the view.
*
* #param string|array $key
* #param mixed $value
* #return $this
*/
public function with($key, $value = null)
{
if (is_array($key)) {
$this->data = array_merge($this->data, $key);
} else {
$this->data[$key] = $value;
}
return $this;
}
the method accepts either a key value pair, or an array. All the keys of this array will be available in the view that is loaded next as php variables with the same name (of course you need to append the dollar sign to the calls in the view). So in the 'greetings' view you would retrieve them as such:
$variable1 = {{ $name }}
$variable2 = {{ $last_name }}
Flashing an array of key value pairs to the next session
You can do pretty much the same using the flashInput method that is found in {root}/vendor/laravel/framework/src/Illuminate/Session/Store.php:
/**
* Flash a key / value pair to the session.
*
* #param string $key
* #param mixed $value
* #return void
*/
public function flash($key, $value)
{
$this->put($key, $value);
$this->push('_flash.new', $key);
$this->removeFromOldFlashData([$key]);
}
You would do that as such:
$request->session()->flashInput('flashData' => ['key1' => value1, 'key2' => value2]);
The difference here is that the data would not be available as variables to your loaded view. Instead they would be stored in an associative array in the session and you would retrieve the stored values this way:
$variable1 = {{ session('flashData['key1']) }}
$variable2 = {{ session('flashData['key2']) }}
Resources
https://laravel.com/docs/5.3/views
https://laravel.com/docs/5.3/session
If you feel that this solved your problem please mark the answer as accepted :)
First make sure your queries return data.
In my projects, I use simple way for doing it.
$user = $client->unique_code; //now user has the code
$fio = $client->name.' '.$client->secondname. ' '.$client->patronymic;
//please make sure this returns your indented result.
return redirect('cabinet/result')->with('user', $user)->with('fio',$fio );
I hope this will work,
Try this code:
$user = 'user';
$fio = 'fio';
return redirect('cabinet/result')
->with('user', $user)
->with('fio', $fio);
For View:
{{ Session::get('user') }} {{ Session::get('fio') }}
I have a collection which I want to iterate and modify while I fetch some of its elements. But I could't find a way or method to remove that fetched element.
$selected = [];
foreach ($collection as $key => $value) {
if ($collection->selected == true) {
$selected[] = $value;
unset($value);
}
}
This is just a representation of my question for demonstration.
After #Ohgodwhy advice the forget() function I checked it again and saw that I actually misunderstood the function. It was exactly as I was looking for.
So for working solution I have added $collection->forget($key) inside the if statement.
Below is the working solution of my problem, using #Ohgodwhy's solution:
$selected = [];
foreach ($collection as $key => $value) {
if ($collection->selected == true) {
$selected[] = $value;
$collection->forget($key);
}
}
(this is just a demonstration)
You would want to use ->forget()
$collection->forget($key);
Link to the forget method documentation
Or you can use reject method
$newColection = $collection->reject(function($element) {
return $item->selected != true;
});
or pull method
$selected = [];
foreach ($collection as $key => $item) {
if ($item->selected == true) {
$selected[] = $collection->pull($key);
}
}
Laravel Collection implements the PHP ArrayAccess interface (which is why using foreach is possible in the first place).
If you have the key already you can just use PHP unset.
I prefer this, because it clearly modifies the collection in place, and is easy to remember.
foreach ($collection as $key => $value) {
unset($collection[$key]);
}
I'm not fine with solutions that iterates over a collection and inside the loop manipulating the content of even that collection. This can result in unexpected behaviour.
See also here: https://stackoverflow.com/a/2304578/655224 and in a comment the given link http://php.net/manual/en/control-structures.foreach.php#88578
So, when using foreach it seems to be ok but IMHO the much more readable and simple solution is to filter your collection to a new one.
/**
* Filter all `selected` items
*
* #link https://laravel.com/docs/7.x/collections#method-filter
*/
$selected = $collection->filter(function($value, $key) {
return $value->selected;
})->toArray();
If you know the key which you unset then put directly by comma
separated
unset($attr['placeholder'], $attr['autocomplete']);
You can use methods of Collection like pull, forget, reject etc.
But every Collection method returns an entirely new Collection instance.
Doc link: https://laravel.com/docs/8.x/collections#introduction