I have this query
$user->orders->where('service_id',$request->service_id)->first();
I need to get the last element , How can I do that ?
there is no created_at column so I can't use latest()
Try latest() method once more and give them a parameter.
$user->orders()
->where('service_id', $request->service_id)
->latest('id')
->first();
Eloquent's latest() method:
/**
* Add an "order by" clause for a timestamp to the query.
*
* #param string $column
* #return $this
*/
public function latest($column = null)
{
if (is_null($column)) {
$column = $this->model->getCreatedAtColumn() ?? 'created_at';
}
$this->query->latest($column);
return $this;
}
This method internally uses created_at, but accepts specific column you want. I suggest that use this method rather than ->orderBy()->frist(). This gives you more readability and maintainability.
$user->orders()->where('service_id', $request->service_id)->orderBy('id', 'DESC')->first();
Related
I am attempting to sort an ArrayCollection by a specific field. The ArrayCollection is an array of courses. In the Course entity there is a method called isLive which returns a boolean.
I would like to sort this collection to have the "live" courses at the beginning of the array, so that's the courses that return true from a isLive call.
This is the code I have at present, but the first entry in the $sorted array is a non-live course.
$iterator = $this->courses->getIterator();
$iterator->uasort(function ($a, $b) {
if ($a->isLive() == $b->isLive()) {
return 0;
}
return ($a->isLive() < $b->isLive()) ? -1 : 1;
});
$sorted = new ArrayCollection(iterator_to_array($iterator));
It looks like a good use case for Doctrine Criteria. They allow to filter/sort ArrayCollections, either in memory if the collection is already loaded, either by adding a WHERE / ORDER BY SQL clause next time the collection will be loaded from the database. So that's pretty optimized!
Code should look like something like this, assuming you have a live field behind isLive():
$criteria = Criteria::create()
->orderBy(["live" => Criteria::DESC])
;
$sorted = $this->courses->matching($criteria);
For the entity, do this: add an OrderBy annotation to the property.
/**
* #OneToMany(targetEntity="Course")
* #OrderBy({"live": "ASC"})
*/
private $courses;
I got to a solution with the use of uasort and array_search as below:
/**
* #return ArrayCollection
*/
public function getCoursesSortedByLive(): ArrayCollection
{
$coursesIterator = $this->courses->getIterator();
$sortOrder = [true];
$coursesIterator->uasort(function ($a, $b) use ($sortOrder) {
return array_search($a->isLive(), $sortOrder) - array_search($b->isLive(), $sortOrder);
});
return new ArrayCollection(iterator_to_array($sitesIterator));
}
I want to get the number of rows of a certain table considering a time frame.
I'm using CakePHP 3.7.
Here you can see my code (from the controller class) :
public function nbOfDefense($dateIn, $dateOut){
if($dateIn!=null && $dateFin!=null){
$conditions = array('thesis.date_end BETWEEN ? and ?' => array($dateIn, $dateOut));
$query = $this->Thesis->find('all',
array('conditions'=>$conditions));
die(strval($query->count()));
return $query;
}else{
$query = $this->Thesis->find('all');
die(strval($query->count()));
return $query->count();
}
}
I'm testing my function through my browser using this URL :
http://localhost:8888/thesis/nbOfDefense/2003-02-01/2019-04-13
What I want my function to do, is to, get in parameters two dates :
If those two dates are not null, you get the number of rows that between the two dates considering a date stored in the table you're consulting.
If the dates are null, then you get the total number of rows of the table.
And return an int, which is the number of rows that are between those two dates.
I feel like the problem here is how I handle my condition, because counting the total number of rows works perfectly (the else part of the code).
The error I have right now with this code is the following :
Cannot convert value of type array to string
it's pointing to this line :
die(strval($query->count()));
I guess the count function returns an array (weird because it doesn't when I count all rows without conditions). I also tried this :
die(strval(sizeof($query->count())));
But I get the same error as before (cannot convert array to string)
I must be missing something but I don't know what...
Try:
use Cake\ORM\Query;
use Cake\Database\Expression\QueryExpression;
class MyController extend AppController
{
public function nbOfDefense($dateIn = null, $dateOut = null)
{
$query = $this->Thesis->find();
if ($dateIn && $dateOut) {
$query->where(function (QueryExpression $exp, Query $q) use ($dateIn, $dateOut) {
return $exp->between('date_end', $dateIn, $dateOut);
});
}
$count = $query->count();
$this->set(compact('query', 'count'));
}
// ..
}
and read:
https://book.cakephp.org/3.0/en/orm/query-builder.html
https://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html
I have a complex query that is not based on any specific model table that I want to paginate output for. However laravel's built in pagination relies on models and tables. How can I paginate a collection and have the output match up with laravel's built in pagination output format?
I keep this in an app\Core\Helpers class so that I can call them from anywhere as \App\Core\Helpers::makePaginatorForCollection($query_results). The most likely place to use this is the last line of a controller that deals with complex queries.
In app/Http/Controllers/simpleExampleController.php
/**
* simpleExampleController
**/
public function myWeirdData(Request $request){
$my_unsafe_sql = '...';//never do this!!
$result = DB::statement(DB::raw($my_unsafe_sql));
return \App\Core\Helpers::makePaginatorForCollection($result);
}
In app\Core\Helpers.php or anywhere you like that auto loads.
/**
* This will match laravel's built in Model::paginate()
* because it uses the same underlying code.
*
* #param \Illuminate\Support\Collection $collection
*
* #return \Illuminate\Pagination\LengthAwarePaginator
*/
public static function makePaginatorForCollection(\Illuminate\Support\Collection $collection){
$current_page = (request()->has('page')? request()->page : 1) -1;//off by 1 (make zero start)
$per_page = (request()->has('per_page')? request()->per_page : config('api.pagination.per_page')) *1;//make numeric
$page_data = $collection->slice($current_page * $per_page, $per_page)->all();
return new \Illuminate\Pagination\LengthAwarePaginator(array_values($page_data), count($collection), $per_page);
}
/**
* Copy and refactor makePaginatorForCollection()
* if collection building is too slow.
*
* #param $array
*
* #return \Illuminate\Pagination\LengthAwarePaginator
*/
public static function makePaginatorForArray($array){
$collection = collect($array);
return self::makePaginatorForCollection($collection);
}
I got 3 controller/tables Users / Adresses / AdressesUsers
This is my Adresses View function
/**
* View method
*
* #param string|null $id Adress id.
* #return \Cake\Network\Response|null
* #throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$this->viewBuilder()->layout('user');
$uid = $this->request->session()->read('Auth.User.id');
$adress = $this->Adresses->get($id, [
'contain' => ['Users']
]);
$counter = 0;
foreach($adress['users'] as $userid){
$users[$counter] = $userid['id'];
}
if(in_array($uid, $users)) {
$adress['dabei'] = 1;
} else {
$adress['dabei'] = 0;
}
$this->set('adress', $adress);
$this->set('_serialize', ['adress']);
}
I want all the users who are connected with the adress to see more detail information. How do I reach that goal? Tried to do it with a if clause, but didnĀ“t work, since I get an array of more then just one user (since multiple users can connect to one adress (adress of orders - not their own).
EDIT: I used an foreach loop to find out which users are in it and use an if/else in my view. But I think this is not best practice :/
In CakePHP 2 I could do something like this:
$name = $this->User->field('name', ['email' => 'user#example.com']);
In CakePHP 3 you have to do something like this to achieve the same thing:
$users = TableRegistry::get('Users');
$query = $users->find()
->select('name')
->where(['email' => 'user#example.com']);
$name = $query->isEmpty() ? null : $query->first()->name;
Is there a simpler way to perform these kinds of operations? I'm not very familiar with the new ORM.
Edit: I have added an example of a class which adds this behavior for Cake 3:
https://stackoverflow.com/a/42136955/851885
It's possible to add this functionality to any Table via a custom behavior.
Save as src/Model/Behavior/EnhancedFinderBehavior.php
<?php
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
/**
* EnhancedFinder behavior
*
* Behavior providing additional methods for retrieving data.
*/
class EnhancedFinderBehavior extends Behavior
{
/**
* Retrieve a single field value
*
* #param string $fieldName The name of the table field to retrieve.
* #param array $conditions An array of conditions for the find.
* #return mixed The value of the specified field from the first row of the result set.
*/
public function field($fieldName, array $conditions)
{
$field = $this->_table->getAlias() . '.' . $fieldName;
$query = $this->_table->find()->select($field)->where($conditions);
if ($query->isEmpty()) {
return null;
}
return $query->first()->{$fieldName};
}
}
Note: for CakePHP versions prior to 3.4, change the code to $this->_table->alias(), which was deprecated in favour of getAlias() in later versions.
Usage
Add the behavior to your class:
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
class UsersTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('EnhancedFinder');
}
}
Now you can use the finder like Cake 2:
$name = $this->User->field('name', ['id' => 1]);
This might be simpler than yours
$users = TableRegistry::get('Users');
$name = $users->get(1)->name;
Make sure that when you use the get function, the parameter should be a primary key in the table.
No, there is not in CakePHP 3.x.
If you want that method back implement it either in a behavior or as a finder using a trait and use it with your table objects.