I'm trying to return a relationship using Laravels API resource classes. Each order has many order totals.
My orders model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Orders extends Model
{
protected $table = 'zen_orders';
public function ordersTotals() {
return $this->hasMany(OrdersTotalsModel::class, 'orders_id');
}
}
My orders resource
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class OrdersResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->orders_id,
'customername' => $this->customers_name,
'paymentmethod' => $this->payment_method,
'datePurchased' => $this->date_purchased,
'status' => $this->orders_status,
'currency' => $this->currency,
'orderTotal' => $this->order_total,
'ordersTotals' => new OrdersTotalsResource($this->id),
];
}
}
My orders total model
namespace App;
use Illuminate\Database\Eloquent\Model;
class OrdersTotalsModel extends Model
{
protected $table = 'zen_orders_total';
protected $primaryKey = 'orders_total_id';
public function ordersTotals() {
return $this->belongsTo(Orders::class, 'orders_id');
}
}
My orders total resource
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class OrdersTotalsResource extends ResourceCollection
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'type' => $this->title,
'amount' => $this->value,
];
}
}
I'm currently getting this error with the code provided:
Symfony\Component\Debug\Exception\FatalThrowableError: Call to a member function toBase() on null in file
Previous to this error I would just get this:
{
"id": 389331,
"customername": "John Smith",
"paymentmethod": "Credit/Debit Card",
"datePurchased": "2017-01-01 00:11:28",
"status": 3,
"currency": "GBP",
"orderTotal": "36.99",
"ordersTotals": []
},
I've followed the documentation as best I can and tried all the possible methods demonstrated there, I believe the orders total model isn't being called as any changes I make to the model and resource don't make a difference.
My approach was wrong, for future visitors I was trying to retrieve records from a relationship using a resource.
On the Orders Resource I added:
'ordersTotals' => OrdersTotalsResource::collection(
OrdersTotalsModel::where('orders_id', '=' , $this->orders_id)
->orderBy('sort_order')
->get()
),
I was querying the model wrong, and had my primary keys mixed up.
I used this debug method to help diagnose where I was going wrong.
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
if (env('APP_DEBUG') == true) {
Event::listen('Illuminate\Database\Events\QueryExecuted', function ($query) {
Log::debug($query->sql . ' - ' . serialize($query->bindings));
});
}
}
}
This would then log the query into the /storage/logs/laravel.log file
Related
I am having an issue following a tutorial on YouTube about relationships.
I have replicated this code from the tutorial and I keep getting errors.
I've tried changing the controller code from auth() to app etc.
Also, I've tried re-running migrations:fresh etc and nothing.
User Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Notifiable, Billable;
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* Get the Instance associated with the user.
*
* #return HasMany
*/
public function instance()
{
return $this->hasMany(Instance::class);
}
}
Instance Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Instance extends Model
{
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'name'
];
public function user()
{
return $this->belongsTo(User::class);
}
}
Controller
<?php
namespace App\Http\Controllers;
class SyncController extends Controller
{
public function successful()
{
return auth()->user()->instance()->create(['name' => 'test']);
}
}
Error
Call to a member function instance() on null {"exception":"[object] (Error(code: 0): Call to a member function instance() on null at /home/#/cc.#.io/app/Http/Controllers/SyncController.php:14)
[stacktrace]
Edit:
Route::middleware(['auth'])->group(function() {
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
Route::get('/subscribe', SyncController::class);
});
Check if your route is guarded by auth middleware. If not you can add that in order to fix. You might use Route group like following -
Route::group(['middleware' => ['auth']], function () {
Route::resource('your_url', 'YourController');
// Or whatever route you want to add...
});
This is because the auth()->user is getting null and it will be necessary to check if the value was actually received after the call was made.
I am trying to pass a model to job class and when the model is sent to job class, it's incomplete there. What I mean is that it shows only two attributes there instead of 10. $queue_match is complete model (all attributes included, everything is fine) but when it is sent to Job class, then I only see two attributes.
PS: I have included my model at the top of Job class,
$z = dispatch(new updateMatchStatus($queue_match))->onConnection('database')->delay(now()->addMinutes('1'));
namespace App\Jobs;
use App\Http\Controllers\AdminController;
use App\Models\Match;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class updateMatchStatus implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $queue_match;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct(Match $queue_match)
{
//
$this->queue_match = $queue_match;
// dd($queue_match);
}
/**
* Execute the job.
*
* #return void
*/
public function handle(Match $queue_match)
{
$now = Carbon::now('PKT');
$now = Carbon::parse($now, 'PKT')->startOfMinute();
// registration close hack, will set Match status to F 30 mins before due time.
$now = $now->addMinutes(30);
$due_time = Carbon::parse($queue_match->due_time, 'PKT');
if ($now->greaterThan($due_time)) {
$queue_match->status = 'F';
// dd($queue_match);
$queue_match->save();
}
}
}
just two attributes in Screenshot
As requested, Here is Match Class
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Match extends Model
{
//
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'platform', 'category', 'map', 'fee', 'is_elite', 'due_time', 'status'
];
public function getPlatformAttribute($value) {
if ($value == 'M') {
return $value ."obile";
}
else {
return $value ."mulator";
}
}
// default attributes
protected $attributes= [
'status' => 'A',
'is_elite' => false
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
// hidden fields for return response
protected $hidden = [
'created_at', 'updated_at',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
];
public function registrations() {
return $this->hasMany('App\Models\Registration', 'match_id', 'id');
}
public function results() {
return $this->hasMany('App\Models\Result', 'match_id', 'id');
}
}
AND, this is how I get the $queue_match which is passed to job.
$queue_match = Match::create([
'category' => $request['category'],
'platform' => $request['platform'],
'map' => $request['map'],
'fee' => $request['fee'],
'due_time' => $request['due_time'],
]);
I have a Product model with a text input field for the product number. In my Laravel application I validate this field to be unique to that specific user. So two users can have a same product number, but one user cannot have duplicate. So far the validation rules work when adding new products:
'product_no' => 'nullable|unique:products,product_no,NULL,id,user_id,' . auth()->user()->id
However, when editing the same product, the validation fails. Probably because it already exists. I am not sure how to exclude the existing ID in the validation. Any ideas?
Example as requested
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class Request1 extends FormRequest
{
private $rules;
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
public function __construct()
{
parent::__construct();
$this->rules = [
'password' => [
'nullable',
]
];
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return $this->rules;
}
}
And the one with unique looks like this then
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class Request2 extends Request1
{
public function __construct()
{
parent::__construct();
$this->rules[] = 'unique:products,product_no,NULL,id,user_id,' . auth()->user()->id';
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return $this->rules;
}
}
How can I create my own core controller in codeigniter 4 like codeigniter 3?
When you say "core" controller I understand you to mean a "base" controller which in CI v3 is often named MY_Controller. If that is what you are asking for it's actually much easier in v4 because of namespaces and the autoloader. Also, there is no need for the trickery of using a prefix like MY_.
Here's just how easy it is. The "base" controller...
File: /application/Controllers/Base.php
<?php namespace App\Controllers;
class Base extends \CodeIgniter\Controller
{
//your code here
}
Then extend the above to create any other controller
File: /application/Controllers/Home.php
<?php namespace App\Controllers;
class Home extends \App\Controllers\Base
{
// Your code here
}
The Home controller will inherit all the properties and methods you define in Base.
You do not need to create another core controller, there is already a core controller provided, which is BaseController.
You need to just extend it to your choice
Try this
<?php namespace App\Core;
use CodeIgniter\Controller;
class Home extends Controller
{
}
see my codes
<?php
namespace Modules\Shared\Controllers;
/**
* Class BaseController
*
* BaseController provides a convenient place for loading components
* and performing functions that are needed by all your controllers.
* Extend this class in any new controllers:
* class Home extends BaseController
*
* For security be sure to declare any new methods as protected or private.
*
* #package CodeIgniter
*/
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;
use Modules\Auth\Config\Services;
use Myth\Auth\AuthTrait;
use Psr\Log\LoggerInterface;
use Modules\Shared\Interfaces\UrlQueryParamInterface;
use Modules\Shared\Libraries\UrlQueryParam;
class ApiController extends ResourceController
{
use AuthTrait;
protected $format = "";
public object $userObject;
public UrlQueryParamInterface $urlQueryParam;
/**
* An array of helpers to be loaded automatically upon
* class instantiation. These helpers will be available
* to all other controllers that extend BaseController.
*
* #var array
*/
protected $helpers = [
'cookie',
'url',
'from',
'filesystem',
'text',
'shared'
];
/**
* Constructor.
*
* #param RequestInterface $request
* #param ResponseInterface $response
* #param LoggerInterface $logger
*/
/**
* #var string
* Holds the session instance
*/
protected $session;
public function __construct()
{
$this->userObject = (object)[];
}
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
{
// Do Not Edit This Line
parent::initController($request, $response, $logger);
$this->urlQueryParam = new UrlQueryParam($request);
$requestWithUser = Services::requestWithUser();
$this->userObject = $requestWithUser->getUser();
}
}
<?php
namespace Modules\Common\Controllers;
use Modules\Common\Config\Services;
use Modules\Common\Entities\AdvertisementEntity;
use CodeIgniter\HTTP\ResponseInterface;
use Modules\Shared\Controllers\ApiController;
class Advertisement extends ApiController
{
/**
* index function
* #method : GET
*/
public function index()
{
$advertisementEntity = new AdvertisementEntity();
$this->urlQueryParam->dataMap($advertisementEntity->getDataMap());
$advertisementService = Services::advertisementService();
$findAllData = $advertisementService->index($this->urlQueryParam);
return $this->respond([
'data' => $findAllData['data'],
'pager' => $findAllData['pager']
], ResponseInterface::HTTP_OK, lang('Shared.api.receive'));
}
/**
* show function
* #method : GET with params ID
*/
public function show($id = null)
{
$advertisementService = Services::advertisementService();
$findOneData = $advertisementService->show($id);
return $this->respond([
'data' => $findOneData['data'],
'pager' => $findOneData['pager']
], ResponseInterface::HTTP_OK, lang('Shared.api.receive'));
}
/**
* create function
* #method : POST
*/
public function create()
{
$rules = [
'name' => 'required|min_length[3]|max_length[255]',
'link' => 'required',
];
if (!$this->validate($rules)) {
return $this->respond([
'error' => $this->validator->getErrors(),
], ResponseInterface::HTTP_NOT_ACCEPTABLE, lang('Shared.api.validation'));
};
$advertisementEntity = new AdvertisementEntity((array)$this->request->getVar());
$advertisementEntity->enableStatus()->createdAt();
$advertisementService = Services::advertisementService();
$advertisementService->create($advertisementEntity);
return $this->respond([
'insertId' => $advertisementService->getInsertID()
], ResponseInterface::HTTP_CREATED, lang('Shared.api.save'));
}
/**
* update function
* #method : PUT or PATCH
*/
public function update($id = null)
{
if ($this->request) {
//get request from Vue Js
$json = $this->request->getJSON();
if (!isset($id)) {
$id = $json->id;
}
$rules = [
'name' => 'required|min_length[3]|max_length[255]',
'link' => 'required',
];
if (!$this->validate($rules)) {
return $this->respond([
'error' => $this->validator->getErrors(),
], ResponseInterface::HTTP_NOT_ACCEPTABLE, lang('Shared.api.validation'));
}
$advertisementEntity = new AdvertisementEntity((array)$this->request->getVar());
$advertisementEntity->updatedAt();
$advertisementService = Services::advertisementService();
$advertisementService->update($id, $advertisementEntity);
}
return $this->respond([
], ResponseInterface::HTTP_OK, lang('Shared.api.update'));
}
/**
* edit function
* #method : DELETE with params ID
*/
public function delete($id = null)
{
$advertisementService = Services::advertisementService();
$advertisementService->delete($id);
return $this->respond([
], ResponseInterface::HTTP_OK, lang('Shared.api.remove'));
}
}
IMHO, the current Database channel for saving notifications in Laravel is really bad design:
You can't use foreign key cascades on items for cleaning up notifications of a deleted item for example
Searching custom attributes in the data column (casted to Array) is not optimal
How would you go about extending the DatabaseNotification Model in vendor package?
I would like to add columns event_id, question_id, user_id (the user that created the notification) etc... to the default laravel notifications table
How do you override the send function to include more columns?
In:
vendor/laravel/framework/src/Illuminate/Notifications/Channels/DatabaseChannel.php
The code:
class DatabaseChannel
{
/**
* Send the given notification.
*
* #param mixed $notifiable
* #param \Illuminate\Notifications\Notification $notification
* #return \Illuminate\Database\Eloquent\Model
*/
public function send($notifiable, Notification $notification)
{
return $notifiable->routeNotificationFor('database')->create([
'id' => $notification->id,
'type' => get_class($notification),
\\I want to add these
'user_id' => \Auth::user()->id,
'event_id' => $notification->type =='event' ? $notification->id : null,
'question_id' => $notification->type =='question' ? $notification->id : null,
\\End adding new columns
'data' => $this->getData($notifiable, $notification),
'read_at' => null,
]);
}
}
To create a custom Notification Channel:
First, create a Class in App\Notifications for example:
<?php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
class CustomDbChannel
{
public function send($notifiable, Notification $notification)
{
$data = $notification->toDatabase($notifiable);
return $notifiable->routeNotificationFor('database')->create([
'id' => $notification->id,
//customize here
'answer_id' => $data['answer_id'], //<-- comes from toDatabase() Method below
'user_id'=> \Auth::user()->id,
'type' => get_class($notification),
'data' => $data,
'read_at' => null,
]);
}
}
Second, use this channel in the via method in the Notification class:
<?php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
use App\Notifications\CustomDbChannel;
class NewAnswerPosted extends Notification
{
private $answer;
public function __construct($answer)
{
$this->answer = $answer;
}
public function via($notifiable)
{
return [CustomDbChannel::class]; //<-- important custom Channel defined here
}
public function toDatabase($notifiable)
{
return [
'type' => 'some data',
'title' => 'other data',
'url' => 'other data',
'answer_id' => $this->answer->id //<-- send the id here
];
}
}
Create and use your own Notification model and Notifiable trait and then use your own Notifiable trait in your (User) models.
App\Notifiable.php:
namespace App;
use Illuminate\Notifications\Notifiable as BaseNotifiable;
trait Notifiable
{
use BaseNotifiable;
/**
* Get the entity's notifications.
*/
public function notifications()
{
return $this->morphMany(Notification::class, 'notifiable')
->orderBy('created_at', 'desc');
}
}
App\Notification.php:
namespace App;
use Illuminate\Notifications\DatabaseNotification;
class Notification extends DatabaseNotification
{
// ...
}
App\User.php:
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
// ...
}
An example for #cweiske response.
If you really need extends the Illuminate\Notifications\Channels\DatabaseChannel not creating a new Channel you can:
Extends the channel:
<?php
namespace App\Notifications;
use Illuminate\Notifications\Channels\DatabaseChannel as BaseDatabaseChannel;
use Illuminate\Notifications\Notification;
class MyDatabaseChannel extends BaseDatabaseChannel
{
/**
* Send the given notification.
*
* #param mixed $notifiable
* #param \Illuminate\Notifications\Notification $notification
* #return \Illuminate\Database\Eloquent\Model
*/
public function send($notifiable, Notification $notification)
{
$adminNotificationId = null;
if (method_exists($notification, 'getAdminNotificationId')) {
$adminNotificationId = $notification->getAdminNotificationId();
}
return $notifiable->routeNotificationFor('database')->create([
'id' => $notification->id,
'type' => get_class($notification),
'data' => $this->getData($notifiable, $notification),
// ** New custom field **
'admin_notification_id' => $adminNotificationId,
'read_at' => null,
]);
}
}
And register the Illuminate\Notifications\Channels\DatabaseChannel on application container again:
app\Providers\AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(
Illuminate\Notifications\Channels\DatabaseChannel::class,
App\Notifications\MyDatabaseChannel::class
);
}
}
Now when the Illuminate\Notifications\ChannelManager try createDatabaseDriver will return your registered database driver.
More one option to solve this problem!
Unlike "Bassem El Hachem", I wanted to keep the database keyword in the via() methods.
So in addition to a custom DatabaseChannel, I also wrote my own ChannelManager that returns my own DatabaseChannel in the createDatabaseDriver() method.
In my apps' ServiceProvider::register() method, I overwrote the singleton for the original ChannelManager class to return my custom manager.
I solved a similar problem by customizing notification class:
create the class for this action:
artisan make:notification NewQuestion
inside it:
public function __construct($user,$question)
{
$this->user=$user;
$this->question=$question;
}
...
public function toDatabase($notifiable){
$data=[
'question'=>$this->(array)$this->question->getAttributes(),
'user'=>$this->(array)$this->user->getAttributes()
];
return $data;
}
then you can access proper data in view or controller like this:
#if($notification->type=='App\Notifications\UserRegistered')
New question from {{$notification->data['user']['name']}}
#endif