Pass parent id to relationship factory? - php

I have a factory of posts and a factory of posts_images, a post can have many images, I did create a seeder for posts that look like this.
$posts = factory(App\Models\Post::class, 100)->create()
->each(function($post) {
$post->images()->saveMany(factory(App\Models\PostsImage::class, 3)->make());
});
I want to create 100 posts and that each post have 3 images, this kind of work, the problem is when the image is created
I want the image to be created from a base_64 string and saved in certain directory but I need the id of the post so I can create the folder where it will be created.
$factory->define(App\Models\PostsImage::class, function (Faker $faker) {
//this does not get me the id
$id = factory(\App\Models\Post::class)->make()->id;
$name = time();
$b64_image = \Config::get('constants.seed_image');
$data = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $b64_image));
if(!file_exists(public_path('images/eventos/'.$id.'/'))) {
mkdir(public_path('images/noticias/'.$id.'/'));
}
file_put_contents(public_path('images/noticias/'.$id.'/'.$name.'.jpg'), $data);
return [
//
'image' => $name,
'order' => $id + 1
];
});
The only line that seems not to be working is
$id = factory(\App\Models\Post::class)->make()->id;
I did try using create instead of make, but this create more rows in the post table, and I don't want that.
Is there a way to pass the post id to the images factory?

The best option will be to create the directory in the posts seeder, since you have the access to the $post object when a Post is created. Try something like this:
$posts = factory(App\Models\Post::class, 100)->create()
->each(function($post) {
//id is available on the $post object created
$id = $post->id;
$name = time();
$b64_image = \Config::get('constants.seed_image');
$data = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $b64_image));
if(!file_exists(public_path('images/eventos/'.$id.'/'))) {
mkdir(public_path('images/noticias/'.$id.'/'));
}
file_put_contents(public_path('images/noticias/'.$id.'/'.$name.'.jpg'), $data);
$post->images()->saveMany(factory(App\Models\PostsImage::class, 3)->make());
});

What I do usually in this situation is to get the last record of the created parent object.
$factory->define(App\Models\PostsImage::class, function (Faker $faker) {
// get last record of Post
$id = \App\Models\Post::orderBy('id', 'desc')->first()->id;
$name = time();
$b64_image = \Config::get('constants.seed_image');
$data = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $b64_image));
if(!file_exists(public_path('images/eventos/'.$id.'/'))) {
mkdir(public_path('images/noticias/'.$id.'/'));
}
file_put_contents(public_path('images/noticias/'.$id.'/'.$name.'.jpg'), $data);
return [
//
'image' => $name,
'order' => $id + 1
];
});

Related

Laravel: Updating hasMany/BelongTo relation

I have a master table jobs with multiple location in separate table job_location. Now I am not able to update/delete, if extra rows found from job_location. Now why I am saying DELETE is because sync() did this, but it's related to many-to-many relation. I am new to laravel, just trying to get eloquent approach to achieve this, otherwise deleting all rows and inserting can be done easily OR updating each and delete remaining is also an option but I wonder Laravel has something for this.
In every request I get multiple job locations(with unchanged/changed city,phone_number,address) which is creating trouble.
Some codeshots:
Model: [Job.php]
class Jobs extends Model
{
protected $fillable = [
'job_id_pk', 'job_name','salary'
];
public function joblocation() {
return $this->hasMany('\App\JobLocation', 'job_id_fk', 'job_id_pk');
}
}
Model:[JobLocation.php]
class JobLocation extends Model
{
protected $fillable = [
'jobl_id_pk', 'job_id_fk','city', 'address', 'phone_number'
];
public function job() {
return $this->belongsTo('\App\Jobs', 'job_id_fk', 'job_id_pk');
}
}
Controller:[JobController.php]
function jobDetail() {
if($params['jid']) {
// update
$obj = \App\Jobs::find($params['jid']);
$obj->job_name = $params['name'];
$obj->salary = $params['salary'];
$obj->save();
} else {
// create new
$data = array(
'job_name' => $params['name'],
'salary' => $params['salary'],
);
$obj = \App\Jobs::create($data);
}
// don't bother how this $objDetail has associative array data, it is processed so
foreach ($params['jobLocations'] AS $key => $objDetail) {
$jobLoc = new \App\JobLocation;
$jobLoc->city = $objDetail['city'];
$jobLoc->phone_number = $objDetail['phone_number'];
$jobLoc->address = $objDetail['address'];
$jobLoc->job()->associate($obj);
$obj->jobLoc()->save($jobLoc);
}
}
In this approach I am able to save all job locations, but I am using same function to update also. Please tell how I can update jobLocations if present. I am ok to loose previous entries, but it would be good if previous gets updated and new get entered OR if we have extra entries they get deleted. I know sounds weird but still guide me a way.
Yea, you cannot use the same function, do this
$jobs = \App\Jobs::find($params['jid']);
foreach ($params['jobLocations'] as $key => $objDetail) {
$joblocation = $jobs->joblocation->where('jobl_id_pk', $objDetail['some_id'])->first();
//here update you job location
$joblocation->save();
}
Something like this:
Controller:[JobController]
public function jobDetail() {
if( !empty($params['jid']) ) {
// update
$job = \App\Jobs::find($params['jid']);
$job->job_name = $params['name'];
$job->salary = $params['salary'];
$job->save();
} else {
// create new
$data = array(
'job_name' => $params['name'],
'salary' => $params['salary'],
);
$job = \App\Jobs::create($data);
}
$locationDetails = !empty($params['jobLocations']) ? $params['jobLocations'] : [];
$jobLocations = array_map(function($location) use($job) {
$location = array_merge($location, [ 'job_id_fk' => $job->job_id_pk ]);
return \App\JobLocation::firstOrNew($location);
}, $locationDetails);
$job->jobLocations()->saveMany($jobLocations);
}

Laravel and a While Loop

I'm new to Laravel and at the moment I have a piece of code in a Controller which without the while loop it works, it retrieves my query from the database.
public function dash($id, Request $request) {
$user = JWTAuth::parseToken()->authenticate();
$postdata = $request->except('token');
$q = DB::select('SELECT * FROM maps WHERE user_id = :id', ['id' => $id]);
if($q->num_rows > 0){
$check = true;
$maps = array();
while($row = mysqli_fetch_array($q)) {
$product = array(
'auth' => 1,
'id' => $row['id'],
'url' => $row['url'],
'locationData' => json_decode($row['locationData']),
'userData' => json_decode($row['userData']),
'visible' => $row['visible'],
'thedate' => $row['thedate']
);
array_push($maps, $product);
}
} else {
$check = false;
}
return response()->json($maps);
}
I am trying to loop through the returned data from $q and use json_decode on 2 key/val pairs but I can't even get this done right.
Don't use mysqli to iterate over the results (Laravel doesn't use mysqli). Results coming back from Laravel's query builder are Traversable, so you can simply use a foreach loop:
$q = DB::select('...');
foreach($q as $row) {
// ...
}
Each $row is going to be an object and not an array:
$product = array(
'auth' => 1,
'id' => $row->id,
'url' => $row->url,
'locationData' => json_decode($row->locationData),
'userData' => json_decode($row->userData),
'visible' => $row->visible,
'thedate' => $row->thedate
);
You're not using $postdata in that function so remove it.
Do not use mysqli in Laravel. Use models and/or the DB query functionality built in.
You're passing the wrong thing to mysqli_fetch_array. It's always returning a non-false value and that's why the loop never ends.
Why are you looping over the row data? Just return the query results-- they're already an array. If you want things like 'locationData' and 'userData' to be decoded JSON then use a model with methods to do this stuff for you. Remember, with MVC you should always put anything data related into models.
So a better way to do this is with Laravel models and relationships:
// put this with the rest of your models
// User.php
class User extends Model
{
function maps ()
{
return $this->hasMany ('App\Map');
}
}
// Maps.php
class Map extends Model
{
// you're not using this right now, but in case your view needs to get
// this stuff you can use these functions
function getLocationData ()
{
return json_decode ($this->locationData);
}
function getUserData ()
{
return json_decode ($this->userData);
}
}
// now in your controller:
public function dash ($id, Request $request) {
// $user should now be an instance of the User model
$user = JWTAuth::parseToken()->authenticate();
// don't use raw SQL if at all possible
//$q = DB::select('SELECT * FROM maps WHERE user_id = :id', ['id' => $id]);
// notice that User has a relationship to Maps defined!
// and it's a has-many relationship so maps() returns an array
// of Map models
$maps = $user->maps ();
return response()->json($maps);
}
You can loop over $q using a foreach:
foreach ($q as $row) {
// Do work here
}
See the Laravel docs for more information.

Get the Last Inserted Id Using Laravel Eloquent

I'm currently using the below code to insert data in a table:
<?php
public function saveDetailsCompany()
{
$post = Input::All();
$data = new Company;
$data->nombre = $post['name'];
$data->direccion = $post['address'];
$data->telefono = $post['phone'];
$data->email = $post['email'];
$data->giro = $post['type'];
$data->fecha_registro = date("Y-m-d H:i:s");
$data->fecha_modificacion = date("Y-m-d H:i:s");
if ($data->save()) {
return Response::json(array('success' => true), 200);
}
}
I want to return the last ID inserted but I don't know how to get it.
Kind regards!
After save, $data->id should be the last id inserted.
$data->save();
$data->id;
Can be used like this.
return Response::json(array('success' => true, 'last_insert_id' => $data->id), 200);
For updated laravel version try this
return response()->json(array('success' => true, 'last_insert_id' => $data->id), 200);
xdazz is right in this case, but for the benefit of future visitors who might be using DB::statement or DB::insert, there is another way:
DB::getPdo()->lastInsertId();
If the table has an auto-incrementing id, use the insertGetId method to insert a record and then retrieve the ID:
$id = DB::table('users')->insertGetId([
'email' => 'john#example.com',
'votes' => 0
]);
Refer: https://laravel.com/docs/5.1/queries#inserts
For anyone who also likes how Jeffrey Way uses Model::create() in his Laracasts 5 tutorials, where he just sends the Request straight into the database without explicitly setting each field in the controller, and using the model's $fillable for mass assignment (very important, for anyone new and using this way): I read a lot of people using insertGetId() but unfortunately this does not respect the $fillable whitelist so you'll get errors with it trying to insert _token and anything that isn't a field in the database, end up setting things you want to filter, etc. That bummed me out, because I want to use mass assignment and overall write less code when possible. Fortunately Eloquent's create method just wraps the save method (what #xdazz cited above), so you can still pull the last created ID...
public function store() {
$input = Request::all();
$id = Company::create($input)->id;
return redirect('company/'.$id);
}
**** For Laravel ****
Firstly create an object, Then set attributes value for that object, Then save the object record, and then get the last inserted id. such as
$user = new User();
$user->name = 'John';
$user->save();
// Now Getting The Last inserted id
$insertedId = $user->id;
echo $insertedId ;
There are several ways to get the last inserted id. All are based on what method do you used when inserting. In your case you can get last Id like the following:
$data->save();
$data->id;
For others who need to know how can they get last inserted id if they use other insert methods here is how:
Using create() method
$book = Book::create(['name'=>'Laravel Warrior']);
$lastId = $book->id;
Using insertGetId()
$id = DB::table('books')->insertGetId( ['name' => 'Laravel warrior'] ); $lastId = $id;
Using lastInsertId() method
$lastId = DB::getPdo()->lastInsertId();
Reference https://easycodesolution.com/2020/08/22/last-inserted-id-in-laravel/
In laravel 5: you can do this:
use App\Http\Requests\UserStoreRequest;
class UserController extends Controller {
private $user;
public function __construct( User $user )
{
$this->user = $user;
}
public function store( UserStoreRequest $request )
{
$user= $this->user->create([
'name' => $request['name'],
'email' => $request['email'],
'password' => Hash::make($request['password'])
]);
$lastInsertedId= $user->id;
}
}
This worked for me in laravel 4.2
$id = User::insertGetId([
'username' => Input::get('username'),
'password' => Hash::make('password'),
'active' => 0
]);
Here's an example:
public static function saveTutorial(){
$data = Input::all();
$Tut = new Tutorial;
$Tut->title = $data['title'];
$Tut->tutorial = $data['tutorial'];
$Tut->save();
$LastInsertId = $Tut->id;
return Response::json(array('success' => true,'last_id'=>$LastInsertId), 200);
}
Use insertGetId to insert and get inserted id at the same time
From doc
If the table has an auto-incrementing id, use the insertGetId method
to insert a record and then retrieve the ID:
By Model
$id = Model::insertGetId(["name"=>"Niklesh","email"=>"myemail#gmail.com"]);
By DB
$id = DB::table('users')->insertGetId(["name"=>"Niklesh","email"=>"myemail#gmail.com"]);
For more details : https://laravel.com/docs/5.5/queries#inserts
For insert()
Example:
$data1 = array(
'company_id' => $company_id,
'branch_id' => $branch_id
);
$insert_id = CreditVoucher::insert($data1);
$id = DB::getPdo()->lastInsertId();
dd($id);
Here is how we can get last inserted id in Laravel 4
public function store()
{
$input = Input::all();
$validation = Validator::make($input, user::$rules);
if ($validation->passes())
{
$user= $this->user->create(array(
'name' => Input::get('name'),
'email' => Input::get('email'),
'password' => Hash::make(Input::get('password')),
));
$lastInsertedId= $user->id; //get last inserted record's user id value
$userId= array('user_id'=>$lastInsertedId); //put this value equal to datatable column name where it will be saved
$user->update($userId); //update newly created record by storing the value of last inserted id
return Redirect::route('users.index');
}
return Redirect::route('users.create')->withInput()->withErrors($validation)->with('message', 'There were validation errors.');
}
Although this question is a bit dated. My quick and dirty solution would look like this:
$last_entry = Model::latest()->first();
But I guess it's vulnerable to race conditions on highly frequented databases.
After saving model, the initialized instance has the id:
$report = new Report();
$report->user_id = $request->user_id;
$report->patient_id = $request->patient_id;
$report->diseases_id = $request->modality;
$isReportCreated = $report->save();
return $report->id; // this will return the saved report id
You can easily fetch last inserted record Id
$user = User::create($userData);
$lastId = $user->value('id');
It's an awesome trick to fetch Id from the last inserted record in the DB.
After
$data->save()
$data->id will give you the inserted id,
Note: If your autoincrement column name is sno then you should use
$data->sno and not $data->id
After saving a record in database, you can access id by $data->id
return Response::json(['success' => true, 'last_insert_id' => $data->id], 200)
In Laravel 5.2 i would make it as clean as possible:
public function saveContact(Request $request, Contact $contact)
{
$create = $contact->create($request->all());
return response()->json($create->id, 201);
}
For Laravel, If you insert a new record and call $data->save() this function executes an INSERT query and returns the primary key value (i.e. id by default).
You can use following code:
if($data->save()) {
return Response::json(array('status' => 1, 'primary_id'=>$data->id), 200);
}
You can do this:
$result=app('db')->insert("INSERT INTO table...");
$lastInsertId=app('db')->getPdo()->lastInsertId();
$objPost = new Post;
$objPost->title = 'Title';
$objPost->description = 'Description';
$objPost->save();
$recId = $objPost->id; // If Id in table column name if other then id then user the other column name
return Response::json(['success' => true,'id' => $recId], 200);
For get last inserted id in database
You can use
$data = new YourModelName;
$data->name = 'Some Value';
$data->email = 'abc#mail.com';
$data->save();
$lastInsertedId = $data->id;
here $lastInsertedId will gives you last inserted auto increment id.
The shortest way is probably a call of the refresh() on the model:
public function create(array $data): MyModel
{
$myModel = new MyModel($dataArray);
$myModel->saveOrFail();
return $myModel->refresh();
}
You can also try like this:
public function storeAndLastInrestedId() {
$data = new ModelName();
$data->title = $request->title;
$data->save();
$last_insert_id = $data->id;
return $last_insert_id;
}
Here it is how it worked for me, family_id is the primary key with auto increment I am using Laravel7
public function store(Request $request){
$family = new Family();
$family->family_name = $request->get('FamilyName');
$family->family_no = $request->get('FamilyNo');
$family->save();
//family_id is the primary key and auto increment
return redirect('/family/detail/' . $family->family_id);
}
Also in the Model Family file which extends Model, should have the increment set to true otherwise the above $family-->family_id will return empty
public $incrementing = true;
Using Eloquent Model
$user = new Report();
$user->email= 'johndoe#example.com';
$user->save();
$lastId = $user->id;
Using Query Builder
$lastId = DB::table('reports')->insertGetId(['email' => 'johndoe#example.com']);
After Saving $data->save(). all data is pushed inside $data. As this is an object and the current row is just saved recently inside $data. so last insertId will be found inside $data->id.
Response code will be:
return Response::json(array('success' => true, 'last_insert_id' => $data->id), 200);
You can get last inserted id with same object you call save method;
$data->save();
$inserted_id = $data->id;
So you can simply write:
if ($data->save()) {
return Response::json(array('success' => true,'inserted_id'=>$data->id), 200);
}
public function store( UserStoreRequest $request ) {
$input = $request->all();
$user = User::create($input);
$userId=$user->id
}
Using Eloquent Model
use App\Company;
public function saveDetailsCompany(Request $request)
{
$createcompany=Company::create(['nombre'=>$request->input('name'),'direccion'=>$request->input('address'),'telefono'=>$request->input('phone'),'email'=>$request->input('emaile'),'giro'=>$request->input('type')]);
// Last Inserted Row ID
echo $createcompany->id;
}
Using Query Builder
$createcompany=DB::table('company')->create(['nombre'=>$request->input('name'),'direccion'=>$request->input('address'),'telefono'=>$request->input('phone'),'email'=>$request->input('emaile'),'giro'=>$request->input('type')]);
echo $createcompany->id;
For more methods to get Last Inserted Row id in Laravel : http://phpnotebook.com/95-laravel/127-3-methods-to-get-last-inserted-row-id-in-laravel

How do you remove specific fields from all entities of a record set in Lithium?

I am using MySQL as the database connection adapter for all my models. I have a downloads model and controller with an index function that renders either an HTML table or a CSV file depending on the type passed from the request. I also have a CSV media type to handle an array of data, which is working as expected (outputs array keys as headers then array values for each row of data).
I wish to do the same find query but then remove ID fields from the record set if a CSV file is going to be rendered. You'll notice that the download ID is being fetched even though it is not in the fields array, so simply changing the fields array based on the request type will not work.
I have tried the following in the index action of my downloads controller:
<?php
namespace app\controllers;
use app\models\Downloads;
class DownloadsController extends \lithium\action\Controller {
public function index() {
// Dynamic conditions
$conditions = array(...);
$downloads = Downloads::find('all', array(
'fields' => array('user_id', 'Surveys.name'),
'conditions' => $conditions,
'with' => 'Surveys',
'order' => array('created' => 'desc')
));
if ($this->request->params['type'] == 'csv') {
$downloads->each(function ($download) {
// THIS DOES NOT WORK
unset($download->id, $download->user_id);
// I HAVE TRIED THIS HERE AND THE ID FIELDS STILL EXIST
// var_dump($download->data());
// exit;
return $download;
});
return $this->render(array('csv' => $downloads->to('array')));
}
return compact('downloads');
}
}
?>
I thought there was an __unset() magic method on the entity object that would be called when you call the standard PHP unset() function on an entity's field.
It would be great if there was a $recordSet->removeField('field') function, but I can not find one.
Any help would be greatly appreciated.
Perhaps you should do $downloads = $downloads->to('array');, iterate the array with a for loop, remove those fields from each row, then return that array. If you have to do this same thing for a lot of actions, you could setup a custom Media handler that could alter the data without needing logic for it in your controller.
Take a look at this example in the Lithium Media class unit test.
You can also avoid having much logic for it in your controller at all through the use of a custom handler. This example also auto-generates a header row from the keys in your data.
In config/bootstrap/media.php:
Media::type('csv', 'application/csv', array(
'encode' => function($data, $handler, $response) {
$request = $handler['request'];
$privateKeys = null;
if ($request->privateKeys) {
$privateKeys = array_fill_keys($request->privateKeys, true);
}
// assuming your csv data is the first key in
// the template data and the first row keys names
// can be used as headers
$data = current($data);
$row = (array) current($data);
if ($privateKeys) {
$row = array_diff_key($row, $privateKeys);
}
$headers = array_keys($row);
ob_start();
$out = fopen('php://output', 'w');
fputcsv($out, $headers);
foreach ($data as $record) {
if (!is_array($record)) {
$record = (array) $record;
}
if ($privateKeys) {
$record = array_diff_key($record, $privateKeys);
}
fputcsv($out, $record);
}
fclose($out);
return ob_get_clean();
}
));
Your controller:
<?php
namespace app\controllers;
use app\models\Downloads;
class DownloadsController extends \lithium\action\Controller {
public function index() {
$this->request->privateKeys = array('id', 'user_id');
// Dynamic conditions
$conditions = array(...);
$downloads = Downloads::find('all', array(
'fields' => array('user_id', 'Surveys.name'),
'conditions' => $conditions,
'with' => 'Surveys',
'order' => array('created' => 'desc')
));
return compact('downloads');
}
}
?>
Why not then just dynamically set your $fields array?
public function index() {
$type = $this->request->params['type'];
//Exclude `user_id` if request type is CSV
$fields = $type == 'csv' ? array('Surveys.name') : array('user_id', 'Surveys.name');
$conditions = array(...);
$with = array('Surveys');
$order = array('created' => 'desc');
$downloads = Downloads::find('all', compact('conditions', 'fields', 'with', 'order'));
//Return different render type if CSV
return $type == 'csv' ? $this->render(array('csv' => $downloads->data())) : compact('downloads');
}
You can see in this example how I send the array for your CSV handler, otherwise it's the $downloads RecordSet object that goes to the view.

Using pagination with a custom model method in CakePHP

I'm setting up pagination to display a list of images belonging to the user in their account. This is what I have in my controller:
class UsersController extends AppController {
public $paginate = array(
'limit' => 5,
'order' => array(
'Image.uploaded' => 'DESC'
)
);
// ...
public function images() {
$this->set('title_for_layout', 'Your images');
$albums = $this->Album->find('all', array(
'conditions' => array('Album.user_id' => $this->Auth->user('id'))
));
$this->set('albums', $albums);
// Grab the users images
$options['userID'] = $this->Auth->user('id');
$images = $this->paginate('Image');
$this->set('images', $images);
}
// ...
}
It works, but before I implemented this pagination I had a custom method in my Image model to grab the users images. Here it is:
public function getImages($options) {
$params = array('conditions' => array());
// Specific user
if (!empty($options['userID'])) {
array_push($params['conditions'], array('Image.user_id' => $options['userID']));
}
// Specific album
if (!empty($options['albumHash'])) {
array_push($params['conditions'], array('Album.hash' => $options['albumHash']));
}
// Order of images
$params['order'] = 'Image.uploaded DESC';
if (!empty($options['order'])) {
$params['order'] = $options['order'];
}
return $this->find('all', $params);
}
Is there a way I can use this getImages() method instead of the default paginate()? The closest thing I can find in the documentation is "Custom Query Pagination" but I don't want to write my own queries, I just want to use the getImages() method. Hopefully I can do that.
Cheers.
Yes.
//controller
$opts['userID'] = $this->Auth->user('id');
$opts['paginate'] = true;
$paginateOpts = $this->Image->getImages($opts);
$this->paginate = $paginateOpts;
$images = $this->paginate('Image');
//model
if(!empty($opts['paginate'])) {
return $params;
} else {
return $this->find('all', $params);
}
Explanation:
Basically, you just add another parameter (I usually just call it "paginate"), and if it's true in the model, instead of passing back the results of the find, you pass back your dynamically created parameters - which you then use to do the paginate in the controller.
This lets you continue to keep all your model/database logic within the model, and just utilize the controller to do the pagination after the model builds all the complicated parameters based on the options you send it.

Categories