I have a coupons table BREAD with Voyager whereby each coupon will have an email address. I would like to send an email to the specific email address which is associated with the coupon when a new coupon has been created.
Coupons table:
For example from the above picture, after I have created the coupon named: haha123, I would like to send an email to cdn#gmail.com.
As I didn't make a custom controller for generating new coupon and has been only using the default BREAD function from Voyager, hence I am unsure where and how should I do it.
SOLVED:
This is most likely not the best way as I didn't make use of the voyager events. Hence, I just do it the troublesome way by adding my own custom voyager CouponsController to overwrite the default controller and add the laravel mail function in the store method of within the custom CouponsController.
Maybe helpful, Send email when model status updated (Laravel Voyager Admin)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Mail;
class Contact extends Model
{
public $table = 'contacts';
public $fillable = ['name', 'email', 'message'];
protected static function boot()
{
parent::boot();
static::updating(function ($contact) {
if ($contact->status == 1) {
Mail::send(
'email',
array(
'name' => $contact->name,
'email' => $contact->email,
'bodyMessage' => $contact->message
),
function ($message) use ($contact) {
$message->from($contact->email);
$message->to('email_address#email.com', 'Name')->subject('Your request status updated');
}
);
}
});
}
}
Voyager just makes your work easy. It creates the model and the controller for you in laravel. You will find the controller and the models in the same place as a custom model or controller created using artisan. I hope it helps.
If you have more questions, please be specific about the issue you are facing.
Cheers
You could very simply hook into the lifecycle of the Coupon model with an event listener:
https://laravel.com/docs/5.6/eloquent#events
First tell the model to fire the CouponCreated event when a new model is created
use App\Events\CouponCreated;
class Coupon extends Model
{
/**
* The event map for the model.
*
* #var array
*/
protected $dispatchesEvents = [
'created' => CouponCreated::class
];
}
That event will be passed the Coupon model which you can use to send the email.
If you want more specific help you'd need to post code. I am sure you could also just do this in the create method of the CouponController if you hunt that down.
Related
In Laravel 8 it is possible to quickly fill relationships with factories. However, I cannot figure out how to generate more than one relationship. How can I create a random or new relationship for each link using the new Laravel 8 syntax?
This factory syntax is only available in Laravel 8.
https://laravel.com/docs/8.x/database-testing#factory-relationships
Problem
Consider the following relationship:
Each link belongs to a website and a post.
Both websites and posts can have many links.
<?php
class Post extends Model
{
use HasFactory;
function links()
{
return $this->hasMany(Link::class);
}
}
class Website extends Model
{
use HasFactory;
function links()
{
return $this->hasMany(Link::class);
}
}
class Link extends Model
{
use HasFactory;
function post()
{
return $this->belongsTo(Post::class);
}
function website()
{
return $this->belongsTo(Website::class);
}
}
What I tried/want
What I tried below will only generate one model for all the links. How can I create a random or new relationship for each link using the new Laravel 8 syntax?
Link::factory()->count(3)->forPost()->forWebsite()->make()
=> Illuminate\Database\Eloquent\Collection {#4354
all: [
App\Models\Link {#4366
post_id: 1,
website_id: 1,
},
App\Models\Link {#4395
post_id: 1, // return a different ID
website_id: 1,
},
App\Models\Link {#4370
post_id: 1, // return a different ID
website_id: 1, // return a different ID
},
],
}
Just add this to your LinkFactory:
public function definition()
{
return [
'post_id' => function () {
return Post::factory()->create()->id;
},
.....
];
}
And now you can create new Post for each new Link:
Link::factory()->count(3)->create();//Create 3 links with 3 new posts
or attach new Links to existing Post:
Link::factory()->count(3)->create(['post_id' => Post::first()->id]); //create 3 links and 0 new posts
In Laravel 9, you can use this macro:
// database/factoryMacros.php
<?php
namespace Database\Support;
use Illuminate\Database\Eloquent\Factories\BelongsToRelationship;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
/** #param Factory|Model $factory */
Factory::macro('hasParent', function (mixed $factory, string $relationship = null): self {
return $this
->state(function () use ($factory, $relationship): array {
$belongsTo = new BelongsToRelationship(
factory: $factory,
relationship: $relationship ?? guessBelongsToMethodName($factory),
);
return $belongsTo
->recycle(recycle: $this->recycle)
->attributesFor(model: $this->newModel());
});
});
Factory::macro('hasChildren', fn (...$arguments): self => $this->has(...$arguments));
Factory::macro('hasChild', fn (...$arguments): self => $this->has(...$arguments));
/** #param Factory|Model $factory */
function guessBelongsToMethodName(mixed $factory): string
{
$modelName = is_subclass_of($factory, Factory::class)
? $factory->modelName()
: $factory::class;
return Str::camel(class_basename($modelName));
}
Usage
Use the method hasParent($factory) instead of for($factory):
// Creates 3 Link, 3 Post, 3 Website
Link::factory()
->count(3)
->hasParent(Post::factory())
->hasParent(Website::factory())
->make();
You can also use hasChildren($factory) or hasChild($factory) instead of has for name consistency:
// Creates 3 Post, 3 Link
Post::factory()
->count(3)
->hasChild(Link::factory())
->make();
The syntax of the macros is the same as for and has.
You can explicitly define the relationship name, pass complex factory chains, pass a concrete model, and use it with recycle, for example.
Installation
Add the file to your composer.json:
{
...
"autoload": {
"files": [
"database/factoryMacros.php"
]
}
}
Run a composer dump-autoload to reload the composer file.
Alternatively, you can register the macro as a service or load it as a mixin.
PS: I intend to create a library for this in the future.
Tests
/**
* Use "DatabaseEloquentFactoryTest.php" as base:
* https://github.com/laravel/framework/blob/de42f9987e01bfde50ea4a86becc237d9c8c5c03/tests/Database/DatabaseEloquentFactoryTest.php
*/
class FactoryMacrosTest extends TestCase
{
function test_belongs_to_relationship()
{
$posts = FactoryTestPostFactory::times(3)
->hasParent(FactoryTestUserFactory::new(['name' => 'Taylor Otwell']), 'user')
->create();
$this->assertCount(3, $posts->filter(function ($post) {
return $post->user->name === 'Taylor Otwell';
}));
$this->assertCount(3, FactoryTestUser::all());
$this->assertCount(3, FactoryTestPost::all());
}
}
TL;DR;
In Laravel 9, it is not possible to achieve this. The for() uses a single model for all instances.
There's a PR to fix this behavior, but the PR was closed, and I'm not sure it will ever be implemented:
https://github.com/laravel/framework/pull/44279
The laravel magic factory method for allows you to populate the database with one record from the foreign table. See link to documentation https://laravel.com/docs/8.x/database-testing#belongs-to-relationships
In your case, using forPost() and forWebsite() will allow you to populate the database with one id from the Post table and the Website table.
If you want to use different IDs use this syntax instead
Link::factory()->count(3)->make()
Had a similar problem and was only able to get it working when I attached within the afterCreating() on a single factory. This allows me to create/store the id of each model and then attach to the Link model
I'm choosing to start with WebsiteFactory but you can also start with PostFactory since those are the "highest parent" models. If you try to make a Link without the website_id and the post_id I believe you will get a error asking for both.
class WebsiteFactory extends Factory
{
public function definition(){...}
public function configure()
{
return $this->afterCreating( function (Website $website){
// the website model is created, hence after-creating
// attach Website to a new Post
$post = Post::factory()->hasAttached($website)->create();
// create new links to attach to both
$links = Link::factory()->for($website)->for($post)->count(3)->create();
});
You can leave PostFactory and LinkFactory as simple definition blocks (or add other stuff if you wanted). Now when you create a new Website via factory, it will create a new post and 3 new links. For example, you can now run
php artisan tinker
$w = Website::factory()->create(); // one website-one post-3 links
$ws = Website::factory()->count(5)->create(); // 5 website-5 post- 15 links
Check out the Factory Callbacks here (9.x docs, but they are in 8.x too):
https://laravel.com/docs/9.x/database-testing#factory-callbacks
\App\Models\Category::factory(10)
->has(Product::factory()->count(10), 'products')
->create();
It would be better if you play around with code. You will understand better.
$user = User::factory()
->has(Post::factory()->count(3), 'posts')
->create();
The above code will create three post for a single user. It will insert three post row and a user row. On the other hand the code below, seems three post will be inserted for user with name Jessica Aercher, that is it won't insert a user.
$posts = Post::factory()
->count(3)
->for(User::factory()->state([
'name' => 'Jessica Archer',
]))
->create();
In Laravel, I have used a model's create method in many controllers,
Now I need to perform strip_tags($comment) to a specific input in all those controllers before it is inserted in database with create() method like this:
Comment:create([
'comment' => $comment,
...
]);
Should I repeatedly do this in all controllers:
$comment = strip_tags($comment); // < Is it possible to do this on model's file so we don't repeat it every time?
Comment:create([
'comment' => $comment,
...
]);
Or this is something that can be achieved in the Model?
You may use model events to make checks and arrangements before saving it.
add following method to your model class;
protected static function boot()
{
parent::boot();
self::saving(function ($model) {
$model->comment = strip_tags($model->comment);
// do your pre-checks or operations.
});
}
here is a useful post to read about it
There is a way to do it directly in the model, it's called Mutators. If your column name is comment then the mutator function will be called setCommentAttribute.
public function setCommentAttribute($comment)
{
$this->attributes['comment'] = strip_tags($comment);
}
Any place where save/update is used for this model, the comment data will go through the set... function.
Currently got some downtime and thought I’d play around with laravel as I usually just use it for basic web dev.
I’ve tried seeders and like how it’s quicker for me to drop test data into a table but I’m not sure how I go about doing this for multiple tables which have relationships.
In my case let’s say I’ve got 3 tables, a customer table, a customer address table and a customer purchase table.
Seeding the customer table is easy because it’s the main table but the other two tables have a column called customer_id which is the reference back to the customer table.
If I want to use seeders how do I write the code to make X amount of customers and then make the following records in the other tables which link back the created customers.
Apologies if I’ve missed something in the documentation.
First, you need to set up the relationship on the Customer model. I assume the Customer model has one-to-many relationship with CustomerAddress and CustomerPurchase models.
<?php
// app/Customer.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
public function addresses()
{
return $this->hasMany(CustomerAddress::class);
}
public function purchases()
{
return $this->hasMany(CustomerPurchase::class);
}
}
Next, you're going to need to define a model factory.
<?php
// database/factories/CustomerFactory.php
use Faker\Generator as Faker;
$factory->define(App\Customer::class, function (Faker $faker) {
return [
'name' => $faker->name,
];
});
$factory->define(App\CustomerAddress::class, function (Faker $faker) {
return [
'address' => $faker->streetAddress,
];
});
$factory->define(App\CustomerPurchase::class, function (Faker $faker) {
return [
'item' => $faker->word,
'amount' => $faker->randomFloat(2, 10, 200),
];
});
Now all you have to do is use those model factories to seed random records on the database. You can still use the Eloquent collection and relationship just fine:
<?php
// database/seed/DatabaseSeeder.php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
factory(App\Customer::class, 10)->create()->each(function ($customer) {
$customerAddresses = factory(App\CustomerAddress::class, 2)->make();
$customerPurchases = factory(App\CustomerPurchase::class, 5)->make();
$customer->addresses()->saveMany($customerAddresses);
$customer->purchases()->saveMany($customerPurchases);
});
}
}
Hope this gives you some ideas.
Some modified code from documentation:
$users = factory(Customer::class, 3)
->create()
->each(function ($u) {
$u->addresses()->save(factory(Address::class)->make());
$u->purchases()->saveMany(factory(Purchase::class, 10)->make());
});
You can simple make a foreach statement
$customers = factory(Customer::class,N)->create();
foreach( $customers as $customer)
{
factory(CustomerAddress::class)->create([
'customer_id' => $customer->id
]);
factory(CustomerPurchase::class)->create([
'customer_id' => $customer->id
]);
}
NOTE This is recommended only in case you want to have an address and purchase for every customer.
Else you should treat the seeding differently and not provide an address and purchase for every customer.
This will make your tests less reliable
I am doing an extension build on the User model on larval 5.
What I want to accomplish is once the user registers and is validated I want ANOTHER database table created named userInfo which has a belongsto Relationship with the User model and the User model has a hasOne relationship with userInfo.
Two things.
How to I successfully implement this logic. I was reading
http://laravel.com/docs/master/eloquent-relationships#inserting-related-models
and
http://laravel.com/docs/5.1/events#registering-events-and-listeners
But Im not to sure
And second.
Where best do I implement this logic.
Thanks in advance
PS. I do not what to combine the two database because the user model is when they register and data that lives in userInfo is "optional" for the user to fill out After authentication.
If I am understanding the question correctly, you want to create an additional related model to the user when they register. That should be pretty straightforward - something like:
$user->userInfo()->save(UserInfo::create([
//whatever information you need to save to the userInfo table - if any
]));
as for where to put it, you have options there. You could put it the 'store' method on your registration controller. Or extract it out to a service class 'RegisterUser', and create the user and userInfo there.
something like:
//controller method
public function store(Request $request, RegisterUser $registerUser)
{
$user = $registerUser->handle($request);
return view(....)
}
//RegisterUser class
public function __construct(UserInfo $userInfo)
{
$this->userInfo = $userInfo;
}
public function handle($request)
{
// create your user
$user = .....
$user->userInfo()->save($this->userInfo->create([
.....
]));
// whatever else you need to do - send email, etc
return $user;
}
Edit: If you are using Laravel 5 and the default registration scaffolding, then you can add code to the app\Services\Registar class. This class is called by the postRegister method in the trait. Change the create method:
// app\Services\Registar
public function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
$user->userInfo()->save($this->userInfo->create([
.....
]));
// whatever else you need to do - send email, etc
return $user;
}
If you are using Laravel 5.1 then the Registar class doesn't exist anymore, the create method has been moved to the AuthController (the method is the same, just in a different location) so override it there.
Your answer pointed me in the right direction but I made some simple tweek to your edited question.
$user->userInfo()->firstOrCreate([
'aboutMe' => 'Please Fill out so users can know about you',
'bodyType' => '--',
]);
return $user;
I'm trying to make a profile page for my registered users.
On this page the Auth\User data will be displayed (Name, Email) but also extra profile information (city, country, phone number, ..).
I've already made the one to one relationship but I'm having one issue.
When a User gets created, I would like to automaticly have a Profile created for that specific user.
At the moment I simply added the profile for my first user through tinker, but as soon as I made a second user & went to the profile page, it gave an error (seeing the profile had not been made yet).
In the Profile.php I have:
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Profile extends Model {
protected $table = 'profiles';
protected $fillable = ['city', 'country', 'telephone'];
public function User()
{
return $this->belongsTo('App\User');
}
}
In the User.php I added:
<?php namespace App;
...
class User extends Model implements AuthenticatableContract, CanResetPasswordContract {
use Authenticatable, CanResetPassword;
...
protected $table = 'users';
protected $fillable = ['name', 'lastname', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];
public function Profile()
{
return $this->hasOne('App\Profile');
}
}
I show the Profile data like this (on my profile.blade.php page):
Full name: {{ Auth::user()->name }} {{ Auth::user()->lastname }}
E-Mail Address: {{ Auth::user()->email}}
City: {{ Auth::User()->profile->city}}
Country: {{ Auth::User()->profile->country}}
Phone number: {{ Auth::User()->profile->telephone}}
I'm guessing I need to add something to the 'AuthenticatesAndRegistersUsers' trait and the 'Registrar.php' service, but I have no idea what.
Thanks,
Cedric
As noted in the comments on your question I believe the best answer here is to combine the two models into one User model.
However, if you want to create a relationship on your user when it is created you can modify the Registrar service.
The AuthenticatesAndRegistersUsers trait will use the registrar (located in app/Services/Registrar.php by default) to validate and register users.
You can just modify the create method in there to automatically create the profile relation at the same time:
public function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
$user->profile()->save(new Profile);
return $user;
}
There are three options that come to my mind.
Combine User and Profile tables
Why are you separating the user account from the profile? I can't think of a good reason to (not saying there isn't one, I just can't think of one). Combining the tables would save you database queries and completely resolve this issue. I think this would be the best option.
Use model event.
Create a listener on the User::created event.
User::created(function(User $user) {
$user->profile->save(Profile::create([... ]));
});
Use a repository
Create a user repository to manage all the data base querying. Then in the repository create method you can manually create the profile record and associate the two. Then use the repository in the Registrar instead of the Model directly