Laravel - delete whole collection - php

I have images for articles, and when I am updating article I would like to check if the images are the same, if not I would like to delete them but if it is possible I would like to delete the whole collection without another query, something like what I have in the code below $images->delete();.
This is my function:
$images = Media::where('article_id', $article->id)->get();
foreach($images as $image) {
$article_images[] = $image->original_name;
}
foreach($files as $file) {
$filePathArr = explode('/', $file);
$fileName = array_pop($filePathArr);
$originalFile = explode('-', $fileName);
$originalFileName = array_pop($originalFile);
$newFiles[] = $originalFileName;
}
if ($newFiles != $article_images){
$images->delete();
}

You just can't delete from database without making a query.
You will have to make new request like this:
Media::where('article_id', $article->id)->delete();
It's just one simple query, so there shouldn't be any performance penalty.
If we are talking about collection with 100's of items, you can optimize the query like this:
Media::whereIn('id', $images->pluck('id'))->delete();

If you have your Models linked, you can.
Class Exercise.php:
/**
* Exercise belongs to exactly one lecture
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function lecture()
{
return $this->belongsTo('\App\Lecture');
}
and Class Lecture.php:
/**
* Gets all the exercises asociated to this lecture
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function exercises()
{
return $this->hasMany('\App\Exercise');
}
Then you can in your controller simply do:
public function delete($id, DeleteLectureRequest $request)
{
$lecture = Lecture::findOrFail($id);
$lecture->exercises()->delete(); // easy
}
(Imagine that your Article == my Lecture, and your Media == my Exerises)
Of course, at first you have to set properly foreign keys in your DB and link your Models that way.

I'd delete a whole collection by the doing following after calling Model::get() or Model::all()
$posts = Post::all();
// Logic
// More logic
$posts->map->delete();

Related

Passing a variable to a query creates the error "Trying to get property 'name' of non-object" with Laravel

I am using Laravel 6. I am trying to create a validation system with a form to create a meeting.
When a user creates a meeting with participants that are already occupied in another meeting, a message should appear in the view with the name of the participants already occupied.
For some reason the function that should find the name of the participants doesn't work. I pass an id during a foreach loop but when I run the form appears the following message: "Trying to get property 'name' of non-object".
The strange thing is that the id passed to the function are OK, but if I write a number (for example "8") in place of $id in the query appears correctly the name "Chris" in the view.
The format of the column "id_participants" in the table meetings is the following "23;7;6".
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use DB;
use App\User;
class CheckParticipant implements Rule
{
protected $participants_occupied = array();
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
$participants = request('participants');
foreach($participants as $participant) {
$meetings = DB::table('meetings')
->where('is_active', '1')
->where('date', request('date_meeting'))
->where(function ($query) {
$query->where(function($sub_q) {
$sub_q->where('start_hour', '>=', request('start'))
->where('start_hour', '<', request('end'));
})
->orWhere(function($sub_q) {
$sub_q->where('start_hour', '<', request('start'))
->where('end_hour', '>=', request('end'));
})
->orWhere(function($sub_q) {
$sub_q->where('end_hour', '>', request('start'))
->where('end_hour', '<=', request('end'));
});
})
->where(function ($query) use($participant) {
$query->where('id_participants', $participant)
->orWhere('id_participants', 'like', '%;'.$participant)
->orWhere('id_participants', 'like', $participant.';%')
->orWhere('id_participants', 'like', '%;'.$participant.';%');
})
->get();
if(count($meetings) > 0) {
array_push($this->participants_occupied, $participant);
}
}
if(count($this->participants_occupied) > 0) {
return false;
} else {
return true;
}
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
for($i = 0; $i < count($this->participants_occupied); $i++) {
$this->participants_occupied[$i] = $this->getNameSurnameById($this->participants_occupied[$i]);
}
return 'The participants are already occupied at that time: ' . implode(',', $this->participants_occupied);
}
public function getNameSurnameById($id)
{
$users = User::all()->where('id', 18)->first(); //if I write a number in place of $id everything works
return $users->name;
}
}
I would like that this program works dynamically. I suppose there is something wrong in the query with the variable $id. Is someone able to help me?
UPDATE:
I solved the problem modifying the message function as follows:
public function message()
{
$arr_names = array(); //I created this array
for($i = 0; $i < count($this->participants_occupied); $i++) {
array_push($arr_names, $this->getNameSurnameById($this->participants_occupied[$i]));
}
return 'The following participants are already occupied at that time: ' . implode(', ', $arr_names);
}
I suppose that the problem consisted that I gave a string value (the name of the participant) to an array that had integers values (The id of the participant). I solved creating a new empty array and I pushed the names to the new array.
You may find it much easier to grab your ids based on some type of Laravel object, rather than an array. I suspect that the array has an incorrect value (not an id) at the index of $i during the loop at some point. And, as pointed out in the comments by #Cristóbal Ramos Merino, you are setting the variable to a potential string (the user name) at the same time as you are trying to pass the possible id through to the getNameSurnameById() method.
I would grab all of the ids passed from the form, do a DB query on User to see who is already occupied, and then just pull the name from the resulting collection.
Something like:
$allFormUsers = User::whereIn('id', $formIds)->get();
Then loop on this to get the names of those occupied:
$occupiedNames = [];
foreach($AllFormUsers->where('occupied', 1) as $u){
$occupiedNames[] = $u->name;
}
I have no idea how you are tracking the occupied - and so the above code is little more than pseudo code, but hopefully will give you an idea of how to do this without the array / concurrency. This also is a little less work on the Database, since you have one query, instead of looping on individual queries each time. You can even pull all users first so you have them stored, and then do a where('occupied', 1) against the collection if you like, as in the above loop. (Assuming that's how you track occupied)

replicate() method not found in laravel 5.2

I am trying to replicate table row and its relationship.
but I am getting error message that replicate() does not exist,
I have seen on stackoverflow that many have used replicate() without any issue, but i am getting this error
my controller code
public function copyshowtime($cinema_id,$show_date)
{
$date=new Carbon($show_date);
$current_show_date=$date->format('Y-m-d');
$next_show_date=$date->addDay()->format('Y-m-d');
$movieshowtime=Movies_showtimes::with('showdata')->where([['cinema_id','=',$cinema_id],['show_date','=',$current_show_date]])->get();
$newshowtime=$movieshowtime->replicate();
return $newshowtime;
}
Is there any namespace i have to use for using replicate() , I am unable to get solution from laravel website also.
help is appreciated.
You can use replicate() on a model but not on a collection.
By fetching your records using get() you are returning a collection.
If you are just expecting one record to be returned then replace get() with first() and then replicate() should exist as it will be returning an instance of the model rather than a collection:
public function copyshowtime($cinema_id,$show_date)
{
$date=new Carbon($show_date);
$current_show_date=$date->format('Y-m-d');
$next_show_date=$date->addDay()->format('Y-m-d');
$movieshowtime=Movies_showtimes::with('showdata')->where([['cinema_id','=',$cinema_id],['show_date','=',$current_show_date]])->first();
$newshowtime=$movieshowtime->replicate();
return $newshowtime;
}
You will also need to save() the $newshowtime.
This code worked perfectly for me
public function copyshowtime($cinema_id,$show_date)
{
$date=new Carbon($show_date);
$current_show_date=$date->format('Y-m-d');
$next_show_date=$date->addDay()->format('Y-m-d');
$movieshowtime=Movies_showtimes::with('showdata')->where([['cinema_id','=',$cinema_id],['show_date','=',$current_show_date]])->get();
foreach ($movieshowtime as $item)
{
$item->show_date=$next_show_date;
$item->show_id=NULL;
$newshowtime=$item->replicate();
$newshowtime->push();
foreach ($item->showdata as $sd)
{
$newshowdata = array(
'showdata_id' => NULL,
'show_id'=>$newshowtime->id,
'category_id'=>$sd->category_id,
'showdata_category'=>$sd->showdata_category,
'showdata_rate'=>$sd->showdata_rate
);
// print_r($newshowdata);
Movies_showdata::create($newshowdata);
}
}
return redirect()->back();
}
Any suggestions to improve this code will be appreciated.
This type of function would help to clone multiple records and add those records in the same table. I tried a similar code flow and worked.
/**
* Clone multiple records in same table
*
* #params int $cinemaId
* #params string $showDate
*
* #return bool $status
*
* #access public
*/
public function copyShowTime($cinemaId, $showDate)
{
$date = new Carbon($showDate);
$currentShowDate = $date->format('Y-m-d');
// Cloned & Create new records
$moviesShowTimeCollection = Movies_showtimes::with('showdata')->where([['cinema_id','=',$cinemaId],['show_date','=',$currentShowDate]])->get();
// Please check that Model name should change according to camelCases - Movies_showtimes to MoviesShowtimes
if(!$moviesShowTimeCollection->isEmpty()) {
$moviesShowTimeData = $moviesShowTimeCollection->toArray();
foreach ($moviesShowTimeData as $key => $value) {
$primaryKey = 'show_id'; // Needs to check the table primary key name
$primaryId = $value[$primaryKey];
$moviesShowTimeObj = Movies_showtimes::find($primaryId);
// below code can modify while cloaning
//$clonedMoviesShowTimeObj = $moviesShowTimeObj->replicate()->fill([
// 'column_name' => $updatedValue
//]);
$clonedMoviesShowTimeObj = $moviesShowTimeObj->replicate(); // just to clone a single record
$status = $clonedMoviesShowTimeObj->save();
}
}
}
Cheers!
You can easily replicate rows with new changes in that rows
$apcntReplicate = TrademarkApplicantMap::where('trademark_id', $trdIdForPostAssesment)->get();
foreach($apcntReplicate as $oldapnctdata)
{
$apcntreplicated = $oldapnctdata->replicate() ;
//update row data which will newly created by replicate
$apcntreplicated->row_name = $newrowdata;
//save new replicated row
$apcntreplicated->save();
}
Don't use toArray() then each element in the foreach loop will be an Eloquent object.

Arrays to database

I have a php file(users.php) which I save the user info. Every time I update or add employee I need to open the file in text editor and make some changes. This is the sample lists of employees in $users array.
$users = array(
'001' => array('id'=>'001', 'name'=>'first lastname', 'dept'=>'Sales', 'position'=>'Lead Team', 'rate'=>'800', 'dayoff'=>'SUN'),
'002' => array('id'=>'002', 'name'=>'sec lastname', 'dept'=>'Sales', 'position'=>'Designer', 'rate'=>'800', 'dayoff'=>'SUN'),
'003' => array('id'=>'003', 'name'=>'david, sample', 'dept'=>'IT', 'position'=>'', 'rate'=>'220.83', 'dayoff'=>'SUN'),
'004' => array('id'=>'004', 'name'=>'Test, Johny', 'dept'=>'', 'position'=>'', 'rate'=>'600', 'dayoff'=>''),
'005' => array('id'=>'005', 'name'=>'Name, Last', 'dept'=>'IT', 'position'=>'Programmer', 'rate'=>'500', 'dayoff'=>'SUN')
);
When I compute their salary I grab all the details of employee($users array) from that file. This is my sample function.
function compute(){
global $users;
include('users.php');
//import list of users;
foreach($the_log as $k=>$v){
if($users[$k]){
//codes here
//show user data with computed salary
}
}
}
How can I make a simple database(like csv file or text file) not MySql or any open source database, so that I can add, edit and delete a user(with just a click) easily whenever I want. What I want to achieve here is to be able to make $users array editable. Is it possible?
Edit: When I use or save data in .csv file, How can I edit or delete a specific user/row?
Just because it's fun, I created an example of how you could do it.
Bare in mind, it's not tested so it might have some bugs but it shows how you could do it.
Since you got so much finished code, I'll leave that up to you to find the bugs. ;-) (However, if you find bugs, leave them as a comment and I'll update the answer).
Important note: Just like #deceze mentioned in his comment, this works well if you know that there won't be any simultaneous "connections" (several people working with the files at the same time) and that you always "open, do stuff, save" and not "open, do stuff, open in a new browser tab, do stuff, save in first tab, save in second tab". Otherwise, your first changes will be overwritten with your second changes and so on...
Class to manage users:
class Users
{
/**
* #var string
*/
protected $path;
/**
* #var array
*/
protected $users = [];
/**
* #param string $path Path to the user file (must be writeable)
*/
public function __construct($path)
{
$this->path = $path;
if (!is_file($this->path)) {
// The file doesn't exist yet, let's create it
file_put_contents($this->path, json_encode([]));
} else {
// It does exist. Load it.
$this->users = json_decode(file_get_contents($this->path), true);
}
}
/**
* Get all users
*
* #return array
*/
public function all()
{
return $this->users;
}
/**
* Get a specific user
*
* #param string|integer $userId The array index for that user
* #return array|null Returns null if user doesn't exist
*/
public function get($userId)
{
if (!array_key_exists($userId, $this->users)) {
// The key doesn't exist, return null
return null;
}
return $this->users[$userId];
}
/**
* Update or add a user
*
* #param string|integer $userId The array index for that user
* #param array $data The user info
* #return boolean
*/
public function save($userId, array $data)
{
$this->users[$userId] = $data;
$written = file_put_contents($this->path, json_encode($this->users));
return $written !== false;
}
}
How you would use it:
// When you have created the instance, use the same instance
// through out your whole application (only do: new Users() once).
// You could do this with some factory class.
$users = new Users('/path/to/users.json');
// List all users
foreach($users->all() as $userId => $row) {
echo $row['first_name'];
// ...
}
// Get user
$user = $users->get('001');
// Change user
$user['first_name'] = "Magnus";
// Save user (this is both update and add)
$users->save('001', $user);

DataMapper ORM - How to get all of a model and each instance's related data

I do not understand how to do the following:
Lets say I have a product table, and a photo table. 1 Product has many photos. So in the product model I do:
var $has_many = array("category", "photo");
Now I want to get all products and relate each of their photos to them. How can I do this? Currently, in my controller I am going through each of the products and querying photos and passing a separate array that way. This CANNOT be ideal. I should be able to tie each photo to the specific product directly no?
Logically, this would work (but it doesnt?)
$product = new Product;
$products = $product->get_by_related_category('name', $where);
$photos = $product->photo->get();
See what I'm getting at? I would love to just pass that $products variable to my view, be able to foreach through it, and have an array of photos tied to each product object.
How can I accomplish this? Or is there a better way to do this?
Thanks!
With a "has many" relation you basically have two way to fetch the related information with SQL:
You can join the other table in like select products.*, photos.* from products left outer join photos on products.id = photos.product_id. This way you will have "duplicate" products data so you need to handle the results accordingly. Unfortunately include_related() doesn't support this directly, it would create the duplicated products with each of them have one related photo in your case.
You can run two queries, first fetching the products (select * from products where ...) and then fetching the photos with the id's of the selected products (select * from photos where product_id in (...)) and sort out what photos row should go what product. There's no built-in functionality for this in DMZ, but here's what I've coded up for a model base class (that extends the DataMapper class) that can be used like this:
$products = new Product;
$products = $products
->get_by_related_category('name', $where) // first get the parent objects
->load_related('photo'); // then load in the related ones inside them
foreach ($products as $product) {
// unique product instances as before
foreach ($product->photo as $photo) {
// and every product has a list of related photos
// for each product individualy
}
}
The method below will gather the id's of the parent objects, run one SQL query with the ids in a where_in() and sort the results out for the parent object's related field object (unfortunately its a little long and doesn't support many-to-many relations).
/**
* load_related
*
* Loads related has_many objects efficiently
*
* #param string $related_field_name the name of the relation
* #param callable $filter_function callback to place extra conditions on the related model query
*/
public function load_related($related_field_name, $filter_function = null) {
$related_properties = $this->_get_related_properties($related_field_name);
$related_models = new $related_properties['class'];
$join_field = $related_properties['join_self_as'].'_id';
$ids = array_unique(array_filter(array_merge(array('id' => $this->id), array_column($this->all, 'id')), 'intval'));
if (empty($ids)) {
return $this;
}
$related_models->where_in($join_field, $ids);
if (is_callable($filter_function)) {
call_user_func($filter_function, $related_models);
}
$related_models = $related_models->get();
$related_models = array_group_by($related_models, $join_field);
foreach ($this->all as $i => $row) {
$related_models_to_row = isset($related_models[$row->id]) ? $related_models[$row->id] : null;
if ($related_models_to_row) {
$this->all[$i]->{$related_field_name} = reset($related_models_to_row);
$this->all[$i]->{$related_field_name}->all = $related_models_to_row;
}
}
if (isset($related_models[$this->id])) {
$this->{$related_field_name} = $related_models[$this->id];
}
return $this;
}
// The two array helper functions used above from my_array_helper.php
function array_group_by($arr, $key, $index_by_col = false) {
$re = array();
foreach ($arr as $v) {
if (!isset($re[$v[$key]])) {
$re[$v[$key]] = array();
}
if ($index_by_col === false) {
$re[$v[$key]][] = $v;
} else {
$re[$v[$key]][$v[$index_by_col]] = $v;
}
}
return $re;
}
function array_column($arr, $key, $assoc = false) {
if (empty($arr)) {
return array();
}
$tmp = array();
foreach ($arr as $k => $v) {
if ($assoc === true) {
$tmp[$k] = $v[$key];
} elseif (is_string($assoc)) {
$tmp[$v[$assoc]] = $v[$key];
} else {
$tmp[] = $v[$key];
}
}
return $tmp;
}
I'm kinda exploring DM for a while now and I needed the same functionality. At first the load_related function from the other answer seemed to be the solution for this.
I did some further research though. I found this answer to another question and it made me thinking if there wasn't a way to autopopulate only some of the relations.
Well, there is !!
You can set this 'option' if you make a relation in a model.
//Instead of doing this:
var $has_many = array('user_picture');
//Do this
var $has_many = array(
'user_picture' => array(
'auto_populate' => TRUE,
),
);
Now the pictures will be available in a user object.
foreach ($u as $user) {
foreach ($user->user_picture as $picture) {
// Do your thing with the pictures
}
}
I found it on this page from the docs.
Enjoy!

How to control scoring and ordering in Zend_Search_Lucene, so one field is more important than the other?

From what I understand, after reading documentation (especially scoring part), every field I add has the same level of importance when scoring searched results. I have following code:
protected static $_indexPath = 'tmp/search/indexes/projects';
public static function createSearchIndex()
{
$_index = new Zend_Search_Lucene(APPLICATION_PATH . self::$_indexPath, true);
$_projects_stmt = self::getProjectsStatement();
$_count = 0;
while ($row = $_projects_stmt->fetch()) {
$doc = new Zend_Search_Lucene_Document();
$doc->addField(Zend_Search_Lucene_Field::text('name', $row['name']));
$doc->addField(Zend_Search_Lucene_Field::text('description', $row['description']));
$doc->addField(Zend_Search_Lucene_Field::unIndexed('projectId', $row['id']));
$_index->addDocument($doc);
}
$_index->optimize();
$_index->commit();
}
The code is simple - I'm generating index, based on data fetched from db, and save it in the specified location.
I was looking in many places, as my desired behavior is that name field is more important than description (let's say 75% and 25%). So when I will search for some phrase, and it will be found in description of the first document, and in name of the second document, then second document will in fact have 3 times bigger score, and will show up higher on my list.
Is there any way to control scoring/ordering in this way?
I found it out basing on this documentation page. You need to create new Similarity algorithm class, and overwrite lengthNorm method. I copied this method from Default class, added $multiplier variable, and set it's value when needed (for a column I want):
class Zend_Search_Lucene_Search_Similarity_Projects extends Zend_Search_Lucene_Search_Similarity_Default
{
/**
* #param string $fieldName
* #param integer $numTerms
* #return float
*/
public function lengthNorm($fieldName, $numTerms)
{
if ($numTerms == 0) {
return 1E10;
}
$multiplier = 1;
if($fieldName == 'name') {
$multiplier = 3;
}
return 1.0/sqrt($numTerms / $multiplier);
}
}
Then the only thing you need to do (edit of code from question) is set your new Similarity algorithm class as a default method just before indexing:
protected static $_indexPath = 'tmp/search/indexes/projects';
public static function createSearchIndex()
{
Zend_Search_Lucene_Search_Similarity::setDefault(new Zend_Search_Lucene_Search_Similarity_Projects());
$_index = new Zend_Search_Lucene(APPLICATION_PATH . self::$_indexPath, true);
$_projects_stmt = self::getProjectsStatement();
$_count = 0;
while ($row = $_projects_stmt->fetch()) {
$doc = new Zend_Search_Lucene_Document();
$doc->addField(Zend_Search_Lucene_Field::text('name', $row['name']));
$doc->addField(Zend_Search_Lucene_Field::text('description', $row['description']));
$doc->addField(Zend_Search_Lucene_Field::unIndexed('projectId', $row['id']));
$_index->addDocument($doc);
}
$_index->optimize();
$_index->commit();
}
I wanted to extra boost name field, but you can do it with anyone.

Categories