I am working on Order-Ticket functionality. Many tickets should ideally be generated for a single order.
Earlier, the responsibility of creating tickets was written inside Order Model and that time it worked fine but now that I am trying to move that responsibility to Ticket model itself, it is not working.
I'll start with the Test file 'TicketTest.php'
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Concert;
use Carbon\Carbon;
use App\Order;
class TicketTest extends TestCase
{
use DatabaseMigrations;
/** #test */
function can_create_multiple_tickets_for_order(){
$numberOfTickets = 2;
// Arrange - Create Concert and Order
$concert = factory(Concert::class)->create(
[
'date' => Carbon::parse('December 1, 2016 8:00pm'),
]
);
$order = $concert->orders()->create([
'email' => 'abc#gmail.com'
]);
// Act - Create Tickets
$order->tickets()->createTickets($numberOfTickets); // This line throws call to undefined method.
// Assert
$this->assertEquals($numberOfTickets, $order->tickets()->count());
}
}
'Order.php' Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
protected $guarded = [];
public function tickets()
{
return $this->hasMany(Ticket::class);
}
}
'Ticket.php' Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ticket extends Model
{
public function createTickets($ticketQuantity){
foreach(range(1, $ticketQuantity) as $i) {
$this->create([]);
}
}
public function order(){
return $this->belongsTo(Order::class);
}
}
Before Laravel 5.4, I solved this problem by creating a new Relation and using that relation to Map Order to Ticket. Created a file 'app/Relation/TicketOrderRelation.php' and added following code in it
<?php
namespace App\Relation;
use Illuminate\Database\Eloquent\Relations\HasMany;
class TicketOrderRelation extends HasMany {
/**
* Create a Collection of new tickets
*
* #param int $ticketQuantity Number of Tickets to be created
* #return \Illuminate\Database\Eloquent\Collection
*/
public function createTickets($ticketQuantity){
$instances = [];
foreach(range(1, $ticketQuantity) as $i) {
$instances[] = $this->create([]);
}
return collect($instances);
}
}
New 'Order.php' file
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\Relation\TicketOrderRelation;
class Order extends Model
{
protected $guarded = [];
/**
* Define a one-to-many relationship. One Order can have many tickets.
*
* This method is duplicate of hasMany method. The only difference is it
* returns object of TicketOrderRelation class at the bottom instead of
* object of HasMany class.
*
* #param string $related
* #param string $foreignKey
* #param string $localKey
* #return \App\Relation\TicketOrderRelation
*/
public function ticketOrderRelation($related, $foreignKey = null, $localKey = null)
{
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$localKey = $localKey ?: $this->getKeyName();
return new TicketOrderRelation($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
}
public function tickets()
{
return $this->ticketOrderRelation(Ticket::class);
}
}
New 'Ticket.php' file
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ticket extends Model
{
protected $guarded = [];
public function order()
{
return $this->belongsTo(Order::class);
}
}
After Laravel 5.4, Eloquent builder started supporting create method which makes creation easy.
This answer shows how to create a custom Builder. I have not yet tried if custom builder solves this problem on Laravel 5.4 or not but if it does, then I would prefer that.
Related
I'm using Laravel Nova 3 Queued Actions.
I have over 25K records in my table.
I want to Laravel Nova Action create new job only if model has attribute status == 1.
I tried to use continue in foreach loop but it does'nt work.
<?php
namespace App\Nova\Actions;
use App\Http\Services\UserService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Collection;
use Laravel\Nova\Actions\Action;
use Laravel\Nova\Fields\ActionFields;
class UserSynchronization extends Action implements ShouldQueue
{
use InteractsWithQueue, Queueable;
public $name = 'Synchronize User';
public static $chunkCount = 1;
public $withoutActionEvents = true;
public function __construct()
{
$this->connection = 'database';
//$this->queue = 'default';
}
/**
* Perform the action on the given models.
*
* #param \Laravel\Nova\Fields\ActionFields $fields
* #param \Illuminate\Support\Collection $models
* #return mixed
*/
public function handle(ActionFields $fields, Collection $models)
{
foreach ($models as $model) {
if (!$model->status !== 1) {
continue;
}
UserService::synchronize($model);
}
return Action::message('Users have been successfully synchronized');
}
/**
* Get the fields available on the action.
*
* #return array
*/
public function fields()
{
return [];
}
}
Create record in jobs table only if model->status === 1
I'm using [Nwidart][1] module package and I'm trying to create a seeder using a factory. But when I try to run php artisan db:seed I get this error
Call to undefined function Modules\Products\Database\Seeders\factory()
Here is my ProductsDatabaseSeeder.php
<?php
namespace Modules\Products\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use Modules\Products\Models\Product;
class ProductsDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Model::unguard();
factory(Product::class, 10)->create();
}
}
My ProductFactory
<?php
/** #var \Illuminate\Database\Eloquent\Factory $factory */
use Faker\Generator as Faker;
use Modules\Products\Models\Product;
$factory->define(Product::class, function (Faker $faker) {
return [
'title' => $this->faker->title,
'price' => $this->faker->randomNumber(3),
];
});
My Product.php
<?php
namespace Modules\Products\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $fillable = [];
}
Full error
Call to undefined function Modules\Products\Database\Seeders\factory()
at Modules/Products/Database/Seeders/ProductsDatabaseSeeder.php:25
21▕
22▕
23▕
24▕
➜ 25▕ $product = factory(Product::class, 10)->create();
26▕
27▕
28▕ }
29▕ }
+8 vendor frames
9 database/seeders/DatabaseSeeder.php:20
Illuminate\Database\Seeder::call()
+24 vendor frames
34 artisan:37
Illuminate\Foundation\Console\Kernel::handle()
This is what I get after changing
factory(Product::class, 10)->create();
to
Product::factory()->count(10)->create();
Class 'Database\Factories\Modules\Products\Models\ProductFactory' not found
at vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:656
652▕ public static function factoryForModel(string $modelName)
653▕ {
654▕ $factory = static::resolveFactoryName($modelName);
655▕
➜ 656▕ return $factory::new();
657▕ }
658▕
659▕ /**
660▕ * Specify the callback that should be invoked to guess factory names based on dynamic relationship names.
+1 vendor frames
2 Modules/Products/Database/Seeders/ProductsDatabaseSeeder.php:25
Modules\Products\Models\Product::factory()
+8 vendor frames
11 database/seeders/DatabaseSeeder.php:21
Illuminate\Database\Seeder::call()
[1]: https://nwidart.com/laravel-modules/v6/introduction
As of Laravel 8 which is what you probably use - as you are using the HasFactory trait - you will need to call
Product::factory()->create()
In order to run the factory
https://laracasts.com/discuss/channels/code-review/please-help-call-to-undefined-function-factory?signup
Apart from adding the HasFactory trait to your model, in order to support the Modules{Module} namespace you need to put this in your model:
/**
* Create a new factory instance for the model.
*
* #return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return \Modules\Module\Database\Factories\ModelFactory::new();
}
So, in your model Product.php
<?php
namespace Modules\Products\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
/**
* Create a new factory instance for the model.
*
* #return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return \Modules\Products\Database\Factories\ProductFactory::new();
}
protected $fillable = [];
}
Also, since laravel 8 the conventions of using a factory has changed.
So, in your
ProductsDatabaseSeeder.php
<?php
namespace Modules\Products\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use Modules\Products\Models\Product;
class ProductsDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Model::unguard();
Product::factory()->count(50)->create();
}
}
And, finally the factory class ProductFactory.php like
<?php
namespace Modules\Products\Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Modules\Products\Models\Product;
class ProductFactory extends Factory
{
protected $model = Product::class;
public function definition()
{
return [
'title' => $this->faker->title,
'price' => $this->faker->randomNumber(3),
];
}
}
I have Two Models News and Review
This is News model
<?php
namespace Modules\Newsletter\Entities;
use Brexis\LaravelWorkflow\Traits\WorkflowTrait;
//use Hyn\Tenancy\Abstracts\TenantModel as TenancyModel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
/**
* This is for storing news
* Class News
* #package Modules\Newsletter\Entities
*/
//class News extends TenancyModel {
class News extends Model {
use WorkflowTrait;
protected $table = 'news_info';
protected $fillable = [
'title', 'header', 'description', 'status', 'created_by', 'media_url', 'media_thumbnail', 'media_type'
];
/**
* This creates relationship between News and Reviews
* #return mixed
*/
public function reviews() {
return $this->morphMany(NewsReview::class, 'reviewable');
}
/**
* This is for creating relationship between News and Review and counting reactions according to is_visible=1
* #return mixed
*/
public function reviewsCountByvisible() {
return $this->morphMany(NewsReview::class, 'reviewable')
->select(
'reviewable_id',
DB::raw("COUNT(CASE WHEN review_reaction=0 THEN 1 ELSE NULL END) as review_bad"),
DB::raw("COUNT(CASE WHEN review_reaction=1 THEN 1 ELSE NULL END) as review_average"),
DB::raw("COUNT(CASE WHEN review_reaction=2 THEN 1 ELSE NULL END) as review_good")
)->where('is_visible', '=', 1)
->groupBy('reviewable_id');
}
}
This is Review model
<?php
namespace Modules\Newsletter\Entities;
use App\User;
//use Hyn\Tenancy\Abstracts\TenantModel as TenancyModel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* This is for storing Reviews of a News
* Class NewsReview
* #package Modules\Newsletter\Entities
*/
//class NewsReview extends TenancyModel {
class NewsReview extends Model {
use SoftDeletes;
protected $fillable = [
'review_text',
'review_reaction',
'is_visible',
'reviewed_by',
'reviewable_id',
'reviewable_type'
];
/**
* This is for creating relation between Reviews and News
* #return mixed
*/
public function reviewable() {
return $this->morphTo();
}
/**
* #return mixed
*/
public function news() {
return $this->belongsTo(News::class);
}
/**
* #return mixed
*/
public function reviewer() {
return $this->hasOne(User::class, 'id', 'reviewed_by');
}
}
This is the function I am using to delete news
public function delete($id){
$news=News::find($id);
$news->delete();
}
I want that if I delete a news then all reviews related to this news should be deleted.
Can anyone help me.Help will be highly appreciated.
you can do that by using model events defined in your AppServiceProvider file :
public function boot()
{
News::deleted(function (News $$news) {
$news->reviews()->delete();
});
}
Alternatively to using events as already suggested, you could also override your News' delete method and handle deletion there:
class News {
public function delete(){
// delete associated reviews
$this->reviews()->delete();
// call parent delete, which deletes the model itself
parent::delete();
}
}
This will only work when directly calling the delete method on a news instance though.
You could also take care of that at the database level via constraints. E.g. by definining:
Schema::table('news_reviews', function (Blueprint $table) {
$table->unsignedBigInteger('news_id');
$table->foreign('news_id')->references('id')->on('news')
->onDelete('cascade'); // this will
});
This has the advantage that you are guaranteed to never have any stray reviews left in your database and that you don't have to manually delete reviews, BUT your application will not be notified when reviews are deleted (because it happens at the database level) and it's less obvious for other developers, so you should document that behaviour.
Please find below working example of morphMany relation model delete. My News model is :
class News extends Model
{
protected $table = 'news';
public function reviews()
{
return $this->morphMany('App\Review', 'reviewable');
}
}
And this is review model
class Review extends Model
{
public function reviewable()
{
return $this->morphTo();
}
}
$news->reviews() is the relationship query to return all of the reviews for a news. If you call delete() on that, it will delete all of those records.
$news = News::find(1);
$news->reviews()->delete();
Please Replace your code, this:
public function delete($id){
$news=News::find($id);
$news->delete();
}
To this :
public function delete($id){
$news=News::find($id);
$news->reviews()->delete();
$news->delete();
}
I am facing a weird error right now
In my controller, when I import the class user like this
use Illuminate\Foundation\Auth\User;
It works when I use eloquent like
public function index()
{
$farms = User::where('role_id', 3)->get();
$user = Auth::user();
$animal = Animal::all();
return view('clinic.index', compact('user', 'animal', 'farms'));
}
But refuses to work when it comes to table relationships like
public function show($id)
{
$farms = User::with(['animals'])->findOrFail($id);
return view('clinic.show',compact('farms'));
}
showing me this error
"Call to undefined relationship [animals] on model [Illuminate\Foundation\Auth\User]"
But whenever I import the user class as App\User in my controller,
It works in the relationship but refuses to work with the eloquent showing this error
"Call to a member function get() on null"
Now I am kinda confused. Any help will be welcomed
App\User
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Model;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $guarded = [];
public static function where(string $string, int $int)
{
}
public static function select(array $array)
{
}
public function role(){
return $this->belongsTo(Role::class);
}
public function animals(){
return $this->hasMany(Animal::class);
}
public function clinics(){
return $this->hasMany(Clinic::class);
}
public function slaughter(){
return $this->hasMany(Slaughter::class);
}
public function address(){
return $this->belongsTo(Address::class);
}
/**
* 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',
];
}
The Illuminate\Foundation\Auth\User class is the parent of the App\User class and animals relation set in the App\Userclass. So you can't call animals relation from Illuminate\Foundation\Auth\User class.
You should remove these functions from the App\User Model:
public static function where(string $string, int $int)
{
}
public static function select(array $array)
{
}
I have a Stuff Model like this :
class Stuff extends Model
{
protected $primaryKey = 'stuff_id';
protected $fillable = ['stuff_id' , 'title' , 'desc'];
protected $dates = ['deleted_at'];
}
In the other hand there is a Product model that extended from Stuff Model like this :
class Product extends Stuff
{
protected $fillable = ['quantity' , 'picture'];
}
As you can see beacause Product is extended from Stuff and primary key of Stuff is stuff_id , Anywhere that I want to call a Product instances and needs to print it's id should use a $product->stuff_id while I want use a clearer name for that like $product->product_id.
Is there any way that can define a alias primary key in child model that interpreted to stuff_id in back-end when running queries on database.
To turn product_id into an alias of stuff_id:
...
$product->product_id // resolves to $product->stuff_id
...
public function getProductIdAttribute(): int
{
return $this->stuff_id;
}
...
Instead of using $primaryKey, you can override the function that reads from that variable.
In your Stuff model, try adding something along the lines of:
/**
* Get the primary key for the model.
*
* #return string
*/
public function getKeyName(): string
{
return [
Stuff::class => 'stuff_id',
Product::class => 'product_id',
][get_class($this)];
}
And for reference, the default behavior: (Illuminate/Database/Eloquent/Model.php)
/**
* Get the primary key for the model.
*
* #return string
*/
public function getKeyName()
{
return $this->primaryKey;
}
Using Global Scope:
//Say ProductScope.php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Schema;
class ProductScope implements Scope
{
protected $model_name;
public function __construct($model_name)
{
$this->model_name = $model_name;
}
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
$attr = Schema::getColumnListing($this->model_name);
$attr_ = array_map(function ($item){
return $item === 'stuff_id' ? $item.' as product_id' : $item;
}, $attr);
$builder->select($attr_);
}
}
Then in the Product Model:
use App\Scopes\ProductScope;
class Product extends Stuff
{
protected $table = 'stuffs';
protected $primaryKey = 'stuff_id';
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope(new ProductScope('stuffs'));
}
}
This will replace the stuff_id with product_id