in my app each time I retrieve paginated results from the database I have to do somethig like this:
$posts = Post::latest()->with(['category','user'])->paginate($request->input('paginate', 6));
$posts =
[
'data' => $posts,
'pagination' => [
'total' => $posts->total(),
'per_page' =>$posts->perPage(),
'current_page' => $posts->currentPage(),
'last_page' => $posts->lastPage(),
'from' => $posts->firstItem(),
'to' => $posts->lastItem()
]
];
As you see I first retrieve the results from database and then I have to manually create the paginated data array, always doing the same seems bad and tedious to me honestly, I was wondering if there is a laravel magic method to automatically build the pagination payload array?
You can use Eloquent: API Resources "https://laravel.com/docs/7.x/eloquent-resources"
You should create a resource: php artisan make:resource PostCollection
and you should change method toArray($request) for something like this:
public function toArray($request)
{
return [
'data' => $this->collection,
'pagination' => [
'total' => $this->total(),
'per_page' =>$this->perPage(),
'current_page' => $this->currentPage(),
'last_page' => $this->lastPage(),
'from' => $this->firstItem(),
'to' => $this->lastItem()
]
];
}
Now when you want to paginate your Model you just do it like this:
//Get posts
$posts = Post::latest()->with(['category','user'])
->paginate($request->input('paginate', 6)));
//Use your resource (Collection)
$postsPaginate = new PostCollection($posts);
//Return your resource
return $porstsPaginate;
Related
I have implemented the php-opencloud/openstack library, and I use it to retrieve objects (object_store/v1).
I use the retrieve function that allows me to retrieve the content of an object,
but how to retrieve a part of an object using "range" defined in the request header with this library?
It seems possible, see the Api.php extract :
public function getObject(): array
{
return [
'method' => 'GET',
'path' => '{containerName}/{+name}',
'params' => [
'containerName' => $this->params->containerName(),
'name' => $this->params->objectName(),
'range' => $this->params->range(),
'ifMatch' => $this->params->ifMatch(),
'ifNoneMatch' => $this->params->ifNoneMatch(),
'ifModifiedSince' => $this->params->ifModifiedSince(),
'ifUnmodifiedSince' => $this->params->ifUnmodifiedSince(),
],
];
}
If I'd have the following table structure
ID name type
1 John Admin
2 Johan Admin
3 Sam User
4 Rick User
How would I use Laravels Eloquent with a groupBy query, but still grab each record, and also sum up per type how many records each type has?
Example result would be as follow
[
'type' => 'Admin',
'records' => 2
'data' => [
0 => [
'name' => 'John'
],
1 => [
'name' => 'Johan'
]
]
],
[
'type' => 'User',
'records' => 2
'data' => [
0 => [
'name' => 'Sam'
],
1 => [
'name' => 'Rick'
]
]
]
Since you are querying all the users you should be doing the grouping after the query instead of grouping in query like:
$users = User::all()->groupBy('type');
Then you can do:
foreach ($allUsers as $type => $users) {
$type; // 'Admin'
$users->count(); // Record Count
foreach ($users as $user) {
$user->name; //
}
}
If you want the data EXACTLY like your example then it would be:
User::all()
->groupBy('type')
->map(function($users, $type) {
return [
'type' => $type,
'records' => $users->count(),
'data' => $users->pluck('name')
];
})
->values();
I think the best way to achieve this is to have a table with types related through hasMany() to your data.
Then you can use standard Eloquent tools without restructuring the data manually:
$data = Type::with('users')->withCount('users')->get();
Also, you can use DB::select('select * from xxx group by yyy') to execute the query.
When I'm trying to select custom attributes from record
$data = Emotion::find()
->select('em_id, em_name, tbl_post.post_id, tbl_post.post_header')
->where(['em_id' => $id])
->joinWith('posts')
->one();
I've got an answer model, where related field have full set of table fields.
Trace message looks like this:
app\models\Emotion#1
( [yii\db\BaseActiveRecord:_attributes] =>
[ 'em_id' => 4
'em_name' => 'test_em']
[yii\db\BaseActiveRecord:_related] =>
[ 'posts' =>
[ 0 => app\models\Post#2 ( [yii\db\BaseActiveRecord:_attributes] =>
[ 'post_id' => 10
'post_header' => 'some header'
'post_date' => '2015-06-24 13:40:25',
'post_descr' => 'Rocco, continue the joke'
'post_content' => 'test content'...
So, maybe i did something wrong or this is a framework issue.
Found a workaround here: Yii2 GitHub issue
Which for my case looks like this:
$data = Emotion::find()
->select('em_id, em_name, tbl_post.post_id, tbl_post.post_header')
->where(['em_id' => $id])
->joinWith('posts')
->createCommand()
->queryAll();
And the output of this request will be an raw array.
Is there an another way to have an ActiveRecord object in a response data?
You should simply modify the relation query, e.g. :
$data = Emotion::find()
->select('em_id, em_name')
->where(['em_id' => $id])
->with([
'posts' => function($query) {
$query->select('post_id, post_header');
}
])->one();
And you don't need joinWith since you don't use posts in the main query.
I'm using Gazzle 6 with Laravel 5.1 and I'm having a strange behaviour returning my data from the APIs I'm using. This is my code:
$data = array(
'id' => '112233'
);
$from = \Carbon\Carbon::now()->subDays(1)->format('d/m/Y');
$to = \Carbon\Carbon::now()->subMonths(1)->format('d/m/Y');
$first_report = $this->client->post('https://my.service.com/reporting/execute/', [
'auth' => ['myemail#email.com', 'mypassword'],
'form_params' => ['data' => json_encode($data)]
]);
$second_report = $this->client->get('mysecondservice.com/reports', [
'query' => [
'account_auth_token' => '000011122223333444455556667778889999',
'start_date' => $to,
'end_date' => $from
]
]);
return array(
'first_report' => $first_report,
'second_report' => $second_report
);
If I return the data as an array like the previous one the first_report and second_report are empty. But if I return only for example
return $first_report;
or
return $second_report;
The data is returned correctly for each report but I don't know what is the issue there, because I have tried with: json_encode or even return $response()->json... but still not working.
Do you have any idea about what's going on?
I think you need to run the send() function on the object you have to get the response, then run getBody() on that to get the response object, then run getContents() on that to get the contents of the response as a string. So in total it would look like
$first_report = $this->client->post('https://my.service.com/reporting/execute/', [
'auth' => ['myemail#email.com', 'mypassword'],
'form_params' => ['data' => json_encode($data)]
])->send()->getBody()->getContents();
$second_report = $this->client->get('mysecondservice.com/reports', [
'query' => [
'account_auth_token' => '000011122223333444455556667778889999',
'start_date' => $to,
'end_date' => $from
]
])->send()->getBody()->getContents();
I find the Guzzle documentation does not match up with the actual methods I use to get the results. Not sure if it's out of date or if I'm doing it wrong, but this method works for me.
In my example code I am using the php client library, but it should be understood by anyone familiar with elasticsearch.
I'm using elasticsearch to create an index where each document contains an array of nGram indexed authors. Initially, the document will have a single author, but as time progresses, more authors will be appended to the array. Ideally, a search could be executed by an author's name, and if any of the authors in the array get matched, the document will be found.
I have been trying to use the documentation here for appending to the array and here for using the array type - but I have not had success getting this working.
First, I want to create an index for documents, with a title, array of authors, and an array of comments.
$client = new Client();
$params = [
'index' => 'document',
'body' => [
'settings' => [
// Simple settings for now, single shard
'number_of_shards' => 1,
'number_of_replicas' => 0,
'analysis' => [
'filter' => [
'shingle' => [
'type' => 'shingle'
]
],
'analyzer' => [
'my_ngram_analyzer' => [
'tokenizer' => 'my_ngram_tokenizer',
'filter' => 'lowercase',
]
],
// Allow searching for partial names with nGram
'tokenizer' => [
'my_ngram_tokenizer' => [
'type' => 'nGram',
'min_gram' => 1,
'max_gram' => 15,
'token_chars' => ['letter', 'digit']
]
]
]
],
'mappings' => [
'_default_' => [
'properties' => [
'document_id' => [
'type' => 'string',
'index' => 'not_analyzed',
],
// The name, email, or other info related to the person
'title' => [
'type' => 'string',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
'authors' => [
'type' => 'list',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
'comments' => [
'type' => 'list',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
]
],
]
]
];
// Create index `person` with ngram indexing
$client->indices()->create($params);
Off the get go, I can't even create the index due to this error:
{"error":"MapperParsingException[mapping [_default_]]; nested: MapperParsingException[No handler for type [list] declared on field [authors]]; ","status":400}
HAD this gone successfully though, I would plan to create an index, starting with empty arrays for authors and title, something like this:
$client = new Client();
$params = array();
$params['body'] = array('document_id' => 'id_here', 'title' => 'my_title', 'authors' => [], 'comments' => []);
$params['index'] = 'document';
$params['type'] = 'example_type';
$params['id'] = 'id_here';
$ret = $client->index($params);
return $ret;
This seems like it should work if I had the desired index to add this structure of information to, but what concerns me would be appending something to the array using update. For example,
$client = new Client();
$params = array();
//$params['body'] = array('person_id' => $person_id, 'emails' => [$email]);
$params['index'] = 'document';
$params['type'] = 'example_type';
$params['id'] = 'id_here';
$params['script'] = 'NO IDEA WHAT THIS SCRIPT SHOULD BE TO APPEND TO THE ARRAY';
$ret = $client->update($params);
return $ret;
}
I am not sure how I would go about actually appending a thing to the array and making sure it's indexed.
Finally, another thing that confuses me is how I could search based on any author in the array. Ideally I could do something like this:
But I'm not 100% whether it will work. Maybe there is something fundemental about elasticsearch that I am not understanding. I am completely new to so any resources that will get me to a point where these little details don't hang me up would be appreciated.
Also, any direct advice on how to use elasticsearch to solve these problems would be appreciated.
Sorry for the big wall of text, to recap, I am looking for advice on how to
Create an index that supports nGram analysis on all elements of an array
Updating that index to append to the array
Searching for the now-updated index.
Thanks for any help
EDIT: thanks to #astax, I am now able to create the index and append to the value as a string. HOWEVER, there are two problems with this:
the array is stored as a string value, so a script like
$params['script'] = 'ctx._source.authors += [\'hello\']';
actually appends a STRING with [] rather than an array containing a value.
the value inputted does not appear to be ngram analyzed, so a search like this:
$client = new Client();
$searchParams['index'] = 'document';
$searchParams['type'] = 'example_type';
$searchParams['body']['query']['match']['_all'] = 'hello';
$queryResponse = $client->search($searchParams);
print_r($queryResponse); // SUCCESS
will find the new value but a search like this:
$client = new Client();
$searchParams['index'] = 'document';
$searchParams['type'] = 'example_type';
$searchParams['body']['query']['match']['_all'] = 'hel';
$queryResponse = $client->search($searchParams);
print_r($queryResponse); // NO RESULTS
does not
There is no type "list" in elasticsearch. But you can use "string" field type and store array of values.
....
'comments' => [
'type' => 'string',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
....
And index a document this way:
....
$params['body'] = array(
'document_id' => 'id_here',
'title' => 'my_title',
'authors' => [],
'comments' => ['comment1', 'comment2']);
....
As for the script for apending an element to array, this answer may help you - Elasticsearch upserting and appending to array
However, do you really need to update the document? It might be easier to just reindex it as this is exactly what Elasticsearch does internally. It reads the "_source" property, does the required modification and reindexes it. BTW, this means that "_source" must be enabled and all properties of the document should be included into it.
You also may consider storing comments and authors (as I understand these are authors of comments, not the document authors) as child document in ES and using "has_child" filter.
I can't really give you specific solution, but strongly recommend installing Marvel plugin for ElasticSearch and use its "sense" tool to check how your overall process works step by step.
So check if your tokenizer is properly configured by running tests as described at http://www.elastic.co/guide/en/elasticsearch/reference/1.4/indices-analyze.html.
Then check if your update script is doing what you expect by retrieving the document by running GET /document/example_type/some_existing_id
The authors and comments should be arrays, but not strings.
Finally perform the search:
GET /document/_search
{
'query' : {
'match': { '_all': 'hel' }
}
}
If you're building the query yourself rather than getting it from the user, you may use query_string with placeholders:
GET /document/_search
{
'query' : {
'query_string': {
'fields': '_all',
'query': 'hel*'
}
}
}