Laravel Spatie - Custom Attribute value when audit log via modal - laravel

on my modal, I have two functions which I have used to log the data when it has been changed. those are below.
namespace App\Models;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Contracts\Activity;
use Illuminate\Support\Facades\Auth;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Receivinglogentry extends Model
{
use HasFactory;
use LogsActivity;
protected $fillable = [
'status',
'amt_shipment',
'container',
'po',
'etd_date',
'eta_date',
];
protected $casts = [
'po_ref' => 'json',
];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()->logOnly(['*'])->logOnlyDirty();
}
public function tapActivity(Activity $activity,string $eventName)
{
$current_user = Auth::user()->name;
$event = $activity->attributes['event'];
$data = $activity->relations['subject']->attributes['container'];
$masterID = $activity->relations['subject']->attributes['id'];
$activity->description = "{$current_user} has {$event} Container : {$data}";
$activity->causer_name = $current_user;
$activity->master_id = $masterID ;
$activity->log_name = 'Receivinglogentry';
}
}
fillable data status has been stored as an integer value. but I have to log it as a string value something like PENDING or ACTIVE. any recommendation to log attributes customizably is appricated.

You can try ENUM concept but in Laravel 9 onwards. Here is a reference link - https://enversanli.medium.com/how-to-use-enums-with-laravel-9-d18f1ee35b56
There they describe about an enum UserRoleEnum:string which you can format as your own requirement.
In your case, the key itself is a number. So I suggest to make it as string as below.
enum StatusEnum:string
{
case STATUS_101 = 'Pending';
case STATUS_34 = 'Completed';
case STATUS_22 = 'On hold';
}
And then call it with your fillable status something like "STATUS_" + $fillable.status
If not using Laravel 9, you may try as below described in :
https://stackoverflow.com/a/55298089/2086966
class MyClass {
const DEFAULT = 'default';
const SOCIAL = 'social';
const WHATEVER = 'whatever';
public static $types = [self::DEFAULT, self::SOCIAL, self::WHATEVER];
and then write the rule as:
'type' => Rule::in(MyClass::$types)

Related

Laravel Factories with Intermediate Tables

I am trying to create a factory for a BlogPost model.
A blog post belongsToMany Tag and vice versa.
There is an intermediate table (blog_post_tag) to store the relations of blogposts to tags.
I would like to seed a blog post with a number of tag names.
How can one seed a DB using factories and intermediate tables?
May have an answer here
So I can add the following below to my BlogPost seeder. This would also create tags, but I would like to get existing tags (preferably 3-5 and only if any exist).
hasAttached() accepts a factory as the first argument so this will not work.
BlogPost::factory()
->hasAttached(
Tag::factory()->count(3)
)
->create();
This is how I fill my relations with existing data:
<?php
namespace Database\Factories;
use App\Models\Alpha;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use App\Models\Beta;
use App\Models\Gamma;
class AlphaFactory extends Factory {
protected $model = Alpha::class;
public function definition() {
$beta_ids = Beta::all()->pluck('id');
$gamma_ids = Gamma::all()->pluck('id');
return [
'name' => Str::slug($this->faker->unique()->realText(50)),
'number' => $this->faker->randomNumber(3),
'beta_id' => $this->faker->randomElement($beta_ids),
'gamma_id' => $this->faker->randomElement($gamma_ids)
];
}
}
If you want to be fancy and create sometimes new entries to relate to:
<?php
namespace Database\Factories;
use App\Models\Alpha;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use App\Models\Beta;
class AlphaFactory extends Factory {
protected $model = Alpha::class;
public function definition() {
$beta_id;
$beta_ids = Beta::all()->pluck('id');
if($beta_ids->isEmpty() || $this->faker->boolean($chanceOfGettingTrue = 50)) {
$beta_id = Beta::factory()->create()->id;
} else {
$beta_id = $this->faker->randomElement($beta_ids);
}
return [
'name' => Str::slug($this->faker->unique()->realText(50)),
'number' => $this->faker->randomNumber(3),
'beta_id' => $beta_id
];
}
}
I don't know if this is the right way to do this, but it works for me. If there is a better way, let me know.

Passing data in Laravel between a controller and a mailable

I can't seem to get data transferred after I submit a create form of a model record to a mailable. I think I'm right on most of it, but I'm stuck at passing the data through the gap between a controller and my mailable.
I have a shipment controller with the following function:
public function store(Request $request)
{
$this->validate(request(), [
'pro_number' => 'required',
'shipment_origin' => 'required'
/*'piecesNumber' => 'required' (had to remove for now, but must review)*/
]);
$user_id = Auth::id();
$input = $request->all();
//Save Initial Shipment Data
$shipment = new Shipment();
$shipment->pro_number = request('pro_number');
$shipment->shipment_origin = request('shipment_origin');
$shipment->date = request('date');
$shipment->due_date = request('due_date');
$shipment->tractor_id = request('tractor_id');
$shipment->trailer_id = request('trailer_id');
$shipment->driver_id = request('driver_id');
$shipment->notes = request('notes');
$shipment->shipper_no = request('shipper_no');
$shipment->ship_to = request('ship_to');
$shipment->ship_from = request('ship_from');
$shipment->bill_to = request('bill_to');
$shipment->bill_type = request('bill_type');
$shipment->load_date = request('load_date');
$shipment->shipment_status = 0;
$shipment->created_by = $user_id;
$shipment->save();
//Save Shipment Details
for ($i = 0; $i < count($request->shipment_details['piecesNumber']); $i++) {
Shipment_Detail::create([
'shipment_id' => $shipment->id,
'pieces_number' => $request->shipment_details['piecesNumber'][$i],
'pieces_type' => $request->shipment_details['piecesType'][$i],
'rate_type' => $request->shipment_details['rateType'][$i],
'charge' => $request->shipment_details['charge'][$i],
'weight' => $request->shipment_details['weight'][$i],
'hazmat' => $request->shipment_details['hazmat'][$i],
'description' => $request->shipment_details['description'][$i] ]);
}
$user = Auth::user()->email;
Mail::to($user)->send(new newFreightBill($shipment));
Session::flash('success_message','Freight Bill Successfully Created'); //<--FLASH MESSAGE
//Return to Register
return redirect('/shipments/i/'.$shipment->url_string);
}
Now as you can see there is a line near the bottom "Mail::to($user)->send(new newFreightBill($shipment));", what I would like to be doing is moving the copying the data from the submitted $shipment to the mailable
My newFreightBill mailable:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Shipment;
class newFreightBill extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public $shipment;
public function __construct(Shipment $shipment)
{
$shipment = $this->shipment;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->from('MYEMAIL')
->view('emails.shipments.created')->with(['shipment', $this->shipment]);
}
}
and my mail template (which I do receive in my tests):
<div>
Pro Number: {{ $shipment['pro_number'] }}
</div>
As you can see the template is VERY simple to say the least. I get in my emails to me the following:
Pro Number:
but absolutely nothing else, no actual number. SO I know the issue is passing the data from the form submit to the mailable variables.
If you are using properties on your Mail class such as public $shipment, you shouldn't use with() method. The properties on your class are available on your view without the need of using with.
And then you should do this: Pro Number: {{ $shipment->pro_number }}
Oh yes, and this: $shipment = $this->shipment; is backwards, it should be $this->shipment = $shipment;.

Laravel 5 Custom validation rule for items in array with reference to other fields

I'm trying to figure out how I can validate some data based on some other input. And to make it even more complicated, both the data and the other input are in an array.
Here are the rules:
$rules = [];
$rules['items.*.articleId'] = ['required', 'integer'];
$rules['items.*.data'] = ['required', new ItemData()];
The way the data is checked should be based on the the articleId of the current item. So the question is if there is a way to know the articleId (of the item) in the ItemData-rule?
I found at least one (somewhat cumbersome) solution:
Supply the rule with all input
Parse the $attribute in the passes method
Use this to find the articleId in the input
ItemData class:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class OrderItemData implements Rule {
private $_input = null;
public function __construct($input) {
$this->_input = $input;
}
public function passes($attribute, $value) {
$idParts = explode('.', $attribute);
array_pop($idParts);
$imp = $this->_input;
foreach ($idParts as $idPart) {
$imp = $imp[$idPart];
}
$articleId = $imp['articleId'];
// do some checking here..
return true;
}
public function message() {
return ':attribute is invalid.';
}
}
Still curious to know if there is a more elegant solution!

Laravel API APP Many-Many Relationship, how to return specific information in JSON?

I been trying to figure this out for some time now. Basically i got 2 models ' Recipe ', ' Ingredient ' and one Controller ' RecipeController ' .
I'm using Postman to test my API. When i go to my get route which uses RecipeController#getRecipe, the return value is as per the pic below:
Return for Get Route
If i want the return value of the get route to be in the FORMAT of the below pic, how do i achieve this? By this i mean i don't want to see for the recipes: the created_at column, updated_at column and for ingredients: the pivot information column, only want name and amount column information.
Return Value Format I Want
Recipe model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Recipe extends Model
{
protected $fillable = ['name', 'description'];
public function ingredients()
{
return $this->belongsToMany(Ingredient::class,
'ingredient_recipes')->select(array('name', 'amount'));
}
}
Ingredient Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ingredient extends Model
{
protected $fillable = ['name', 'amount'];
}
RecipeController
<?php
namespace App\Http\Controllers;
use App\Ingredient;
use App\Recipe;
use Illuminate\Http\Request;
class RecipeController extends Controller {
public function postRecipe(Request $request)
{
$recipe = new Recipe();
$recipe->name = $request->input('name');
$recipe->description = $request->input('description');
$recipe->save();
$array_ingredients = $request->input('ingredients');
foreach ($array_ingredients as $array_ingredient) {
$ingredient = new Ingredient();
$ingredient->name = $array_ingredient['ingredient_name'];
$ingredient->amount = $array_ingredient['ingredient_amount'];
$ingredient->save();
$recipe->ingredients()->attach($ingredient->id);
}
return response()->json(['recipe' => $recipe . $ingredient], 201);
}
public function getRecipe()
{
$recipes = Recipe::all();
foreach ($recipes as $recipe) {
$recipe = $recipe->ingredients;
}
$response = [
'recipes' => $recipes
];
return response()->json($response, 200);
}
API Routes:
Route::post('/recipe', 'RecipeController#postRecipe')->name('get_recipe');
Route::get('/recipe', 'RecipeController#getRecipe')->name('post_recipe');
Thanks Guys!
I think your best solution is using Transformer. Using your current implementation what I would recommend is fetching only the needed field in your loop, i.e:
foreach ($recipes as $recipe) {
$recipe = $recipe->ingredients->only(['ingredient_name', 'ingredient_amount']);
}
While the above might work, yet there is an issue with your current implementation because there will be tons of iteration/loop polling the database, I would recommend eager loading the relation instead.
But for the sake of this question, you only need Transformer.
Install transformer using composer composer require league/fractal Then you can create a directory called Transformers under the app directory.
Then create a class called RecipesTransformer, and initialize with:
namespace App\Transformers;
use App\Recipe;
use League\Fractal\TransformerAbstract;
class RecipesTransformer extends TransformerAbstract
{
public function transform(Recipe $recipe)
{
return [
'name' => $recipe->name,
'description' => $recipe->description,
'ingredients' =>
$recipe->ingredients->get(['ingredient_name', 'ingredient_amount'])->toArray()
];
}
}
Then you can use this transformer in your controller method like this:
use App\Transformers\RecipesTransformer;
......
public function getRecipe()
{
return $this->collection(Recipe::all(), new RecipesTransformer);
//or if you need to get one
return $this->item(Recipe::first(), new RecipesTransformer);
}
You can refer to a good tutorial like this for more inspiration, or simply go to Fractal's page for details.
Update
In order to get Fractal collection working since the example I gave would work if you have Dingo API in your project, you can manually create it this way:
public function getRecipe()
{
$fractal = app()->make('League\Fractal\Manager');
$resource = new \League\Fractal\Resource\Collection(Recipe::all(), new RecipesTransformer);
return response()->json(
$fractal->createData($resource)->toArray());
}
In case you want to make an Item instead of collection, then you can have new \League\Fractal\Resource\Item instead. I would recommend you either have Dingo API installed or you can follow this simple tutorial in order to have in more handled neatly without unnecessary repeatition

Laravel get relationship model within object after save()

I am using laravel 4.2.
I have two models as below :
class User extends Eloquent{
protected $table = 'users';
public function user_card_details(){
return $this->hasMany('User_card_details');
}
}
And
class User_card_details extends Eloquent {
protected $table = 'user_card_details';
public $timestamps = true;
public $softdeletes = true;
public function user(){
return $this->belongsTo('User')->first();
}
}
And I can save the relationship record using :
$user_card_details = new User_card_details();
$user_card_details->card_number = Input::get('card_number');
$user_card_details->card_exp_month = Input::get('card_expires_m');
$user_card_details->card_exp_year = Input::get('card_expires_y');
$user_card_details->card_cvv = Input::get('card_cvv');
$user->user_card_details()->save($user_card_details);
Up to this it works fine for me.
After save() , I want the user object should be populated with user_details.
So if I want to use the properties, I can use it like :
echo $user->user_card_details->card_number;
But it is not working now.
Any suggestions?
Thanks
You have to remove the () to get the actual model or collection:
echo $user->user_card_details->card_number;
When you're calling the actual function, you'll receive an instance of the Query builder.
Also, it seems that you're not persisting your $user_card_details-object before you try to bind it to your user:
$user_card_details = new User_card_details();
$user_card_details->card_number = Input::get('card_number');
$user_card_details->card_exp_month = Input::get('card_expires_m');
$user_card_details->card_exp_year = Input::get('card_expires_y');
$user_card_details->card_cvv = Input::get('card_cvv');
$user_card_details->save(); //Added this line.
$user->user_card_details()->save($user_card_details);
The more correct way would be:
$user_card_details = [
'card_number' => Input::get( 'card_number' ),
'card_exp_month' => Input::get( 'card_expires_m' ),
'card_exp_year' => Input::get( 'card_expires_y' ),
'card_cvv' => Input::get( 'card_cvv' ),
];
$userCardDetailObj = $user->user_card_details()->create( $user_card_details );
Now, your User_card_detail-instance will be available as the returned object.

Resources