Laravel : insert random id on field have autoincrement id - laravel

I have generate random id function :
class CreateRandomId {
public static function get_id($my_table) {
$id = mt_rand(1000000, 9999999999);
if(self::check_id($id,$my_table)){
get_id($my_table);
}
return $id;
}
public static function check_id($id,$my_table) {
$table=DB::table($my_table)->where('id',$id)->get();
if (count($table)==0) {
return false;
}
return true;
}
}
I used it and it is generate id correctly but when i want to save it as id thus :
$new_user_id = CreateRandomId::get_id('users');
$user = User::create([
'id' => $new_user_id,
'social_id'=>$request->id,
'client'=> $request->client,
'username'=>$request->username,
'password'=>$password,
'email'=>$request->email,
'pics'=>$request->pics,
'role'=>'user'
]);
It doesn't save the random id but the auto_incrementid
I view this topic How to disable Laravel eloquent Auto Increment?
but I dont want to remove auto increment I just want to insert random data instead of it .

Lets close this question, so as you said in the comments, the fix for this is to make the id field fillable so it looks something like this
protected $fillable = ['id', ...];

try to add your primary key in fillable array , and make increment to be false like this :-
public $increment = false;
protected $fillable = ['id',..];

Related

Yii2 Activerecord not saved before redirect and shown in "view"

Yii2 framework. When I save multiple ActiveRecords in AFTER_INSERT_EVENT of another ActiveRecord, the values in the database is not updated fast enough, so old values are shown when redirect to viewing the data.
To be more specific: Standard XAMPP environment with PHP 7.2.9. I have made a trait to make it easy to have extra attributes with history in model (either existing attributes or new attributes). The trait is used on ActiveRecord.
Notice the sleep(5) in function TL_save. This handled the problem, but it is not the correct solution. How do I ensure all is updated before it is read again? I want to avoid use locks on the row as that would require alteration of a table before it can be used. Is there a way around it? Transactions - I have tried it but perhaps not correct as it had no effect. A reload of the view page also solves the problem, but again: not very classy :-)
Also: Should I share this code on GitHub? I have not done so before and are not quite sure if it would be of any value to others really.
trait TimelineTrait
{
private $timelineConfig;
public function timelineInit($config)
{
$std = [
'attributes' => [], // required
'_oldAttributes'=>[],
'datetime'=> date('Y-m-d H:i:s'),
'validationRule'=>'safe',
'table'=>$this->tableName(),
'onlyDirty'=>true, // using !=, not !==
'events'=>[
self::EVENT_AFTER_INSERT=>[$this, 'TL_EventAfterInsert'],
self::EVENT_AFTER_UPDATE=>[$this, 'TL_EventAfterUpdate'],
self::EVENT_AFTER_FIND=>[$this, 'TL_EventAfterFind'],
self::EVENT_AFTER_DELETE=>[$this, 'TL_EventAfterDelete'],
],
'TimelineClass'=>Timeline::class,
/*
Must have the following attributes
id integer primary key auto increment not null,
table varchar(64) not null,
table_id integer not null,
attribute varchar(64) not null,
datetime datetime not null
value text (can be null)
*/
];
$this->timelineConfig = array_replace_recursive($std, $config);
foreach($this->timelineConfig["events"]??[] as $trigger=>$handler)
$this->on($trigger, $handler);
}
public function __get($attr)
{
$cfg = &$this->timelineConfig;
if (in_array($attr, array_keys($cfg["attributes"])))
return $cfg["attributes"][$attr];
else
return parent::__get($attr);
}
public function __set($attr, $val)
{
$cfg = &$this->timelineConfig;
if (in_array($attr, array_keys($cfg["attributes"]))) {
$cfg["attributes"][$attr] = $val;
} else
parent::__set($attr, $val);
}
public function attributes()
{
return array_merge(parent::attributes(), $this->timelineConfig["attributes"]);
}
public function rules()
{
$temp = parent::rules();
$temp[] = [array_keys($this->timelineConfig["attributes"]), $this->timelineConfig["validationRule"]];
return $temp;
}
public function TL_EventAfterInsert($event)
{
$this->TL_save($event, true);
}
public function TL_EventAfterUpdate($event)
{
$this->TL_save($event, false);
}
private function TL_save($event, $insert)
{
$cfg = &$this->timelineConfig;
if ($cfg["onlyDirty"])
$cfg["_oldAttributes"] = $this->TL_attributesOnTime();
foreach($cfg["attributes"] as $attr=>$val) {
$a = [
'table'=>$cfg["table"],
'table_id'=>$this->id,
'attribute'=>$attr,
'datetime'=>$cfg["datetime"],
];
if ($insert)
$model=null;
else
$model = Timeline::find()->where($a)->one();
$isNew = empty($model); // this exact attribute does not exist on timeline already
if ($isNew)
$model = new $cfg["TimelineClass"]($a);
$model->value = $val;
if (!$cfg["onlyDirty"]
|| $cfg["onlyDirty"] && $model->value!=($cfg["_oldAttributes"][$attr]??\uniqid('force_true'))) {
$ok = $model->save();
if (!$ok) $this->addErrors($attr, $model->getErrorSummary());
}
}
sleep(5);
}
public function TL_EventAfterFind($event)
{
$cfg = &$this->timelineConfig;
$data = $this->TL_attributesOnTime();
foreach($data as $attr=>$val)
$cfg["attributes"][$attr] = $val;
$cfg["_oldAttributes"] = $cfg["attributes"];
}
private function TL_attributesOnTime()
{
$cfg = &$this->timelineConfig;
$timelineTable = $cfg["TimelineClass"]::tableName();
$sql = "SELECT t1.* FROM $timelineTable AS t1
LEFT JOIN (SELECT * FROM $timelineTable WHERE `table`=:table AND table_id=:table_id AND datetime<=:datetime) AS t2
ON (t1.table=t2.table and t1.table_id=t2.table_id and t1.datetime<t2.datetime AND t1.attribute=t2.attribute)
WHERE t2.id IS NULL AND t1.datetime<:datetime AND t1.table=:table AND t1.table_id=:table_id
";
$params = [
'table'=>$cfg["table"],
'table_id'=>$this->id,
':datetime'=>$cfg["datetime"],
];
$data = \Yii::$app->db->createCommand($sql,$params)->queryAll();
$data = ArrayHelper::map($data,'attribute','value');
return $data;
}
public function TL_EventAFterDelete($event)
{
$cfg = &$this->timelineConfig;
$cfg["TimelineClass"]::deleteAll([
'table'=>$cfg["table"],
'table_id'=>$event->sender->id
]);
}
}
Example of it's use:
<?php
namespace app\models;
class KeyTime extends Key
{
use \app\behaviors\TimelineTrait;
public function init()
{
parent::init();
$this->timelineInit([
'attributes'=>[
// default values for attributes
'keyid'=>'historic id', // this is existing attribute in Key model
'label'=>'mylabel', // label and color does not exist in Key model
'color'=>'red',
],
]);
}
}
The actionUpdate
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('update', [
'model' => $model,
]);
}
After many "flashes" with microtime(true) on, I found the reason it worked sometimes with sleep(1).
The answer is in TL_attributesOnTime. the last line in $sql was
WHERE t2.id IS NULL AND t1.datetime<:datetime AND t1.table=:table AND t1.table_id=:table_id
…but it should be…
WHERE t2.id IS NULL AND t1.datetime<=:datetime AND t1.table=:table AND t1.table_id=:table_id
Notice the < is changed to <= Otherwise when the record was saved in the same second as it was populated it would not be included.
Hope it can help somebody else.

Laravel Eloquent relationship object stale even though data correct in database

I'm using Laravel 5.7 and have a one-to-one relationship between 2 eloquent models.
I have this simple function that works well, and the correct values persist to the database:
public function saveMarketingOriginInfo(Contact $contact, $data) {
$contact->marketingOrigin()->create($data);
$this->makeOtherChangesByReference($contact->marketingOrigin);
$contact->marketingOrigin->save();
return $contact->marketingOrigin;
}
However, when writing a functional test for it, I noticed that the object that it returns is stale (doesn't have the correct values in its properties).
My tests only pass if I change the return statement to return \App\Models\MarketingOrigin::find($contact->id);.
(MarketingOrigin uses 'contact_id' as primary key.)
What am I doing wrong?
How can I return the same object that was just saved in the previous line ($contact->marketingOrigin->save();) without making a database read query (find())?
Update to respond to comments:
protected $table = 'marketing_origins';//MarketingOrigin class
protected $primaryKey = 'contact_id';
protected $guarded = [];
public function contact() {
return $this->belongsTo('App\Models\Contact');
}
The test:
public function testSaveMarketingOriginInfo() {
$helper = new \App\Helpers\SignupHelper();
$contactId = 92934;
$contact = factory(\App\Models\Contact::class)->create(['id' => $contactId]);
$leadMagnetType = 'LMT';
$audience = 'a60907';
$hiddenMktgFields = [
'audience' => $audience,
'leadMagnetType' => $leadMagnetType
];
$result = $helper->saveMarketingOriginInfo($contact, $hiddenMktgFields);
$this->assertEquals($result->contact_id, $contactId, 'contact_id did not get saved');
$this->assertEquals($result->campaignId, '6075626793661');
$this->assertEquals($result->leadMagnetType, $leadMagnetType);
$marketingOrigin = \App\Models\MarketingOrigin::findOrFail($contactId);
$this->assertEquals($marketingOrigin->adsetId, '6088011244061');
$this->assertEquals($marketingOrigin->audience, $audience);
$this->assertEquals($marketingOrigin, $result, 'This is the assertion that fails; some properties of the object are stale');
}
This is because the relationship has not been loaded yet.
You could try $contact->load('marketingOrigin'); to eager load the relationship:
public function saveMarketingOriginInfo(Contact $contact, $data) {
$contact->marketingOrigin()->create($data);
$this->makeOtherChangesByReference($contact->marketingOrigin);
$contact->marketingOrigin->save();
$contact->load('marketingOrigin'); // <---- eager load the relationship
return $contact->marketingOrigin;
}

Saving Model data to database

I have a Report Model which is like the following.
class Report extends Model
{
protected $table = 'reports';
protected $guarded = [];
public function leadsCollection()
{
return $this->hasMany('App\ReportModels\LeadsCollection');
}
}
A Report can have many LeadsCollection, its Model is the following.
class LeadsCollection extends Model
{
protected $table = 'leadsCollection';
protected $guarded = [];
private $xmlElement;
public function __construct($xmlElement = null, $attributes = array()) {
parent::__construct($attributes);
$this->xmlElement = $xmlElement;
}
public function report()
{
return $this->belongsTo('App\ReportModels\Report');
}
function asArray(){
$reportItem = array();
foreach($this->xmlElement->Leads->Lead as $lead) {
$dateIdentified = date("d/m/Y", strtotime($lead->Date));
$reportItem[] = array(
'LeadID' => (string)$lead->ID,
'Client' => (string)$lead->Client->Name,
'Category' => (string)$lead->Category,
'DateIdentified' => $dateIdentified,
'LeadName' => (string)$lead->Name,
'Owner' => (string)$lead->Owner->Name
);
}
return $reportItem;
}
}
Now I am trying to save some data to a database. So I get a list of all Leads by calling my LeadsCollection and passing it an XML list of Leads.
I then loop these Leads and add it to an array. At the same time however I need to save it to the database. This is what I have so far.
public function getForecastReportForLeads() {
$leads = new LeadsCollection(new \SimpleXMLElement(Helper::getCurrentLeads()));
$reportArray = array();
foreach ($leads->asArray() as $lead) {
$report = new Report();
$report->reportName = 'Lead Forecast';
if($report->save()) {
$leads->leadId = $lead['LeadID'];
$leads->leadCategory = $lead['Category'];
$leads->dateIdentified = $lead['DateIdentified'];
$leads->leadName = $lead['LeadName'];
$leads->owner = $lead['Owner'];
$leads->client = $lead['Client'];
$leads->report_id = $report->id;
$leads->save();
$reportItem = array(
'leadData' => $lead
);
$reportArray[] = $reportItem;
}
}
return $reportArray;
}
So I create the Report item, and within the database if I have 7 Leads I end up with 7 Report rows within my reports table, as it should be. However, when I save the Leads, I only end up with 1 row in my leadsCollection table, every other entry seems to be overridden. I think this is because I am not creating the Lead Object within the loop. However, I cant really create it within the loop because I need to loop whats returned when I first create it.
Not sure how clear I am but is there anything I can add to my Model so I can stop any overriding? Or do I need to do this another way?
Thanks
Either you get the variable inside the save method or initialize the new
$report = new Report($reportItem);
$report->save($report)
I'm having a similar Issue right, let me show my code. It would work for your case. My bug is that I'm updating and the plan_detail.id gets moved instead of creating a new one. But if you create would be fine:
public function store(Request $request)
{
$this->validate($request, [ 'title' => 'required',
'description' => 'required']);
$input = $request->all();
$plan_details = Plan_Detail::ofUser()->get();
$plan = new Plan($request->all());
DB::beginTransaction();
Auth::user()->plans()->save($plan);
try {
foreach ($plan_details as $k => $plan_detail)
Plan::find($plan['id'])->details()->save($plan_detail);
DB::commit();
} catch (Exception $e) {
Log::error("PGSQL plan detail " . $e->message());
DB::rollback();
session()->flash('message', 'Error al guardar el plan de entreno');
}
return redirect('plans');
}

Laravel profile edit

Alright , I have used this way to save the users info and It works perfect,
static public function memberSave($request) {
$signup = false;
$member = new Members();
$member->name = $request['name'];
$member->email = $request['email'];
$member->password = bcrypt($request['password']);
$member->save();
if (!empty($member->id)) {
$new_id = $member->id;
DB::insert("INSERT INTO roles VALUES ($new_id, 5613)");
$signup = true;
Session::flash('sm', 'Thank you! You have signed up successfully!');
}
return $signup;
}
but when making this for editing the profile(by user) It doesn't work
becuase I use new(); (making object)
I also didn't succeed to use find(); so I tried to use this
static public function saveProfile($id,$name,$email,$password) {
$sql = "UPDATE members SET name=?,email=?,password=? WHERE id=?";
$member = DB::select($sql, [$name,$email,$password,$id]);
but when I want to bcrypt the password in laravel doesnt work .
this is the code also in the second page
public function postProfile(ProfileValidation $request) {
if (Members::saveProfile($request['id'], $request['name'], $request['email'], $request['password'])) {
return redirect('');
}
}
I hope getting helped for editing the users profile by laravel , thanks.
Your Members class must extend Eloquent\Model for following this code to work.
class Members extends Model {
// optional
protected $table = 'members';
...
To find and update the member using email,
// find the single member
$member = Members::where('email', request['email'])->first();
// update the member
$member->name = $request['name'];
$member->password = $request['password'];
// now save the updated member
$member->save();
In order to to encrypt Password, Laravel provides Hash Facade,
// import this
use Hash;
...
// encrypt Password
$encrypted = Hash::make($request['password']);
...
if you want your user automatically hash the password at your model put:
public function setPasswordAttribute($value)
{
$this->attributes['password'] = Hash::make($value);
}
and you can directly check for the user if exist create new or update it:
public function saveMember($request)
{
$member = Member::findOrNew($request->email);
//All your input you want to save
$member->save();
}

laravel validate multiple models

I would like a best practice for this kind of problem
I have items, categories and category_item table for a many to many relationship
I have 2 models with these validations rules
class Category extends Basemodel {
public static $rules = array(
'name' => 'required|min:2|max:255'
);
....
class Item extends BaseModel {
public static $rules = array(
'title' => 'required|min:5|max:255',
'content' => 'required'
);
....
class Basemodel extends Eloquent{
public static function validate($data){
return Validator::make($data, static::$rules);
}
}
I don't know how to validate these 2 sets of rules from only one form with category, title and content fields.
For the moment I just have a validation for the item but I don't know what's the best to do:
create a new set of rules in my controller -> but it seems redundant
sequentially validate Item then category -> but I don't know how to handle validations errors, do I have to merges them? and how?
a 3rd solution I'm unaware of
here is my ItemsController#store method
/**
* Store a newly created item in storage.
*
* #return Redirect
*/
public function store()
{
$validation= Item::validate(Input::all());
if($validation->passes()){
$new_recipe = new Item();
$new_recipe->title = Input::get('title');
$new_recipe->content = Input::get('content');
$new_recipe->creator_id = Auth::user()->id;
$new_recipe->save();
return Redirect::route('home')
->with('message','your item has been added');
}
else{
return Redirect::route('items.create')->withErrors($validation)->withInput();
}
}
I am very interested on some clue about this subject
thanks
One way, as you pointed yourself, is to validate it sequentially:
/**
* Store a newly created item in storage.
*
* #return Redirect
*/
public function store()
{
$itemValidation = Item::validate(Input::all());
$categoryValidation = Category::validate(Input::all());
if($itemValidation->passes() and $categoryValidation->passes()){
$new_recipe = new Item();
$new_recipe->title = Input::get('title');
$new_recipe->content = Input::get('content');
$new_recipe->creator_id = Auth::user()->id;
$new_recipe->save();
return Redirect::route('home')
->with('message','your item has been added');
}
else{
return Redirect::route('items.create')
->with('errors', array_merge_recursive(
$itemValidation->messages()->toArray(),
$categoryValidation->messages()->toArray()
)
)
->withInput();
}
}
The other way would be to create something like an Item Repository (domain) to orchestrate your items and categories (models) and use a Validation Service (that you'll need to create too) to validate your forms.
Chris Fidao book, Implementing Laravel, explains that wonderfully.
You can also use this:
$validationMessages =
array_merge_recursive(
$itemValidation->messages()->toArray(),
$categoryValidation->messages()->toArray());
return Redirect::back()->withErrors($validationMessages)->withInput();
and call it in the same way.
$validateUser = Validator::make(Input::all(), User::$rules);
$validateRole = Validator::make(Input::all(), Role::$rules);
if ($validateUser->fails() OR $validateRole->fails()) :
$validationMessages = array_merge_recursive($validateUser->messages()->toArray(), $validateRole->messages()->toArray());
return Redirect::back()->withErrors($validationMessages)->withInput();
endif;

Resources