PHP/Laravel 9 - How can i solve Call to undefined method - php

I have a problem with my laravel 9:
Call to undefined method App\Models\Country::id()
I use the Laravel framework version 9.x for programming.
This is my model code:
class Country extends Model
{
use HasApiTokens, HasFactory, Notifiable, HasRoles;
protected $table = 'countries';
protected $fillable = [
'name',
'code'
];
}
This is my controller code:
<?php
namespace App\Http\Controllers;
use App\Models\Country;
use Illuminate\Http\Request;
use App\Services\LogWriter;
use Spatie\Permission\Models\Role;
class CountryController extends Controller
{
...
/**
* Show the form for editing the specified resource.
*
* #param \App\Models\Country $country
* #return \Illuminate\Http\Response
*/
public function edit(Country $country)
{
return view('countries.edit', compact('country'));
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param \App\Models\Country $country
* #return \Illuminate\Http\Response
*/
public function update(Request $request, Country $country)
{
$request->validate([
'name' => 'required|string|max:100',
'code' => 'required|string|max:2|unique:countries',
]);
$country->update($request->all());
message_set("Successful! Country information has been changed.", 'success', 5);
return redirect()->route('countries.index');
}
...
I dont know why I get this error and would be glad if I could get any help.

Lesson learned today, always check your error messages for the full information; assuming the problem was in the Controller can be right in a lot of cases, but Laravel's errors can also be triggered in a .blade.php view.
The problem was in the view edit.blade.php
"Check if you're calling ->id() in there"

Related

Laravel 9 Accessor not Returning Value

I have a Reminder model that has a sound_path column. I created an accessor for that column in the model but it's returning null and I doubled checked that the database has a value in that column. What am I doing wrong?
Note: Of course I can call $this->sound_path directly in soundPathUrl mutator without creating the accessor from the first place but I'm interested to know why if I called the accessor is not returning any value.
Reminder model
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Spatie\Translatable\HasTranslations;
class Reminder extends BaseModel
{
use HasFactory, SoftDeletes, HasTranslations;
public $translatable = [
'title__ml',
];
protected $casts = [
'available_days_for_reminder' => 'json',
'is_multiple_days_allowed' => 'boolean'
];
protected $appends = ['sound_path_url'];
/**
* Get the sound path
*
* #param string $value
* #return \Illuminate\Database\Eloquent\Casts\Attribute
*/
protected function soundPath(): Attribute
{
return Attribute::make(
get: fn ($value) => $value
);
}
/**
* Get sound path download URL
*
* #return \Illuminate\Database\Eloquent\Casts\Attribute
*/
protected function soundPathUrl(): Attribute
{
return new Attribute(
get: fn () => asset('storage/' . $this->soundPath),
);
}
}
Reminder controller
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\Reminder\ReminderCollection;
use App\Http\Resources\Reminder\ReminderResource;
use App\Models\Reminder;
use Exception;
use Illuminate\Http\Request;
class ReminderController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$reminders = Reminder::paginate(5);
return ReminderCollection::collection($reminders);
}
}
ReminderCollection API resource
namespace App\Http\Resources\Reminder;
use Illuminate\Http\Resources\Json\JsonResource;
class ReminderCollection extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title__ml,
'sound' => $this->sound_path_url
];
}
}
Screenshot of Response
At the arrow there should be the value of sound_path.
Your issue is that you are calling $this->soundPath instead of $this->sound_path in soundPathUrl method...
So, you should have this:
/**
* Get sound path download URL
*
* #return \Illuminate\Database\Eloquent\Casts\Attribute
*/
protected function soundPathUrl(): Attribute
{
return new Attribute(
get: fn () => asset('storage/' . $this->sound_path),
);
}
Check the documentation and you will see you still have to call your properties using snake_case.
It seems soundPath accessor does nothing, it returns the same value which seems empty. Where does soundPath value come from? First, make sure, soundPath itself has any value within that function.
Call soundPath as a function
protected function soundPathUrl(): Attribute
{
return new Attribute(
get: fn () => asset('storage/' . $this->soundPath()),
);
}
}

Laravel delete linked model's data by user_id in booted function of User model

I've read part of the Laravel docs for events and closures for models, I've got various models in my project whereby a user may have data linked to them in another table by a user_id column, the user_id column that I have in my various tables is structured as an unsigned integer (I'm aware I could've gone with a foreignId column by kind of a legacy approach here)
It looks like:
$table->integer('user_id')->unsigned()->nullable()->index();
I'd like to delete user data by their ID within these other tables and rather than creating a delete function and grabbing each model I want to delete data against, I've utilised the closure booted function and what I believe to be an event to listen and delete related model data, but I experience an error when trying to delete my user account, other data in other tables isn't deleted, the error I get is:
Call to undefined method App\Models\User::releationship()
My user model looks like:
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject, MustVerifyEmail
{
use Notifiable, SoftDeletes;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'first_name', 'last_name', 'email', 'password'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token'
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime'
];
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* #return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* #return array
*/
public function getJWTCustomClaims()
{
return [];
}
/**
* Route notifications for the Slack channel.
*
* #param \Illuminate\Notifications\Notification $notification
* #return string
*/
public function routeNotificationForSlack($notification)
{
$url = $this->slack_webhook;
$webhook = (isset($url) && !empty($url)) ? $url : null;
return $webhook;
}
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::deleted(function ($model) {
$model->relationship()->delete();
});
}
}
And an example (of many) model I have, UptimeChecks looks like:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class UptimeChecks extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'uptime_checks';
/**
* Join user table
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
All is then kicked off by a deleteAccount function in my API, which is deleting the user's account, but isn't deleting data in other tables. What am I missing and how could I do a check to make sure other data is deleted before confirming to the user that their account and linked data is gone?
/**
* Delete account
*
* #return Response
*/
public function deleteAccount(Request $request)
{
// attempt to delete account
try {
$user = User::findOrFail(Auth::id());
$user->delete();
// everything went okay!
return response()->json(['success' => true, 'message' => 'Your account has been deleted'], 200);
} catch (Exception $e) {
// catch the error
return response()->json(['success' => false, 'message' => 'We was unable to delete your account at this time'], 422);
}
}
In Laravel, when doing $model->relationship()->delete(); you will need to have the relationship defined and relationship() seems like it is copy pasted code snippet. Simply add the relationship to your User model.
class User extends Authenticatable implements JWTSubject, MustVerifyEmail
{
...
public function uptimeChecks() {
return $this->hasMany(UptimeChecks::class);
}
}
Now you can access and delete the relationship in your boot method.
$model->uptimeChecks()->delete();
You need to create a function in User.php
public function uptimeCheck()
{
return $this->hasOne('App\UptimeChecks');
}
and change the boot function
$model->uptimeCheck()->delete();
This way you need to do for all related relations.
This probably should be: $model->user()->delete() instead. There's nothing else.
If this shouldn't be the intention, reconsider the direction of the relationship.

Laravel 5.8 Custom validation error 'class does not exist'

I'm am new to laravel and i am trying to get my custom validation rules to work on my controller.
It's showing that the class does not exist.
ReflectionException thrown with message "Class App\Http\Controllers\StoreBooksRequest does not exist"
I made the request file using the artisan command.
lando artisan make:request StoreBooksRequest
this is my request file :
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreBooksRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
'title' => 'required|unique:books|max:150',
'description' => 'required',
'isbn' => 'required|max:20'
];
}
}
and this is the controller where i am trying to get the custom request rules to work :
namespace App\Http\Controllers;
use App\Book;
use Illuminate\Http\Request;
class BooksController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
//
$books = Book::all();
return view('books.index', compact('books'));
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
return view('books.create');
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(StoreBooksRequest $request)
{
$book = new Book();
$book->title = $request->title;
$book->description = $request->description;
$book->isbn = $request->isbn;
$book->save();
}
I think the problem is with the error saying that the request file is in the Controllers folder and not in the standard Requests folder.
You have not included the namespace of your custom request's class. Add use App\Http\Requests\StoreBooksRequest; after use Illuminate\Http\Request;
You seem to be using wrong namespace for your
Class App\Http\Controllers\StoreBooksRequest
Your namespace is set to namespace App\Http\Requests; while you are calling it from controller, If you move your Class to App\Http\Requests.
Also, don't forget to import the class in your controller
use StoreBooksRequest
When you execute the php artisan make:request Myrequestname, Laravel create the file inside the App\Http\Request subdirectory, so you need to be careful to use the right namespace, another thing you always had to be carefull is about the name you use, is not the same Mycontroller than mycontroller and is worst if your server is a Linux server, because the file system make differentiation beewteen Caps.

Laravel 5 and Cashier on Company table

I am new to laravel and have been working with cashier for a web app I am developing. In my app a user creates their account and company and they are allowed to use the app. Because a company can have many users, I need cashier to check if the company has a subscription or not.
In the cashier docs using Stripe I have set it up where no credit card is required up front and they can use the system for 14 days until being prompted for a credit card.
So far I have successfully created the cashier columns on my companies table and added the subsctiption table according to the docs.
add_cashier_table_fields.php migration file:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddCashierTableFields extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
//
Schema::table('companies', function ($table) {
$table->string('stripe_id')->nullable();
$table->string('card_brand')->nullable();
$table->string('card_last_four')->nullable();
$table->timestamp('trial_ends_at')->nullable();
});
Schema::create('subscriptions', function ($table) {
$table->increments('id');
$table->integer('company_id');
$table->string('name');
$table->string('stripe_id');
$table->string('stripe_plan');
$table->integer('quantity');
$table->timestamp('trial_ends_at')->nullable();
$table->timestamp('ends_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
//
}
}
Then in my company model I added the Billable trait as suggested.
Company.php - model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Laravel\Cashier\Billable;
class Company extends Model
{
use Billable;
protected $dates = [
'trial_ends_at',
'subscription_ends_at'
];
protected $fillable = [
'company_name',
'trial_ends_at',
'subscription_ends_at'
];
protected $cardUpFront = false;
public function users()
{
return $this->hasMany(\App\User::class);
}
}
Now in my RegisterController.php file I have it where when a company is created it where it will Carbon the date 14 days from that day and add to 'trial_ends_at' column
Auth/RegisterController.php
<?php
namespace App\Http\Controllers\Auth;
use App\User;
use App\Company;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\RegistersUsers;
use Carbon\Carbon;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after login / registration.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'company_name' => 'required|unique:companies,company_name',
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return User
*/
protected function create(array $data)
{
$company = \App\Company::create([
'company_name'=> $data['company_name'],
'trial_ends_at' => Carbon::now()->addDays(14), //Collect CC# 14 days from now
]);
$user = $company->users()->create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
$user->attachRole(1); //Admin role
return $user;
}
}
I am trying to check if the current subscription is within its trial period or not using
if ($company->onTrial()) {}
I figure since I need to restrict access to the whole system (besides the registration pages) I should use a middleware to check for subscription status. So I created Subscription.php middleware with the following:
<?php
namespace App\Http\Middleware;
use Closure;
use App\User;
use App\Company;
use Illuminate\Support\Facades\Auth;
class Subscription
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::check()){
//dd($request->user);
; $companyID = Auth::user()->company_id;
$company = Company::find($companyID);
dd($company->onTrial());
if($company->onTrial()){
return redirect('order');
}
}
return $next($request);
}
}
Question: What is the best way to attach cashier to a company (instead of per user) and restrict access to the system if the subscription is not active?
When I var_dump($company->onTrial()) that it always prints false? I made sure the date was from earlier this year so I should be past the trial time but no matter if I am in the trial timeframe or not it always prints false. Is this the best approach for what I am trying to do? Apologies for all the code, I wanted to give everyone the whole picture because there is little information about this online.
The only thing I can see different from other posts about this topic is my Company model extends Model and not Authenticatable. I verified Subscription was added to my kernel.php file and the middleware is register in my routes file.
Turns out this is working. When I change my dates manually in the database to be outside of my trial it returns false, likewise if I am in my trial period it returns true. In my case I needed to check onTrial() and also if the current url was localhost:8000/order - if not then the app redirects them to that order page until they enter their card info. Im posting my final middleware here in case anyone in the future has a similar situation and needs the functioning code. (Still don't know if this is the best approach but it works)
<?php
namespace App\Http\Middleware;
use Closure;
use App\User;
use App\Company;
use Illuminate\Support\Facades\Auth;
class Subscription
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::check()){
//dd($request->user);
$companyID = Auth::user()->company_id;
$company = Company::find($companyID);
//dd($company->onTrial());
if(!$company->onTrial() && $request->path() != 'order'){ //If trial has expired redirect to order page
return redirect('order');
}
}
return $next($request);
}
}

Dispatching an event in Laravel 5 on model created

I wan to create a Laravel signup system where I would send an email to verify the email once a user has signed up. I tried adding an event dispatched to the created method but I got an error
Fatal error: Non-static method Illuminate\Contracts\Events\Dispatcher::fire() cannot be called statically
Here is what I came up with.
<?php
namespace App;
use Laravel\Cashier\Billable;
use Laravel\Spark\Teams\CanJoinTeams;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as BaseUser;
use Laravel\Spark\Auth\TwoFactor\Authenticatable as TwoFactorAuthenticatable;
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
use Laravel\Spark\Contracts\Auth\TwoFactor\Authenticatable as TwoFactorAuthenticatableContract;
class User extends BaseUser implements TwoFactorAuthenticatableContract
{
use Billable, TwoFactorAuthenticatable,CanJoinTeams;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'email',
'name',
'password',
];
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = [
];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = [
'card_brand',
'card_last_four',
'extra_billing_info',
'password',
'remember_token',
'stripe_id',
'stripe_subscription'
];
/**
* Boot the model.
*
* #return void
*/
public static function boot()
{
parent::boot();
static::creating(function ($user) {
$user->token = str_random(30);
});
static::created(function ( $user) {
EventDispatcher::fire('UserCreated');
});
}
/**
* Confirm the user.
*
* #return void
*/
public function confirmEmail()
{
$this->verified = true;
$this->token = null;
$this->save();
}
}
I also tried changing the code to
use Billable, TwoFactorAuthenticatable,CanJoinTeams, EventDispatcher;
and replacing the boot part with
static::created(function ( $user, EventDispatcher $event) {
$event->fire('UserCreated');
});
But it gave me another error
App\User cannot use Illuminate\Contracts\Events\Dispatcher - it is not a trait
How do I fire the event once the model has been created ?
In Laravel >5.4 and higher, you can associate eloquent event in your Model class to fire a custom event - e.g. 'created' => ProjectCreated::class. So basically, on Eloquent model created fire ProjectCreated Event.
class Project extends Model
{
/**
* #var array
*/
protected $fillable = ['title', 'description', 'client_id'];
/**
*
* Eloquent model events
*
* #var array
*/
protected $dispatchesEvents = [
'created' => ProjectCreated::class,
];
}
Model's lifecycle events are fired by Eloquent already so no need to fire them yourself. Event name is created with the following code:
$event = "eloquent.{$event}: ".get_class($model);
Therefore, if you want to listen on the created event, you need to listen on "eloquent.created: App\User". The event handler will get the related User model as one of handle() parameters.
If you hovewer want to dispatch an event of your own, you can do that using Event facade:
Event::fire('UserCreated', $user);
You can read more about events in Laravel here: https://laravel.com/docs/5.1/events

Categories