Laravel Pass Authenticated to Every View - laravel

I need to pass a collection to every view; the collection contains the IDs of the items in the user's shopping cart. I've tried Service Providers and a BaseClass but neither worked as (apparently) Auth hasn't been registered at those points and only returns null.
What's the best way get records from an authenticated user and pass it to every view?
Edit: here's the relevant code
User.php
public static function getCart()
{
if (Auth::guest()) {
return [];
}
$collection = new \Illuminate\Database\Eloquent\Collection();
$collection = Auth::user()->cart()->pluck('post_id');
return $collection;
}
CartServiceProvider.php
namespace App\Providers;
use View;
use App\User;
use Illuminate\Support\ServiceProvider;
class CartServiceProvider extends ServiceProvider
{
public function boot()
{
View::share('cart', User::getCart());
}
public function register()
{
//
}
}
In any view...
<?php dd($cart); ?>
returns [] because Auth hasn't been registered yet, so the empty array is returned.

Found the answer on Laracasts and it seems to work quite well.
https://laracasts.com/discuss/channels/general-discussion/l5-service-provider-for-sharing-view-variables
From the OP #imJohnBon: "I managed to solve this issue by creating 2 files. First a ComposerServiceProvider which uses a wildcard to be applied to every view and not just particular views:"
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\View\Factory as ViewFactory;
class ComposerServiceProvider extends ServiceProvider {
public function boot(ViewFactory $view)
{
$view->composer('*', 'App\Http\ViewComposers\GlobalComposer');
}
public function register()
{
//
}
}
"And then the corresponding GlobalComposer where I share variables that should be available in all views:"
namespace App\Http\ViewComposers;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Auth;
class GlobalComposer {
public function compose(View $view)
{
$view->with('currentUser', Auth::user());
}
}

Related

Laravel route model binding without global scope

I have following route group in my laravel 8.0 app:
Route::prefix('offline_transaction')->name('offline_transaction.')->group(function () {
Route::post('/approve/{transaction:uuid}', [OfflineTransactionController::class, 'approve'])
->name('approve');
Route::post('/reject/{transaction:uuid}', [OfflineTransactionController::class, 'reject'])
->name('reject');
});
And Transaction model is:
class Transaction extends Model implements CreditBlocker
{
//....
protected static function boot()
{
parent::boot();
static::addGlobalScope(new AuthUserScope());
}
//....
}
And this is my AuthUserScope:
class AuthUserScope implements Scope
{
private string $fieldName;
public function __construct($fieldName = 'user_id')
{
$this->fieldName = $fieldName;
}
public function apply(Builder $builder, Model $model)
{
$user = Auth::user();
if ($user) {
$builder->where($this->fieldName, $user->id);
}
}
}
Now the problem is when an admin wants to approve or reject a transaction, 404 Not found error will throws. How can I pass this?
Customizing The Resolution Logic
If you wish to define your own model binding resolution logic, you may
use the Route::bind method. The closure you pass to the bind
method will receive the value of the URI segment and should return the
instance of the class that should be injected into the route. Again,
this customization should take place in the boot method of your
application's RouteServiceProvider:
Solution
What you can do is change the parameter name(s) in your routes/web.php file for the specific route(s).
Route::prefix('offline_transaction')->name('offline_transaction.')->group(function () {
Route::post('/approve/{any_transaction}', [OfflineTransactionController::class, 'approve'])
->name('approve');
Route::post('/reject/{any_transaction}', [OfflineTransactionController::class, 'reject'])
->name('reject');
Note the any_transaction. Change that to whatever naming convention you find most convenient.
Then, in your app/Providers/RouteServiceProvider.php file, change your boot(...) method to something like this:
use App\Models\Transaction;
use Illuminate\Support\Facades\Route;
// ...
public function boot()
{
// ...
Route::bind('any_transaction', function($uuid) {
return Transaction::withoutGlobalScopes()->where('uuid', $uuid)->firstOrFail();
});
// ...
}
// ...
Then in your controller app/Http/Controllers/OfflineTransactionController.php file, access the injected model:
use App\Models\Transaction;
// ...
public function approve(Transaction $any_transaction) {
// ...
}
// ...
Credits: Using Route Model Binding without Global Scope #thomaskim
Addendum
If you would like to remove a specific global scope from the route model bound query, you may use
withoutGlobalScope(AuthUserScope::class) in the boot(...) method of the app/Providers/RouteServiceProvider.php file.
Another approach is that I can use Route::currentRouteNamed in AuthUserScope class as following, which I prefer to use instead of Route::bind:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
class AuthUserScope implements Scope
{
private string $fieldName;
public function __construct($fieldName = 'user_id')
{
$this->fieldName = $fieldName;
}
public function apply(Builder $builder, Model $model)
{
$user = Auth::user();
if ($user && !Route::currentRouteNamed('admin.*')) {
$builder->where($this->fieldName, $user->id);
}
}
}

Laravel 5.8 add data to a layout variable via controller constructor

I am trying to add data to a layout variable via a controller constructor. The reason I want to do this is because I always need to add categories to the topmenu when this controller is called.
No success so far. I add data to a layout via a view composer like this.
namespace App\Http\ViewComposers;
use Illuminate\View\View;
use App\Menu;
class MenuComposer
{
public function compose(View $view)
{
if (in_array($view->getName(), ['layouts.master', 'layouts.master-post', 'layouts.error']))
{
$menu = Menu::menu('topmenu');
view()->with('topmenu', $menu);
// view()->share('topmenu', $menu); not working either
}
}
}
I want to extend the data in a Controller constructor.
namespace App\Http\Controllers\Post;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\View\View;
class PostController extends Controller {
public function __construct(View $view)
{
$view->offsetGet('topmenu');
// $view->gatherData() not working either
}
Whatever I try, Laravel throws an exception:
Target [Illuminate\Contracts\View\Engine] is not instantiable while building [App\Http\Controllers\Post\PostController, Illuminate\View\View].
What I did in the serviceprovider boot function:
view()->share('topmenu', [
'items' => $newItemsToAdd
]);
In the viewComposer I did:
$extraItems = view()->shared('topmenu');
if (!empty($extraItems)) {
$items = aray_merge($items, $extraItems);
}
}

Laravel: Class not found if it is called from a Trait

After creating several Apps with Laravel and using softDelete properties I realized that methods like destroy(), restore() and kill() are exactly the same among several controllers. Therefore I am trying to put themn in a trait and use it from diferent Controllers.
My code is as follows:
ProfilesController.php
<?php
namespace App\Http\Controllers;
use App\Profile;
class ProfilesController extends Controller
{
public function destroy(Profile $profile)
{
Profile::del($profile, 'profiles');
return redirect()->route('profiles.index');
}
public function trashed()
{
Profile::trash('Profile');
}
}
Profile.php (model)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Profile extends Model
{
protected $fillable = ['user_id', 'role_id', 'title', 'subtitle', 'slug', 'birthday', 'about'];
use SoftDeletes, Helpers, commonMethods;
public function getRouteKeyName()
{
return 'slug';
}
// ... more code here
}
trait file: commonMethods.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use App\Profile;
use Session;
trait commonMethods
{
public static function del($element, $page_name)
{
$element->delete();
Session::flash('success', $element . ' successfully deleted!');
}
public static function trash($model)
{
$total = $model::onlyTrashed()->get();
$total_tr = count($total);
$all_tr = $model::all();
return view('partials.templates.trashed', compact('total', 'total_tr', 'all_tr'));
}
// ...more code here
}
The problem:
I try to visit the view "Trashed" that will list all elements "softdeleted" but not "killed", the method.
I pass the $model variable with the method trash($model)
I get the following error:
Class App/Profile does not found. Try to call App/Profile
I have debugged and the $model variable contains exactly what I need, the string 'Profile' which is what I need to build the Query:
$total = Profile::onlyTrashed()->get();
This query works while in the ProfilesController, but does not work while in a trait, since the model class is not found.
Any idea how could I make it work?
I am using Laravel 6.
If you need to use a class as a string you will want to use its full name. 'App\Profile' instead of 'Profile'.
$model = 'Profile';
new $model; // will use `\Profile`
$model = 'App\Profile';
new $model; // will use '\App\Profile';
In your controller( ProfilesController ) write :
use App\Profile;
In your model write :
use App\commonMethods;

Laravel and Local Tunnel integration

My idea was to use Local Tunnel's subdomain feature to expose callback URI in a more convenient way. However, I believe that I could've achieved the same results in a simper way.
This is the solution with Laravel Valet:
In package.json I've added a script called shared
"scripts": {
...
"share": "lt --port 80 --subdomain blog --local-host blog.test"
}
In AppServiceProvider.php I've extended the UrlGenerator to avoid redirecting back to http://blog.test
<?php
namespace App\Providers;
use App\Services\LocalTunnelUrlGenerator;
use Blade;
use Illuminate\Http\Resources\Json\Resource;
use Illuminate\Routing\Router;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
(...)
}
public function register()
{
$this->enableLocalTunnel();
}
private function enableLocalTunnel()
{
if (!app()->environment('local') || !config('app.use_local_tunnel')) {
return;
}
$this->app->extend('url', function (UrlGenerator $defaultGenerator) {
/** #var Router $router */
$router = $this->app['router'];
$routes = $router->getRoutes();
return new LocalTunnelUrlGenerator($routes, $defaultGenerator->getRequest());
});
}
}
This is the the LocalTunnelUrlGenerator.php:
<?php
namespace App\Services;
use Illuminate\Http\Request;
use Illuminate\Routing\RouteCollection;
use Illuminate\Routing\UrlGenerator;
class LocalTunnelUrlGenerator extends UrlGenerator
{
public function __construct(RouteCollection $routes, Request $request)
{
parent::__construct($routes, $request);
}
public function formatRoot($scheme, $root = null)
{
return "https://blog.localtunnel.me";
}
}
Why all that? Because whenever the application call the redirect() method, we are sent back to http://blog.test.
Do I really need to extend the UrlGenerator to make it work?

How to always use withTrashed() for model Binding

In my app, I use soft delete on a lot of object, but I still want to access them in my app, just showing a special message that this item has been deleted and give the opportunity to restore it.
Currently I have to do this for all my route parametters in my RouteServiceProvider:
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
parent::boot();
Route::bind('user', function ($value) {
return User::withTrashed()->find($value);
});
Route::bind('post', function ($value) {
return Post::withTrashed()->find($value);
});
[...]
}
Is there a quicker and better way to add the trashed Object to the model binding ?
Jerodev's answer didn't work for me. The SoftDeletingScope continued to filter out the deleted items. So I just overrode that scope and the SoftDeletes trait:
SoftDeletingWithDeletesScope.php:
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class SoftDeletingWithDeletesScope extends SoftDeletingScope
{
public function apply(Builder $builder, Model $model)
{
}
}
SoftDeletesWithDeleted.php:
namespace App\Models\Traits;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Scopes\SoftDeletingWithDeletesScope;
trait SoftDeletesWithDeleted
{
use SoftDeletes;
public static function bootSoftDeletes()
{
static::addGlobalScope(new SoftDeletingWithDeletesScope);
}
}
This effectively just removes the filter while still allowing me to use all the rest of the SoftDeletingScope extensions.
Then in my model I replaced the SoftDeletes trait with my new SoftDeletesWithDeleted trait:
use App\Models\Traits\SoftDeletesWithDeleted;
class MyModel extends Model
{
use SoftDeletesWithDeleted;
For Laravel 5.6 to 7
You can follow this doc https://laravel.com/docs/5.6/scout#soft-deleting. And set the soft_delete option of the config/scout.php configuration file to true.
'soft_delete' => true,
For Laravel 8+
You can follow this doc https://laravel.com/docs/8.x/routing#implicit-soft-deleted-models. And append ->withTrashed() to the route that should accept trashed models:
Ex:
Route::get('/users/{user}', function (User $user) {
return $user->email;
})->withTrashed();
You can add a Global Scope to the models that have to be visible even when trashed.
For example:
class WithTrashedScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->withTrashed();
}
}
class User extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new WithTrashedScope);
}
}
Update:
If you don't want to show the deleted objects you can still manually add ->whereNull('deleted_at') to your query.

Resources