I have the table item which each user can add and delete as many as they want, however I want that there is always atleast one entry in item.
There is this dirty way but there sure are better ways.
My solution:
$items = item::all();
foreach($items as item)
{
$a++;
}
if ($a > 1) {
delete_item();
return back();
} else {
return back();
}
There must be a cleaner way to do this.
You could use the count() method from QueryBuilder/Eloquent. It returns the number of items in your table:
$totalOfItems = Items::count();
if ($totalOfItems > 1) {
delete_item();
}
return back();
This way, if the total of items in your table is bigger than 1 your delete_item() operation will do its job, otherwise, it will just return back.
Hope that this helps you.
If you want this to be the default behavior, you can add event listener on deleting event and use count().
Check the documentation for specific details on which laravel version
Related
I was trying to get the remaining time for movie delivery in a rental system made with laravel.
I built the following query using eloquent and it returned the result I wanted:
$resting_time = DB::table('alquiler')
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->select('socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days"))
->orderBy('Days','asc')
->paginate(6);
but there is a problem when these rentals go over the delivery deadline it returns negative values, so I would like the query to return only the rentals that have the remaining days greater than zero and then paginate those results.
I create this statement and using map() filter only the positives that are returned in a collection but the problem is that I can't paginate them.
$resting_time = DB::table('alquiler')
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->select('socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days"))
->get()->map(function($alquiler){
return ($alquiler->Days >= 0) ? $alquiler : null;
});
$resting_time = $resting_time->filter()->sortBy('Days');
This is the returning collection:
But this type of collection cannot be paginated.
Any idea how to fix it, or maybe an easier way to do it? Sorry if something doesn't make sense, I'm just starting in laravel.
In second case its not working,because you work with:
\Illuminate\Support\Collection::class
in first case, you work with :
\Illuminate\Database\Eloquent\Collection::class
To make it work , you can try to do next thing:
take a
\Illuminate\Support\Collection::class
and return it paginated via
Illuminate\Pagination\Paginator::class
so the end result will look like this:
$resting_time = DB::table('alquiler')
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->select('socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days"))
->get()->map(function($alquiler){
return ($alquiler->Days >= 0) ? $alquiler : null;
});
$resting_time = $resting_time->filter()->sortBy('Days');
return new Illuminate\Pagination\Paginator($resting_time, 6);
However, i would recommend to prepare data from SQL side, neither doing all of the manipulations from collection perspective.
Most of the answers already provided will work, but will return a collection instead of a paginated resource. The trick is to use the tap helper method before map'ping, to return the same object you modified.
return tap(Alquiler::select(['socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days")])
->with('socio', 'pelicula')
->paginate(20))
->map(function ($model) {
return ($model->Days >= 0) ? $model : null;
});
or you can do this way too:
return Alquiler::select(['socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days")])
->with('socio', 'pelicula')
->paginate(20))
->map(function ($model) {
if($alquiler->Days >= 0) {
return $model;
}
});
I tried both methods and it didn't work at least the way I wanted, so I did a little more research and put this together:
public function getRestingTime(){
$resting_time = Alquiler::select(['socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days")])
->whereRaw('DATEDIFF(alq_fecha_hasta,NOW()) >= ?', [0])
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->orderBy('Days','asc')->paginate(6);
return $resting_time;
}
I hope it helps someone, thanks likewise to the people who responded cleared my mind a bit and gave me new things to try.
I'm overriding Mage_Catalog_Block_Product_List 's _getProductCollection by adding:
foreach ($this->_productCollection as $product) {
$product->setDistance(Mage::helper('myhelper')->getDistance($product));
}
Now I want the collection to be sorted by distance, I tried the following:
$this->_productCollection = Mage::helper('myhelper')->sortProductByDist($this->_productCollection);
The helper for sorting is like following (stolen from SO):
public function sortProductByDist($products) {
$sortedCollection = Mage::getSingleton('catalog/layer')
->getProductCollection()->addFieldToFilter('entity_id', 0);
$sortedCollection = $sortedCollection->clear();
$collectionItems = $products->getItems();
usort($collectionItems, array($this,'_sortItems'));
foreach ($collectionItems as $item) {
$sortedCollection->addItem($item);
}
return $sortedCollection;
}
protected function _sortItems($a, $b) {
$order = 'asc';
$al = strtolower($a->getDistance());
$bl = strtolower($b->getDistance());
if ($al == $bl) {
return 0;
}
if ($order == 'asc') {
return ($al < $bl) ? -1 : 1;
} else {
return ($al > $bl) ? -1 : 1;
}
}
The problem is the product collection is no longer paginated when this additional sort is applied.
Anyone knows how to fix this?
You are not doing it the right way, and there are no easy solutions. You need to use the database to do the sorting.
The _productCollection is not an array, it's an object that has references, the query at this point can still be updated, the pagination will be handled by the query to the database.
if you do a
Mage::log((string) $this->_productCollection->getSelect());
you will see the query in the logs
What you do is to load the products of the current page, add the distance on all products of the page, and create a new collection where you force your items in. So that collection's data is not coming from the database and only contains the elements of the current page.
Sorting using php is a bad idea, because if you have a lot of products it means you need to load them all from the database. That will be slow.
The solution
Calculate distance in the database directly by modifying the query.
You can edit the select query and do the distance calculation in the database
$this->_productCollection
->getSelect()
->columns("main.distance as distance")
Now you can add a sort on the product collection
$this->_productCollection->setOrder('distance');
The complicated part will be to write the equivalent of your getDistance method in mysql. In my example I assumed distance was in the database already.
Don't hesitate to print the query at various steps to understand what is going on.
Hi i am having troubles sorting this out as the title says i am trying to check if a record exist and return a boolean instead of the record value from the database so i can compare it with a filter form, i know this can be solved using nested if's but i'm trying to find a better way, this is my code so far
public function getSend(){
if($this->request->role==1){
$id= Cv::select('cv.user_id','cv.pub')
->where('cv.pub','==',$this->request->pub)
->get();
dd($id);
return view('gestione.'.$this->view_namespace.'.send.pagina');
}
my idea is something like this
->where('cv.pub','==',$this->request->pub)
this one works because "pub" stores a boolean record in my database alot of other records store strings for example
->where('cv.something','==',$this->request->something)
wouldnt work because "something" is a string and not a boolean so how do i turn "something" into a boolean based on if wheter exist or not
thanks in advance by the way i am using laravel 5.1
Try this,
->where('cv.something','==',!empty($this->request->something))
$something = !empty($this->request->something);
if($something) {
$exits = Cv::where('cv.'+$something, $something)->get();
if($exits) {
// Cv exits
}
}
You may try something like this:
$id = Cv::select('cv.user_id','cv.pub') // select is not required here
->where('cv.pub','==',$this->request->pub)
->exists();
Also, you may use it like:
if (Cv::where('cv.pub', $this->request->pub)->exists()) {
// The record exists, so do something...
}
This query will return a boolean true if exists, otherwise false. You may also make the query conditionally depending on the $this->request->pub in one go, for example:
$exists = Cv::when($this->request->pub, function($query) {
$query->where('cv.pub', $this->request->pub);
})->exists();
if ($exists) {
// ...
}
$result = Cv::select('cv.user_id','cv.pub')
->where('cv.pub','==',$this->request->pub);
if($request->something){
$result = $result->whereNotNull('cv.something'); // or != '', depending how you store it
}
This is mostly how filters works
If you want to return a boolean you can use count() :
$id= Cv::select('cv.user_id','cv.pub')
->where('cv.pub','==',$this->request->pub)
->count();
This will return the number of records, 0 if none, 1 if one is found. Obviously this will only work if 1 record is found, if 3 are found this solution wont work however I from what you have said it seems like that is all you will return.
Anything more than 0 means that a value exists.
Hey so i am trying to do an upvote downvote system and I am only using 1 table, the table columns are
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->integer('voter_id')->unsigned();
$table->foreign('voter_id')->references('id')->on('users');
$table->boolean('vote_type')->default(0);
$table->integer('commentable_id');
$table->string('commentable_type');
$table->string('unique_vote')->unique();
Basically I am trying to count how many votes the comment has but only where the vote_type is == 1 and also the reverse for downvotes where the value is 0
I was thinking about doing this with 2 different tables as it would make counting easier but I also dont want a large database.
i know of {{$comment->votes->count()}} but this returns the total rows regardless of the vote_type value and I am wondering if anyone has a solution or knows of a way while keeping the queries low.
Why you do it like this
public function showCart() {
$votes = Vote::where('vote_type',1)->count();
// do stuff and then return the count with the actual data & view
}
You cant chain like this
$votes = Vote::where('vote_type',1)->where('something',$something)->count();
if you want the result for the logged in user
$votes = Auth::user()->votes->where('vote_type',1)->count();
I hope you get the point here, you dont have to do the count in blade
Too late to answer this question, but in general groupBy on a collection can be a good option for this
$votesInGroups = Vote::all()->groupBy('vote_type');
if you want to refine the data:
$votesInGroups->map(function($group, $key){
// assign keys so that its meaningful
if($key == 1) {
return [
'upvotes' => $group->count();
];
}
// yada yada yada
});
I ended up just creating another relation and then enquing it with the main call. eg
comments class
public function upvotes()
{
return $this->morphMany('App\Models\General\Votes', 'commentable')->whereVoteType(1);
}
public function downvotes()
{
return $this->morphMany('App\Models\General\Votes', 'commentable')->whereVoteType(0);
}
--
public function index($category_slug, $subcategory_slug, $post_slug)
{
return view('pages.forum-post', [
'post' => Post::With('comments','comments.user','comments.votes','comments.upvotes','comments.downvotes','comments.user.UserProfile')->whereSlug($post_slug)->firstOrFail()
]);
}
--
blade
#if ($comment->upvotes)
{{$comment->upvotes->count()}}
#endif
#if ($comment->downvotes)
{{$comment->downvotes->count()}}
#endif
<hr />
I have populate a form of which every text field generated is based on the database result. I simply name every text field using the id. Now when the form is filled, I use controller to save it. But prior to insert the database, I loop the Request::input() to check every item whether such entry is exist or not. I just wonder if there is efficient way to check every item in the loop to insert it into db. Here is my code
public function store(Request $request, $id, $inid)
{
$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
$instruments = InstrumentReading::whereBetween('created_at', [$startOfDay, $endOfDay])
->where('iv_inid', '=', $inid)
->get();
foreach ($request->input() as $k => $v) {
$read = new InstrumentReading;
$read->iv_inid = $inid;
$read->iv_ipid = $k;
$read->iv_usid = Auth::user()->id;
$read->iv_reading = $v;
$read->save();
}
if ($instruments->count() > 0) {
//to filter the iv_ipid...
foreach($instruments as $instrument)
{
$instrument->iv_status = "VOID";
$instrument->save();
}
}
}
In words of efficent approach what you can do is to simple check / fetch ONLY all posible rows from the database, and the check in the loop if the row was already inserted. Also fetch only iv_ipid column, as we do not need all columns from the table to do our check. It will be faster to select only the column we need. You can use directly Fluent (Query Builder) over Eloquent to pull the data from database as it greatly increase the performance for a simple query like this.
public function store(Request $request, $id, $inid)
{
// Search only records with submitted iv_ipid, iv_inid and created today
$alreadyInserted = DB::table('instrument_readings')
->whereBetween('created_at', [
Carbon::now()->startOfDay(),
Carbon::now()->endOfDay()
])
// Get only records with submitted iv_ipid
->whereIn('iv_ipid', array_keys($request->input()))
// Get records with given iv_inid only
->where('iv_inid', $inid)
// For our check we need only one column,
// no need to select all of them, it will be fast
->select('iv_ipid')
// Get the records from DB
->lists('iv_ipid');
foreach ($request->input() as $k => $v) {
// Very simple check if iv_ipid is not in the array
// it does not exists in the database
if (!in_array($k, $alreadyInserted)) {
$read = new InstrumentReading;
$read->iv_inid = $inid;
$read->iv_ipid = $k;
$read->iv_usid = Auth::user()->id;
$read->iv_reading = $v;
$read->save();
} else {
//todo
}
}
This is the most efficent way suggested until now, because you fetch at once only the records you are interested in, not all records from today. Also you fetch only one column, the one that we need for out check. Eloquent ususlally give a lot of overheat on the perfomance, so in the suggested code I use directly Fluent, which will boost the speed this part of code is executed by ~ 20%.
Your mistake in the original code is that you are doing database call each time in a loop. When you need such a simple task as a check, never put database calls, queries etc. in a loop. It is an overkill. Instead select all needed data before the loop and then do your checks.
Now this is in case you only need to save new records to database. In case you want to manipulate each record in the loop, let's say you need to loop through each submited entry, get get the model or create it if it does not exists and then do something else with this model, the most efficent way then will be this one:
public function store(Request $request, $id, $inid)
{
foreach ($request->input() as $k => $v) {
// Here you search for match with given attributes
// If object in DB with this attributes exists
// It will be returned, otherwise new one will be constructed
// But yet not saved in DB
$model = InstrumentReading::firstOrNew([
'iv_inid' => $inid,
'iv_ipid' => $k,
'iv_usid' => Auth::user()->id
]);
// Check if it is existing DB row or a new instance
if (!$model->exists()) {
// If it is a new one set $v and save
$model->iv_reading = $v;
$model->save();
}
// Do something with the model here
.....
}
This way Laravel will check if model with the passed parameters already exist in database, and if so it will return it for you. If it does not exist, it will create new instance of it, so you can then set the $v and save to db. So you are good to go to do anything else with this model and you can be sure it exists in database after this point.
First approach (efficiency first)
Consider using a simple SQL INSERT IGNORE Query and use Fluent, i.e.:
Make a composite unique key containing:
iv_inid
iv_ipid
created_time, up to an hour granularity, this is important, because created_at might have a far greater granularity than your intended purpose, and might slow things down a bit.
Use DB, i.e.:
DB::query(
"INSERT IGNORE INTO $yourTable VALUES ( ... )"
);
Pros:
- Extremely fast, all the necessary checking is done on the DB Server
Cons:
- You cannot know which values triggered a duplicate value / unique key violation, as related errors are treated as warnings.
Second approach (convenience first)
Use firstOrFail, i.e.:
$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
// ... for
try {
InstrumentReading::where('iv_inid', $inid)
->where('iv_ipid', $k)
->whereBetween('created_at', [$startOfDay, $endOfDay])
->firstOrFail();
continue;
} catch (ModelNotFoundException $e) {
$instrumentReading = InstrumentReading::create([
// your values
]);
}
// ... endfor
Pros:
- Easy to implement
Cons:
- Somewhat slower than simple queries
Your code will send request to database every time you need to check the value. Instead, search all value of this day then check the value. This approach will send request to database only one time.
$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
// Search only this day
$instruments = InstrumentReading::whereBetween('created_at', [$startOfDay, $endOfDay])->get();
foreach($instruments as $instrument)
{
// Check the value
}