When installing jetstream with it's authentication/authorization system laravel developer receive scaffolding with lots of views and view-components.
Firstly those views are in vendor\laravel\jetstream\resources\views\components\ folder.
Then when developer publishes views according to jeststream installation: https://jetstream.laravel.com/1.x/installation.html#livewire-components
php artisan vendor:publish --tag=jetstream-views
Those views/components appears at resources\views\vendor\jetstream\components.
The links to those views on the blade templates are:
<x-jet-welcome />
or to unify
<x-jet-someJetElement />
This corresponds with laravel blade documentation: https://laravel.com/docs/8.x/packages#view-components
We need to register those aliases in SurviceProvider:
public function boot()
{
$this->loadViewComponentsAs('courier', [
Alert::class,
Button::class,
]);
}
And then use them like this:
<x-courier-alert />
<x-courier-button />
My question is if we can register those aliases without creating a package?
So what I did:
Added aliases in AppServiceProvider - default service provider for users.
<?php
namespace App\Providers;
use App\View\Components\Alert;
use App\View\Components\Button;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->loadViewComponentsAs('nopackage', [
Alert::class,
Button::class,
]);
}
}
Created two components app\View\Components:
Alert.php:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Alert extends Component
{
/**
* Create a new component instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* #return \Illuminate\View\View|string
*/
public function render()
{
return view('components.alert');
}
}
Button.php:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Button extends Component
{
/**
* Create a new component instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* #return \Illuminate\View\View|string
*/
public function render()
{
return view('components.button');
}
}
Created two view components resources\views\components:
alert.blade.php:
<div>
<h2>Hello from Alert!</h2>
</div>
button.blade.php:
<div>
<h2>Hello from button!</h2>
</div>
Created view resources\views\bladetest.blade.php:
<h1>
Hello from blade test!
</h1>
<x-nopackage-Alert/>
Created route:
Route::view('/bladetest', 'bladetest');
And it does not work: "Unable to locate a class or view for component [nopackage-Alert]."
What I am doing wrong?
I set up a class for deleting comments left by users under an item model in a web page:
<?php
namespace App\Policies;
use App\Comment;
use App\Model_comment;
use App\User;
use App\Post;
class CommentPolicy
{
/**
*
* Defines if User can delete a Comment
*
* #param User $user
* #param Comment $comment
* #param Post $post
* #return bool
*/
public function deleteComment(User $user, Comment $comment, Post $post)
{
return ($user->id === $comment->user_id || $user->id === $post->user_id);
}
/**
* #param User $user
* #param Model_comment $model_comment
* #return bool
*/
public function deleteModelComment(User $user, Model_comment $model_comment)
{
return ($user->id === $model_comment->user_id);
}
}
Now I am trying to use this to show a button for deleting the comment.
While the "deleteComment" for posts comments is working the "deleteModelComment" is not passing at all, not any errors given.
The class is registered correctly under Auth Service Provider.
I am invoking it in the blade view like this:
#can('deleteModelComment', $c)
<button id="deletecmnt_{{$c->id}}" class="fa fa-minus pull-right"
aria-hidden="true"
data-token="{{ csrf_token() }}"></button>
#endcan
$c is the Model_comment. Any clue?
I have created a frontend service provider for my app:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Session;
class FrontendServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
view()->share('message', !is_null(Session::get('message')) ? '<div class="alert alert-success"><strong>Success! </strong>'.Session::get('message').'</div>' : '');
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
I am setting a flash message in the controller like so:
public function resend_welcome($token)
{
$user_id = Crypt::decrypt($token);
$user = User::findOrFail($user_id);
if($user->meta->confirmed==1) {
abort(404);
}
Event::fire(new UserRegistered($user));
Session::flash('message', 'A new email has been sent.');
return redirect(route('register.success', $token));
}
Then in my view I am trying to output $message like so:
{{ $message }}
The problem is $message is always NULL. If I use Session::get('message') in my view instead then it works and the flash message is displayed. Why is Session::get('message') always NULL in the service provider even though I have set it in the controller?
In Laravel 5 accessing session information inside a service provider boot method is not possible as the session logic is only started as part of the web middleware group Illuminate\Session\Middleware\StartSession::class.
To share the view variable you can use Middleware for that. Something like:
class BroadcastFlashMessage
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
view()->share('message', !is_null(Session::get('message'))
? '<div class="alert alert-success"><strong>Success! </strong>'.Session::get('message').'</div>'
: '');
return $next($request);
}
}
An alternative is to use laracasts/flash package.
I was following the laracasts video for creating follow option but on clicking on the username it is showing the above error and I don't know where to define this variable. Followscontroller
<?php
namespace App\Http\Controllers;
use Redirect;
use App\User;
use Laracasts\Commander\CommanderTrait;
use App\FollowUserCommand;
use Sentinel;
use Illuminate\Support\Facades\Input;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class FollowsController extends Controller
{
use CommanderTrait;
/**
* Follow a User
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store()
{
$input = array_add(Input::all(), 'user_id', Sentinel::getuser()->id);
$this->execute(FollowUserCommand::class, $input);
return Redirect::back();
}
/**
* Unfollow a User
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
FollowUserCommand
<?php namespace App;
use App\User;
class FollowUserCommand {
public $user_id;
public $userIdToFollow;
function __construct($user_id, $userIdToFollow)
{
$this->user_id = $user_id;
$this->userIdToFollow = $userIdToFollow;
}
}
FollowUserCommandHandler
<?php namespace App;
use Laracasts\Commander\CommandHandler;
class FollowUserCommandHandler implements CommandHandler {
protected $userRepo;
function __construct(UserRepository $userRepo)
{
$this->userRepo = $userRepo;
}
public function handle($command)
{
$user = $this->userRepo->findById($command->user_id);
$this->userRepo->follow($command->userIdToFollow, $user);
return $user;
}
}
UserRepository
<?php namespace App;
use App\User;
class UserRepository {
public function save(User $user)
{
return $user->save();
}
public function getPaginated($howMany = 4)
{
return User::orderBy('first_name', 'asc')->paginate($howMany);
}
public function findByUsername($username)
{
return User::with(['feeds' => function($query)
{
$query->latest();
}
])->whereUsername($username)->first();
}
public function findById($id)
{
return User::findOrFail($id);
}
public function follow($userIdToFollow, User $user)
{
return $user->follows()->attach($userIdToFollow);
}
}
User.php
<?php namespace App;
use Cartalyst\Sentinel\Users\EloquentUser;
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends EloquentUser {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes to be fillable from the model.
*
* A dirty hack to allow fields to be fillable by calling empty fillable array
*
* #var array
*/
protected $fillable = [];
protected $guarded = ['id'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = ['password', 'remember_token'];
/**
* To allow soft deletes
*/
use SoftDeletes;
protected $dates = ['deleted_at'];
public function feeds()
{
return $this->hasMany('App\Feed');
}
public function comment()
{
return $this->hasMany('App\Comment');
}
// This function allows us to get a list of users following us
public function follows()
{
return $this->belongsToMany(self::class, 'follows', 'follower_id', 'followed_id')->withTimestamps();
}
// Get all users we are following
public function following()
{
return $this->belongsToMany('User', 'followers', 'user_id', 'follow_id')->withTimestamps();
}
// if current user follows another user
public function isFollowedBy(User $otherUser)
{
$idsWhoOtherUserFollows = $otherUser->follows()->lists('followed_id');
return in_array($this->id, $idsWhoOtherUserFollows) ;
}
}
form.blade.php
#if($user->isFollowedBy($currentUser))
<p>You are following {{ $user->username }}<p>
#else
{!! Form::open(['route' => 'follows_path']) !!}
{!! Form::hidden('userIdToFollow', $user->id) !!}
<button type="submit" class="btn btn-primary">Follow {{ $user->username }} </button>
{!! Form::close() !!}
#endif
Assuming the tutorial implements the Auth class, you can get the current user by changing #if($user->isFollowedBy($currentUser)) to #if($user->isFollowedBy(\Illuminate\Support\Facades\Auth::user())). It is otherwise very difficult to read through your code, but kudos to you for trying to be thorough.
You obviously don't want to use Auth::user() in this way. Trying using it as Auth::user() without the full namespace, but otherwise add the namespace as use Illuminate\Support\Facades\Auth; in the controller handling that view.
I have a settings table where I store things like website title, social network links and other things... I make then all acessible by seting a cache variable.
Now, my question is, how can I update this table? By example... If I have the following blade form:
{!! Form::model(config('settings'), ['class' => 's-form', 'route' => ['setting.update']]) !!}
{{ method_field('PUT') }}
<div class="s-form-item text">
<div class="item-title required">Nome do site</div>
{!! Form::text('title', null, ['placeholder' => 'Nome do site']) !!}
</div>
<div class="s-form-item text">
<div class="item-title required">Descrição do site</div>
{!! Form::text('desc', null, ['placeholder' => 'Descrição do site']) !!}
</div>
<div class="s-form-item s-btn-group s-btns-right">
Voltar
<input class="s-btn" type="submit" value="Atualizar">
</div>
{!! Form::close() !!}
In the PUT request how can I search in the table by the each name passed and update the table? Here are the another files:
Route
Route::put('/', ['as' => 'setting.update', 'uses' => 'Admin\AdminConfiguracoesController#update']);
Controller
class AdminConfiguracoesController extends AdminBaseController
{
private $repository;
public function __construct(SettingRepository $repository){
$this->repository = $repository;
}
public function geral()
{
return view('admin.pages.admin.configuracoes.geral.index');
}
public function social()
{
return view('admin.pages.admin.configuracoes.social.index');
}
public function analytics()
{
return view('admin.pages.admin.configuracoes.analytics.index');
}
public function update($id, Factory $cache, Setting $setting)
{
// Update?
$cache->forget('settings');
return redirect('admin');
}
}
Repository
class SettingRepository
{
private $model;
public function __construct(Setting $model)
{
$this->model = $model;
}
public function findByName($name){
return $this->model->where('name', $name);
}
}
Model
class Setting extends Model
{
protected $table = 'settings';
public $timestamps = false;
protected $fillable = ['value'];
}
ServiceProvider
class SettingsServiceProvider extends ServiceProvider
{
public function boot(Factory $cache, Setting $settings)
{
$settings = $cache->remember('settings', 60, function() use ($settings)
{
return $settings->lists('value', 'name')->all();
});
config()->set('settings', $settings);
}
public function register()
{
//
}
}
Migration
class CreateSettingsTable extends Migration
{
public function up()
{
Schema::create('settings', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 100)->unique();
$table->text('value');
});
}
public function down()
{
Schema::drop('settings');
}
}
Ok, step by step.
First, let's think about what we really want to achieve and look at the implementation in the second step.
Looking at your code, I assume that you want to create an undefined set of views that contain a form for updating certain settings. For the user, the settings seem to be structured in groups, e.g. "General", "Social", "Analytics", but you don't structure your settings in the database like that. Your settings is basically a simple key/value-store without any relation to some settings group.
When updating, you want a single update method that handles all settings, disregarding which form the update request is sent from.
I hope I'm correct with my assumptions, correct me if I'm not.
Okay cool, but, come on, how do I implement that?
As always, there are probably a thousand ways you can implement something like this. I've written a sample application in order to explain how I would implement it, and I think it's pretty Laravelish (what a word!).
1. How should my data be stored?
We already talked about it. We want a basic key/value-store that persists in the database. And because we work with Laravel, let's create a model and a migration for that:
php artisan make:model Setting --migration
This will create a model and the appropriate migration. Let's edit the migration to create our key/value columns:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSettingsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('settings', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->text('value');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('settings');
}
}
In the Setting model, we have to add the name column to the fillable array. I'll explain why we need to below. Basically, we want to use some nice Laravel APIs and therefore we have to make the name-attribute fillable.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model {
/**
* #var array
*/
protected $fillable = ['name'];
}
2. How do I want to access the settings data?
We discussed this in your last question, so I won't go into detail about this and I pretend that this code already exists. I'll use a repository in this example, so I will update the SettingsServiceProvider during development.
3. Creating the repositories
To make the dependencies more loosely coupled, I will create an Interface (Contract in the Laravel world) and bind it to a concrete implementation. I can then use the contract with dependency injection and Laravel will automatically resolve the concrete implementation with the Service Container. Maybe this is overkill for your app, but I love writing testable code, no matter how big my application will be.
app/Repositories/SettingRepositoryInterface.php:
<?php
namespace App\Repositories;
interface SettingRepositoryInterface {
/**
* Update a setting or a given set of settings.
*
* #param string|array $key
* #param string $value
*
* #return void
*/
public function update($key, $value);
/**
* List all available settings (name => value).
*
* #return array
*/
public function lists();
}
As you can see, we will use the repository for updating settings and listing our settings in a key/value-array.
The concrete implementation (for Eloquent in this example) looks like this:
app/Repositories/EloquentSettingRepository.php
<?php
namespace App\Repositories;
use App\Setting;
class EloquentSettingRepository implements SettingRepositoryInterface {
/**
* #var \App\Setting
*/
private $settings;
/**
* EloquentSettingRepository constructor.
*
* #param \App\Setting $settings
*/
public function __construct(Setting $settings)
{
$this->settings = $settings;
}
/**
* Update a setting or a given set of settings.
* If the first parameter is an array, the second parameter will be ignored
* and the method calls itself recursively over each array item.
*
* #param string|array $key
* #param string $value
*
* #return void
*/
public function update($key, $value = null)
{
if (is_array($key))
{
foreach ($key as $name => $value)
{
$this->update($name, $value);
}
return;
}
$setting = $this->settings->firstOrNew(['name' => $key]);
$setting->value = $value;
$setting->save();
}
/**
* List all available settings (name => value).
*
* #return array
*/
public function lists()
{
return $this->settings->lists('value', 'name')->all();
}
}
The DocBlocks should pretty much explain how the repository is implemented. In the update method, we make use of the firstOrNew method. Thats why we had to update the fillable-array in our model.
Now let's bind the interface to that implementation. In app/Providers/SettingsServiceProvider.php, add this to the register-method:
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(
\App\Repositories\SettingRepositoryInterface::class,
\App\Repositories\EloquentSettingRepository::class
);
}
We could have added this to the AppServiceProvider, but since we have a dedicated service provider for our settings we will use it for our binding.
Now that we have finished the repository, we can update the existing code in the boot-method of our SettingsServiceProvider so that it uses the repository instead of hardcoding App\Setting.
/**
* Bootstrap the application services.
*
* #param \Illuminate\Contracts\Cache\Factory $cache
* #param \App\Repositories\SettingRepositoryInterface $settings
*/
public function boot(Factory $cache, SettingRepositoryInterface $settings)
{
$settings = $cache->remember('settings', 60, function() use ($settings)
{
return $settings->lists();
});
config()->set('settings', $settings);
}
4. Routes and controller
In this simple example, the homepage will show a form to update some settings. Making a PUT/PATCH-request on the same route will trigger the update method:
<?php
get('/', ['as' => 'settings.index', 'uses' => 'Admin\SettingsController#index']);
put('/', ['as' => 'settings.update', 'uses' => 'Admin\SettingsController#update']);
The index-method of our controller will return a view that contains the form. I've commented the update method throughout to explain what each line does:
app/Http/Controllers/Admin/SettingsController.php:
<?php
namespace App\Http\Controllers\Admin;
use Illuminate\Contracts\Cache\Factory;
use Illuminate\Http\Request;
use App\Repositories\SettingRepositoryInterface;
class SettingsController extends AdminBaseController {
/**
* #var \App\Repositories\SettingRepositoryInterface
*/
private $settings;
/**
* SettingsController constructor.
*
* #param \App\Repositories\SettingRepositoryInterface $settings
*/
public function __construct(SettingRepositoryInterface $settings)
{
$this->settings = $settings;
}
/**
* Shows the setting edit form.
*
* #return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index()
{
return view('settings.index');
}
/**
* Update the settings passed in the request.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Contracts\Cache\Factory $cache
*
* #return \Illuminate\Http\RedirectResponse
*/
public function update(Request $request, Factory $cache)
{
// This will get all settings as a key/value array from the request.
$settings = $request->except('_method', '_token');
// Call the update method on the repository.
$this->settings->update($settings);
// Clear the cache.
$cache->forget('settings');
// Redirect to some page.
return redirect()->route('settings.index')
->with('updated', true);
}
}
Note the first statement in the update method. It will fetch all POST-data from the request, except the method and CSRF token. $settings is now an associative array with your settings sent by the form.
5. And last, the views
Sorry for the Bootstrap classes, but i wanted to style my example app real quick :-)
I guess that the HTML is pretty self-explanatory:
resources/views/settings/index.blade.php:
#extends('layout')
#section('content')
<h1>Settings example</h1>
#if(Session::has('updated'))
<div class="alert alert-success">
Your settings have been updated!
</div>
#endif
<form action="{!! route('settings.update') !!}" method="post">
{!! method_field('put') !!}
{!! csrf_field() !!}
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" id="title" name="title" placeholder="Title" value="{{ config('settings.title', 'Application Title') }}">
</div>
<div class="form-group">
<label for="Facebook">Facebook</label>
<input type="text" class="form-control" id="facebook" name="facebook" placeholder="Facebook URL" value="{{ config('settings.facebook', 'Facebook URL') }}">
</div>
<div class="form-group">
<label for="twitter">Twitter</label>
<input type="text" class="form-control" id="twitter" name="twitter" placeholder="Twitter URL" value="{{ config('settings.twitter', 'Twitter URL') }}">
</div>
<input type="submit" class="btn btn-primary" value="Update settings">
</form>
#stop
As you can see, when I try to get a value from the config, I also give it a default value, just in case it has not been set yet.
You can now create different forms for different setting groups. The form action should always be the settings.update route.
When I run the app, I can see the form with the default values:
When I type some values, hit the update button and Laravel redirects me to the form again, I can see a success message and my settings that now persist in the database.
You can inject the Request class. Lets update the title:
// Injecting Illuminate\Http\Request object
public function update(Request $request, $id, Factory $cache, Setting $setting)
{
$newTitle = $request->get('title');
$cache->forget('settings');
return redirect('admin');
}
To change the value in db, then could do:
$titleSetting = App\Setting::where('name', 'title')->first();
$titleSetting->value = $newTitle;
$titleSetting->save();
The whole code looks like:
public function update(Request $request, $id)
{
$newTitle = $request->get('title');
\Cache::forget('settings');
$titleSetting = App\Setting::where('name', 'title')->first();
$titleSetting->value = $newTitle;
$titleSetting->save();
return redirect('admin');
}