Create Relationship inside the create function - laravel

I have a model that has a one to many relationship to the versions of the description.
In my Controller
$tag = Tags::create([
'name' => $request->get('name'),
'user_id' => \Auth::id(),
]);
$tag->update([
'content' => $request->get('description')
]);
In my Model:
public function setContentAttribute(string $value)
{
$this->versions()->create([
'user_id' => \Auth::id(),
'value' => $value
]);
}
So I can't put content directly as an attribute in the create method because there is no Model right now.
But is it possible to overwrite the create Method?
When I try to overwrite something like this in my Model it will do an infinity loop
public static function create($attr) {
return parent::create($attr);
}
So my question is if it is possible to have something like this:
$tag = Tags::create([
'name' => $request->get('name'),
'user_id' => \Auth::id(),
'content' => $request->get('content')
]);
and in the Model:
public static function create($attr) {
$value = $attr['content'];
$attr['content'] = null;
$object = parent::create($attr);
$object->content = $value;
$object->save();
return $object;
}
Update
I didn't overwrite the create method but called it customCreate. So there is no infinity loop anymore and I can pass all variables to the customCreate function that handles the relationships for me.
Solution
After reading the changes from 5.3 to 5.4 it turns out that the create method was moved so you don't have to call parent::create() anymore.
The final solution is:
public static function create($attr) {
$content = $attr['content'];
unset($attr['content']);
$element = static::query()->create($attr);
$element->content = $content;
$element->save();
return $element;
}

I don't see why not and you could probably implement a more general approach? Eg. checking if set{property}Attribute() method exists, if it does - use it to assign a value, if it doesn't - use mass assigning.
Something like:
public static function create($attr) {
$indirect = collect($attr)->filter(function($value, $property) {
return method_exists(self::class, 'set' . camel_case($property) . 'Attribute');
});
$entity = parent::create(array_diff_key($attr, $indirect->toArray()));
$indirect->each(function($value, $property) use ($entity) {
$entity->{$property} = $value;
});
$entity->save();
return $entity;
}
I haven't really tested it but it should work. I use something like this in one of my Symfony apps.

Related

CodeIgniter 3 code does not add data to database into 2 different tables (user_info & phone_info)

The problem is when I entered a new name no data is added. A similar thing happen when I entered an already existing name. Still, no data is added to the database. I am still new to CodeIgniter and not entirely sure my query builder inside the model is correct or not.
In the Model, I check if the name already exists insert data only into the phone_info table. IF name does not exist I insert data into user_info and phone_info.
Controller:
public function addData()
{
$name = $this->input->post('name');
$contact_num = $this->input->post('contact_num');
if($name == '') {
$result['message'] = "Please enter contact name";
} elseif($contact_num == '') {
$result['message'] = "Please enter contact number";
} else {
$result['message'] = "";
$data = array(
'name' => $name,
'contact_num' => $contact_num
);
$this->m->addData($data);
}
echo json_encode($result);
}
Model:
public function addData($data)
{
if(mysqli_num_rows($data['name']) > 0) {
$user = $this->db->get_where('user_info', array('name' => $data['name']))->result_array();
$user_id = $user['id'];
$phone_info = array(
'contact_num' => $data['contact_num'],
'user_id' => $user_id
);
$this->db->insert('phone_info',$phone_info);
} else {
$user_info = array(
'name' => $data['name']
);
$this->db->insert('user_info', $user_info);
$user = $this->db->get_where('user_info', array('name' => $data['name']))->result_array();
$user_id = $user['id'];
$phone_info = array(
'contact_num' => $data['contact_num'],
'user_id' => $user_id
);
$this->db->insert('phone_info', $phone_info);
}
}
DB-Table user_info:
DB-Table phone_info:
Extend and change your model to this:
public function findByTitle($name)
{
$this->db->where('name', $name);
return $this->result();
}
public function addData($data)
{
if(count($this->findByTitle($data['name'])) > 0) {
//.. your code
} else {
//.. your code
}
}
Explanation:
This:
if(mysqli_num_rows($data['name']) > 0)
..is not working to find database entries by name. To do this you can use codeigniters built in model functions and benefit from the MVC Pattern features, that CodeIgniter comes with.
I wrapped the actual findByName in a function so you can adapt this to other logic and use it elswehere later on. This function uses the query() method.
Read more about CodeIgniters Model Queries in the documentation.
Sidenote: mysqli_num_rows is used to iterate find results recieved by mysqli_query. This is very basic sql querying and you do not need that in a MVC-Framework like CodeIgniter. If you every appear to need write a manual sql-query, even then you should use CodeIgniters RawQuery methods.

Laravel - How to update Input Array without deleting Sales Detail

In my Laravel-8 project, I have this controller for Input Field Array Update.
Controller:
public function update(UpdateSaleRequest $request, $id)
{
try {
$sale = Sale::find($id);
$data = $request->all();
$update['date'] = date('Y-m-d', strtotime($data['date']));
$update['company_id'] = $data['company_id'];
$update['name'] = $data['name'];
$update['remarks'] = $data['remarks'];
$sale->update($update);
SaleDetail::where('sale_id', $sale->id)->delete();
foreach ($data['invoiceItems'] as $item) {
$details = [
'sale_id' => $sale->id,
'item_id' => $item['item_id'],
'employee_id' => $item['employee_id'],
'quantity' => $item['qty'],
'price' => $item['cost'],
'total_price' => $item['cost'] * $item['qty'],
'sale_type_id'=>$item['sale_type_id']
];
$saleDetail = new SaleDetail($details );
$saleDetail->save();
}
} catch (JWTException $e) {
throw new HttpException(500);
}
return response()->json($sale);
}
In the form, the user can add more Sales Detail or remove.
Some of the SaleDetail fields are being used somewhere else.
Is there a way to update the input field array without deleting the SaleDetail as shown in what I did here:
SaleDetail::where('sale_id', $sale->id)->delete();
Thanks
I've tried to restructure your code so that's easier to edit. I've left some comments. I can really recommend refactoring.guru. There you will find many ways to improve your code so that it is more extensible, maintainable and testable. If you have any questions, please feel free to ask.
class Sale extends Model
{
// Use a relationship instead of building your own query
public function details() {
return $this->hasMany(SaleDetail::class);
}
}
class SaleDetail extends Model
{
// Use a computed property instead of manually calculating total price
// You can access it with $saleDetail->totalPrice
public function getTotalPriceAttribute() {
return $this->price * $this->quantity;
}
}
class UpdateSaleRequest extends Request
{
public function authorize() {
return true;
}
protected function prepareForValidation() {
$this->merge([
// Create a Carbon instance by string
'date' => Carbon::make($this->date)
]);
}
public function rules() {
// Your validation rules
// Please also validate your invoice items!
// See https://laravel.com/docs/8.x/validation#validating-arrays
}
}
// We let Laravel solve the sale by dependency injection
// You have to rename the variable name in ihr web.php
public function update(UpdateSaleRequest $request, Sale $sale)
{
// At this point, all inputs are validated!
// See https://laravel.com/docs/8.x/validation#creating-form-requests
$sale->update($request->validated());
// Please ensure, that all properties have the same name
// In your current implementation you have price = cost, be consistent!
foreach($request->input('invoiceItems') as $invoiceItem) {
// How we can consider that a detail is already created?
// I assume that each item_id will only occur once, otherwise you'll
// place the id of each detail in your update form (e.g. in a hidden input)
$candidate = $sale->details()
->where('item_id', $properties['item_id'])
->first();
if($candidate) {
$candidate->update($properties);
} else {
$sale->details()->create($properties);
}
}
// A JWT-Exception should not be necessary, since your authentication
// will be handled by a middleware.
return response()->json($sale);
}
I have not tested the code, few adjustments may be needed.
Laravel has a method called updateOrCreate as follow
/**
* Create or update a record matching the attributes, and fill it with values.
*
* #param array $attributes
* #param array $values
* #return \Illuminate\Database\Eloquent\Model|static
*/
public function updateOrCreate(array $attributes, array $values = [])
{
return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
$instance->fill($values)->save();
});
}
That means you could do some thing like
public function update(UpdateSaleRequest $request, $id)
{
try {
$sale = Sale::find($id);
$data = $request->all();
$update['date'] = date('Y-m-d', strtotime($data['date']));
$update['company_id'] = $data['company_id'];
$update['name'] = $data['name'];
$update['remarks'] = $data['remarks'];
$sale->update($update);
foreach ($data['invoiceItems'] as $item) {
$details = [
'item_id' => $item['item_id'],
'employee_id' => $item['employee_id'],
'quantity' => $item['qty'],
'price' => $item['cost'],
'total_price' => $item['cost'] * $item['qty'],
'sale_type_id'=>$item['sale_type_id']
];
$sale->saleDetail()->updateOrCreate([
'sale_id' => $sale->id
], $details);
}
} catch (JWTException $e) {
throw new HttpException(500);
}
return response()->json($sale);
}
I would encourage you to refactor and clean up your code.You can also read more about it here https://laravel.com/docs/8.x/eloquent#upserts

Laravel 7: How to use in data seeder array

How to make distinct inserts using a custom array using database seeder?
Using the following code:
$categories = ['Hardware', 'Software', 'Planning', 'Tools'];
foreach ($categories as $category) {
App\Category::insert([
'name' => $category,
'slug' => Str::slug($category),
]);
}
It doesn't work without a factory for a category that is the problem matter if I use insert or create.
It gives this error
Unable to locate factory for [App\Category].
at vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:273
269| */
270| protected function getRawAttributes(array $attributes = [])
271| {
272| if (! isset($this->definitions[$this->class])) {
> 273| throw new InvalidArgumentException("Unable to locate factory for [{$this->class}].");
274| }
275|
276| $definition = call_user_func(
277| $this->definitions[$this->class],
Thanks
Create an array of data in the run method of you seeder file
public function run()
{
$categories = [
['name' => 'Music'],
['name' => 'Gaming'],
['name' => 'Entertainment'],
['name' => 'Non-Profit & Activism'],
['name' => 'Other'],
];
foreach ($categories as $category) {
Category::create($category);
}
}
You are pushing all data into the Model, so you need to set fillable or guarded in the Model.
class Category extends Model
{
protected $guarded = [];
}
try using insert witch take an array as parameter:
public function run()
{
$inputs[] = ['name'=> 'Hardware'];
$inputs[] = ['name'=> 'Software'];
$inputs[] = ['name'=> 'Planning'];
$inputs[] = ['name'=> 'Tools'];
App\Category::insert($inputs);
}
or you can do it in another way:
public function run()
{
$inputsNames = ['Hardware', 'Software', 'Planning', 'Tools'];
foreach($inputsNames as $categoryName)
{
$inputs[]=['name'=>$categoryName];
}
App\Category::insert($inputs);
}

Is there any efficient method on how to get id of object to my create method

I am creating a web module, and want to get ID of table licensing level two parse into my create method. Hence each ID of level will have a task and the ID need to be stored within my licensing table as a foreign key which reflects ID in Level Two table. How could I solve this, anyone can give me a good suggestion or way on doing this
public function add_show($id)
{
$level = PreLicensingLevelTwo::where('id', $id)->first();
$level->prelicensingtask = PreLicensingTask::where('pre_licensing_level_two_id', $level->id)->with('staff', 'statusdesc', 'prelicensingtaskstaff')->get();
return view('staff.regulatory.statutory.approval.display',compact('level'));
}
public function create()
{
$staff = Staff::pluck('staff_name');
$status = PreLicensingStatus::pluck('status_description', 'id');
return view('staff.regulatory.statutory.approval.create', compact('staff','status'));
}
public function show($id)
{
$one = PreLicensingLevelOne::where('pre_licensing_main_id', $id)->get();
foreach ($one as $key => $license)
{
$license->two = PreLicensingLevelTwo::where('pre_licensing_level_one_id', $license->id)->get();
}
$rendered = view('staff.regulatory.statutory.approval.show')->with('one', $one)->render();
return response()->json(array('status' => 1, 'tableData' => $rendered));
}
With help from my working collegue this is how i able to solve the question i asked
public function store(Request $request)
{
$this->validate($request, [
'task_title' => 'required',
'task_description' => 'required',
'task_due_date' => 'required',
]);
$leveltwo = PreLicensingLevelTwo::find($request->input('pre_licensing_level_two_id'));
$prelicensingtask = new PreLicensingTask;
$prelicensingtask->task_title =$request->input('task_title');
$prelicensingtask->task_description =$request->input('task_description');
$prelicensingtask->task_due_date =$request->input('task_due_date');
$prelicensingtask->created_by_staff_id = Auth::user()->ref_user->staff_id;
$prelicensingtask->status = $request->input('status');
$prelicensingtask->pre_licensing_level_two_id = $leveltwo->id;
$prelicensingtask->pre_licensing_level_one_id = $leveltwo->pre_licensing_level_one_id;
$prelicensingtask->pre_licensing_main_id = $leveltwo->pre_licensing_main_id;
$prelicensingtask->centre_id = Auth::user()->ref_user->centre_id;
$prelicensingtask->save();
return redirect()->back();
}

Laravel Map DB Column Names Using Proper Convention to Actual DB Column Names in Model

We're building a portal to replace part of an existing application as step one, but the DB schema holds to absolutely no conventions. Aside from the lack of any constraints, indexes, etc the names of columns are not descriptive and not snake-cased.
Is it possible to map DB table column names so that the portal uses proper descriptive and snake-cased column names like first_name but writes to the actual database column first to at least have the portal be a first step towards cleaning up the tech debt?
For example, similar to how the table name (Model::table) can be set if the table name doesn't follow convention:
Example
private $columns = [
// convention => actual
'first_name' => 'first',
'last_name' => 'last',
'mobile_phone' => 'phone',
'home_phone' => 'otherPhone', // seriously!?
];
I've looked through Model and the HasAttributes trait, but I'm still hoping that this might exist, or someone has found a way to do this as a temporary solution.
You can create a parent class for all your models:
abstract class Model extends \Illuminate\Database\Eloquent\Model {
protected $columns = [];
public function attributesToArray()
{
$attributes = parent::attributesToArray();
foreach ($this->columns as $convention => $actual) {
if (array_key_exists($actual, $attributes)) {
$attributes[$convention] = $attributes[$actual];
unset($attributes[$actual]);
}
}
return $attributes;
}
public function getAttribute($key)
{
if (array_key_exists($key, $this->columns)) {
$key = $this->columns[$key];
}
return parent::getAttributeValue($key);
}
public function setAttribute($key, $value)
{
if (array_key_exists($key, $this->columns)) {
$key = $this->columns[$key];
}
return parent::setAttribute($key, $value);
}
}
Then override $columns in your models:
protected $columns = [
'first_name' => 'first',
'last_name' => 'last',
'mobile_phone' => 'phone',
'home_phone' => 'otherPhone',
];
The proper way is to use accessors and mutators.
Defining An Accessor
public function getFirstNameAttribute() {
return $this->first;
}
Then, you can access the value by $model->first_name.
Defining A Mutator
public function setFirstNameAttribute($value) {
$this->attributes['first'] = $value;
}
Then, you can mutate the value for example:
$model->first_name = 'first_name';
$model->save();

Resources