How to convert array in controller to string in views in laravel - laravel

I have a controller which passes an array data to view. Inside the view, I wanted to use the data as string in JSON object format.
Here is my controller:
class TestController extends Controller
{
private $user;
public function index()
{
return view('app')->with([
'userdata' => array('user' => 'John', 'age' => 20),
'access_token' => 'token_here'
]);
}
}
Here is my view app.php
<html>
<-- more html codes--->
<script>
let userdata = "{{ $userdata }}"; // ERROR: htmlspecialchars() expects parameter 1 to be string, array given
</script>
<-- more html codes--->
</html>
I tried using implode,
<script>
let userdata = "{{ implode(' ', $userdata)";
console.log(userdata);
</script>
It didn't have an error, but the problem is, the result becomes:
{"userdata":{&quotuser&quot .....}
How can I have a correct result like this:
{'userdata': {'user':'john', 'age': 20}...} // this should be a string
Does anybody know?

You can use json_encode:
{!! json_encode($userdata) !!}
note that you have to use {!! !!} to get exact string what you want. strings placed in {{ }} are escaped
Also you can user blade directive #json . depends which version of laravel you are using:
#json($userdata)

From Laravel blade docs.
Rendering JSON
Sometimes you may pass an array to your view with the intention of rendering it as JSON in order to initialize a JavaScript variable. For example:
<script>
var app = <?php echo json_encode($array); ?>;
</script>
However, instead of manually calling json_encode, you may use the #json Blade directive. The #json directive accepts the same arguments as PHP's json_encode function:
<script>
var app = #json($array);
var app = #json($array, JSON_PRETTY_PRINT);
</script>

Related

Laravel Blade how to pass $attributes to component from controller

I have a blade component containing $attributes; It would be a bag of attributes when the component called from another blade template but when from the controller via view() the $attributes is undefined! How can I pass data as $attributes from the controller?
Component: sample
<div {{ $attributes->except('content') }}>{{ $content }}</div>
Template: works well.
...
<x-sample class="test" content="test"/>
...
Controller: Error Undefined variable $attributes
$attributes = ['class' => 'test', 'content' => 'test'];
view('components.sample', $attributes)->render();
view('components.sample', ['attributes' => $attributes])->render();
view('components.sample')->with($attributes)->render();
UPDATE (Solution):
It works:
view('components.sample', [
'prop1' => '...',
'prop2' => '...',
'attributes' => new ComponentAttributeBag(['attr1' => '...', 'attr2' => '...']),
])->render();
what is happening is components inherit variable from view, if you define your variable from home view for example, it should work in component from that view. Then when you include the view to about page the variables wont be recognized since they are not inherited from about view. And in Laravel you cannot pass data directly form controller to component. but laravel have solved it by View Composer.
Since I dont like too much complication especially with providers I edited my AppServiceProvider you can create your own provider.
in YourServiceProvider in my case AppServiceProvider in boot() function there are three options you can use one. I recommend there 3rd one since its clean
public function boot()
{
// option1 - Every single view
View::share('shops', Shop::orderBy('name')->get());
// option2 - Gradular views with wildcards
View::composer(['client.*'], function ($view){
$view->with('shops', Shop::orderBy('name')->get());
});
// option3 - Dedicated class
View::composer(['client.includes.*','client.create-product','client.cart'], ShopsComposer::class);
View::composer(['client.includes.*','client.cart'], CartComposer::class);
}
If you use the 3rd method you have to create ViewComposer class
<?php
namespace App\Http\View\Composers;
use App\Models\CartItem;
use Illuminate\Support\Facades\Session;
use Illuminate\View\View;
class ShopsComposer
{
public function compose(View $view){
$shop = auth()->user()->shops->sortBy('name');
$cartItem = new CartItem();
$cartCount = 0;
if (Session::has('cartId')) {
$carts = Session::get('cartId');
$cartItems = $cartItem->with('products')->where('cart_id', '=', $carts->id)->get();
foreach ($cartItems as $item) {
$cartCount += $item->quantity;
}
}
$view->with('shopsComposer', $shop)->with('cartCount', $cartCount);
}
}
The variables you define there will be available to all the views. that includes components. Since component inherit variable from view file.
I am sorry I had to share my working example, since I am running out of time. I hope it works if I understood well your question.

Vue.js with Laravel Permission

I am in the process of integrating Laravel Permission API with Vue.JS frontend. I am using https://github.com/spatie/laravel-permission library for Laravel Permission. I am not understanding how can I check permission in the Vue JS front End (In Laravel blade I am using #Can to check the permission).
I will do a ajax call to check for permissions instead , something like this, but of cours eyou need to modify it to cater your needs.
Routes:
Route::get('/permission/{permissionName}', 'PermissionController#check');
Controller:
function check($permissionName) {
if (! Auth::user()->hasPermissionTo($permissionName)) {
abort(403);
}
return response('', 204);
}
Vue: (if you wanna do this synchronously), this is a simple example (Vue global mixin), you can turn this into Vue directive or component.
Vue.mixin("can", (permissionName) => {
let hasAccess;
axios.get(`/permission/${permissionName}`)
.then(()=> {
hasAccess = true;
}
.catch(()=> {
hasAccess = false;
};
return hasAccess;
});
And then everytime you wanna check permission, you can just do
<el-input v-if="can('write-stuff')"> </el-input>
I'm literally working on this exact same thing. I'm thinking of adding a custom Vue directive that would check against the Laravel.permissions array.
It might even be as simple as
Vue.directive('can', function (el, binding) {
return Laravel.permissions.indexOf(binding) !== -1;
})
I haven't tested this code. Just brainstorming here.
<button v-can="editStuff">You can edit this thing</button>
I can hold permissions this way:
window.Laravel = <?php echo json_encode([
'csrfToken' => csrf_token(),
'userId' => Auth::user()->id,
'permissions' => Auth::user()->permissions()->pluck('name')->toArray()
]); ?>
Just stumbled upon this problem and I would like to share what I found and implemented.
Add an accessor on the User model the spatie/laravel-permission is using
public function getAllPermissionsAttribute() {
$permissions = [];
foreach (Permission::all() as $permission) {
if (Auth::user()->can($permission->name)) {
$permissions[] = $permission->name;
}
}
return $permissions;
}
On your global page or layout page pass the permission from the accessor to the javascript.
<script type="text/javascript">
#auth
window.Permissions = {!! json_encode(Auth::user()->allPermissions, true) !!};
#else
window.Permissions = [];
#endauth
</script>
Create a global directive on resources/js/app.js
Vue.directive('can', function (el, binding, vnode) {
if(Permissions.indexOf(binding.value) !== -1){
return vnode.elm.hidden = false;
}else{
return vnode.elm.hidden = true;
}
})
Here you are checking if the permission you supplied on the directive is on the permission array from laravel.
If found then it will hide the element else show, this function is like a v-if.
Use it like this on your component - "add_items" is your permission
<button type="button" v-can="'add_items'"></button>
This solution is from this but instead of a mixin, I use a directive.
Got the idea of directive from #Ismoil Shifoev comment above.
You can use this format in Vuejs for Laravel Permission:
<div v-if="can('edit post')">
<!-- Edit post form -->
</div>
<div v-if="is('super-admin')">
<!-- Show admin tools -->
</div>
add function to User Model to get all user permissions&roles like this:
class User extends Authenticatable
{
// ...
public function jsPermissions()
{
return json_encode([
'roles' => $this->getRoleNames(),
'permissions' => $this->getAllPermissions()->pluck('name'),
]);
}
}
pass this data to JavaScript in HTML header:
<script type="text/javascript">
window.Laravel = {
csrfToken: "{{ csrf_token() }}",
jsPermissions: {!! auth()->check()?auth()->user()->jsPermissions():null !!}
}
</script>
in app.js file add global Vuejs can function to check user permissions and is function to check user roles:
Vue.prototype.can = function(value){
return window.Laravel.jsPermissions.permissions.includes(value);
}
Vue.prototype.is = function(value){
return window.Laravel.jsPermissions.roles.includes(value);
}
https://github.com/ahmedsaoud31/laravel-permission-to-vuejs
I would go with Ralph solution. But I find myself better using. This function to fetch the Permissions.
public function getAllPermissionsAttribute() {
return Auth::user()->getAllPermissions()->pluck('name');
}
Just a bit cleaner, and since I tend to use Roles instead of particular permissions for each User, this solution should work as well.

Laravel 5.6 Function () does not exist in route/web.php

This is my code using to send an email
Route::post('/mail/send', [
'EmailController#send',
]);
in EmailController this is the send action
public function send(Request $request)
{
$data = $request->all();
$data['email'] = Input::get('email');
$data['name'] = Input::get('name');
$obj = new \stdClass();
$obj->attr = 'Hello';
Mail::to("dev#mail.com")->send(new WelcomeEmail($obj));
}
getting a error as Function () does not exist
In your route/web.php file
Change it to
Route::post('/mail/send', 'EmailController#send');
Refer to the documentation to see the possible options to define routes:
https://laravel.com/docs/5.6/routing
Route's action method can be defined using a array, but not simply wrap controller#action in an array, you should assign it to array's key 'uses'.
In your example, it should be like this:
Route::post('/mail/send', [
'uses' => 'EmailController#send',
//'middleware' => .... assign a middleware to this route, if needed
]);
the array form usually is used when we want to specify more specification about the route like use a specific middleware and pass middleware parameters.
if you just want to define route's processing method you can simply use controller#action as Route::post's second parameter:
Route::post('/mail/send','EmailController#send');
In your route ...
Route::post('/mail/send','EmailController#send')->name('send_email');
Inside of your HTML form add below code...
<form action="{{route('send_email')}}" method="post">
...
{{csrf_field()}}

Passing an array with redirect. Problems with »reload«.

Framework is Laravel. I am passing an array with the redirect method from a controller like this:
$serializeThrowsArray = serialize($throwsArray);
return redirect()->route('pages.result')
->with( ['serializeThrowsArray' => $serializeThrowsArray] );
to a named route:
Route::get('/result', ['as' => 'pages.result', function() {
$serializeThrowsArray = session()->get('serializeThrowsArray');
$throwsArray = unserialize($serializeThrowsArray);
return view('pages.result', ['throwsArray' =>$throwsArray]);
}]);
which loads the next page:
#section('content')
#foreach ($throwsArray as $throw)
{{$throw}},
#endforeach
#endsection
Everything work as it should, except when i hit F5(reload) and get the next error msg: "Invalid argument supplied for foreach()" and the next code is higlighted:
<?php $__currentLoopData = $throwsArray; $__env->addLoop($__currentLoopData);
foreach($__currentLoopData as $throw): $__env->incrementLoopIndices(); $loop
= $__env->getLastLoop(); ?>
I know is a problem with session-flash that has been cleared. Is there a work around or another way to pass an array with redirect?
Try
return->view('pages.result')

Access Cookies from Twig?

I have code like this in the base controller:
$this->eu_cookie_preference = $this->input->cookie('eu-cookie-preference');
and in each one of my controller functions I pass this variable to the twig like this:
$this->twig->display('account/my_details.twig', array(
'title' => 'Website | My Details',
'lang' => $this->lang,
'eu_cookie_preference' => $this->eu_cookie_preference,
));
And in the Base Twig I use this variable to do various things.
Is there a way to access the $this->eu_cookie_preference variable from Twig without having to explicitly pass it to each Twig in every controller function?
I have a similar problem with session vars as I have to pass them to each twig in order to access them.
Answer
You can use Twigs addGlobal function to do so. See manual
// Add static text
$twig->addGlobal('text', 'Hello World');
// Add array
$twig->addGlobal('arr', array(1, 2, 3));
// Add objects
$twig->addGlobal('obj', $obj);
You can use these globals just like normal vars:
This is a Text: "{{ text }}",
item in an array {{ arr[0] }},
obj value {{ obj.publicAttr }} or
obj function {{ obj.toHTML5('<img src="" />') }}
Additional information
This way you can implement lazy loading as well. If you load sessions data from your database, that will not be used in every template it can be useful to build a class like this:
class OnDemand {
private $cache;
private $function;
function __construct($function) {
$this->function = $function;
}
private function cache() {
if($this->cache == null) {
$function = $this->function;
$this->cache = $function();
}
}
function __toString() {
$this->cache();
return (string) $this->cache;
}
function __get($key) {
$this->cache();
return $this->cache[$key];
}
function __isset($key) {
$this->cache();
return isset($this->cache[$key]);
}
}
and pass values like this:
$twig->addGlobal('aDataArray', new OnDemand(function(){
// load database data
$data = DB::loadData(...);
return $data;
}));
The function will only be called, when you call the variable in twig.
{{ aDataArray.user.name }}

Resources