I have a test in a Laravel project where I do a getJson request and some answer should be returned. But the method in the controller doesn't get hit.
The test
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class NotificationsTest extends TestCase
{
use DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
$this->signIn();
}
public function test_a_user_can_fetch_their_unread_notifications()
{
create(DatabaseNotification::class);
$response = $this->getJson(url('/profiles') . '/' . auth()->user()->name . '/notifications')->json();
$this->assertCount(1, $response);
}
The line in webp.php that should process this request:
Route::get('/profiles/{user}/notifications', 'UserNotificationsController#index');
The UserNotificationsController:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\User;
class UserNotificationsController extends Controller
{
public function __construct()
{
$this->middelware('auth');
}
public function index() {
dd(" UsderNotificationsController-Index method hit");
return auth()->user()->unreadNotifications;
}
public function destroy(User $user, $notificationId)
{
dd(' Destroy method hit');
auth()->user()->notifications()->findOrFail($notificationId)->markAsRead();
}
}
If I run the test with phpunit, I would expect that the DD() in the index method should be executed. But it doesn't.
I tried all kinds of variations to generate the URI, but always get the same result. Can anyone tell me why I do not generate the correct URI?
Kind regards,
HUbert
//start by doing that : in your controller
Route::get('/profiles/notifications', 'UserNotificationsController#index');
public function test_a_user_can_fetch_their_unread_notifications()
{
$this->withoutHandlingException();
create(DatabaseNotification::class,['user_id'=>$this->signIn()->id]);
$this->signIn()//i think it should return authenticated user
$response = $this->get('/profiles/notifications')
->assertStatus(200);
// $this->assertCount(1, $response);
}
-//in your index function
public function index() {
dd(" UsderNotificationsController-Index method hit");
return response()->json(auth()->user()->unreadNotifications,200);
}
Related
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);
}
}
}
I want to pass $defaultFrom from NewsletterController.php:
<?php
namespace App\Http\Controllers;
use App\Mail\NewsletterMail;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
class NewsletterController extends Controller
{
public function send()
{
$defaultFrom = 'newsletter#stuttard.de';
DB::table('newsletter_mails')->insert(['from' => $defaultFrom]);
$emails = DB::select('select * from newsletters order by id desc');
foreach ($emails as $email) {
Mail::to($email)->send(new NewsletterMail());
}
}
}
to NewsletterMail.php:
<?php
namespace App\Nova;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
class NewsletterMail extends Resource
{
public function fields(Request $request)
{
return [
ID::make(__('ID'), 'id')->sortable(),
Text::make('From', 'from')->default($defaultFrom)->placeholder($defaultFrom),
];
}
}
I've tried to put public $defaultFrom; above the fields() function or call new NewsletterMail($defaultFrom) but this seems to be wrong syntax. Sorry, I'm a bit new to Laravel.
I assume that you have Newsletter model. Move $defaultFrom to model as public const DEFAULT_FROM = 'newsletter#stuttard.de';. After doing this, you can call it's value in both places using Newsletter::DEFAULT_FROM.
I added migrate in database, but why getting error I don't know, I am pretty new to Laravel, I don't know how can I fix this.
Error is:
Undefined variable: title
My code:
namespace App\Http\Controllers\Admin;
use App\Models\Abouts;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class AboutusController extends Controller
{
public function index(){
return view('admin.aboutus');
}
public function store(Request $request){
$aboutus = new Abouts();
$aboutus->$title = $request->input('title');
$aboutus->$subtitle = $request->input('subtitle');
$aboutus->$description = $request->input('description');
$aboutus->save();
return redirect('/abouts')->with('success','nice');
}
}
you can write this way also.
namespace App\Http\Controllers\Admin;
use App\Models\Abouts;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class AboutusController extends Controller
{
public function index(){
return view('admin.aboutus');
}
public function store(Request $request){
$input = $request->all();
Abouts::create($input); //here About us your model
return redirect('/abouts')->with('success','nice');
}
}
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?
when i use laravel middleware its routes is not work properly
<?php
namespace App\Http\Controllers;
use Auth;
use App\Article;
use App\Http\Requests;
use Illuminate\Http\Request;
use App\Http\Requests\ArticleRequest;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
//use Illuminate\Http\Request;
class ArticlesController extends Controller
{
public function __construct(){
$this->middleware('auth',['only'=>'create']);
}
//
public function index(){
//return \Auth::user();
$articles = Article::latest('published_at')->published()->get();
return view('articles.index',compact('articles'));
}
public function show($id){
$article = Article::findorFail($id);
//dd($article->published_at->addDays(8)->diffForHumans());
return view('articles.show',compact('article'));
}
public function create(){
if(Auth::guest()){
return redirect('articles');
}
return view('articles.create');
}
public function store(ArticleRequest $request){
/*
$input = Request::all();
$input['published_at'] = Carbon::now();
*/
$article = new Article($request->all());
Auth::user()->articles()->save($article);
//Article::create($request->all());
return redirect('articles');
}
public function edit($id){
$article = Article::findorFail($id);
return view('articles.edit', compact('article'));
}
public function update($id, ArticleRequest $request){
$article = Article::findorFail($id);
$article->update($request->all());
return redirect('articles');
}
}
when i go to http://localhost/lernlaravel/public/articles/create it works fine
but when i go to http://localhost/learnlaravel/public/articles it redirect to http://localhost/articles.
index() method is used for listing articles how i can fix it?
The redirect () accepts a URL path so if you want ensure your redirect will work on both testing and production environments, I would pass either action () or route () to all of your applications redirect calls. In your this case I would go with
return redirect(action ('ArticlesController#show', $articles->id));
This way Laravel will automatically generate the proper URL path to the controller you want to handle the request.
If you choose to go with route() you are required to have named the route in your routes file, but I find that with resourceful controllers it's less complicated to go with action.