Merging laravel request params with array - php

I'm trying to get laravel-scout tntsearch with eloquent's query builder, this has proven difficult so my current approach is to first use tntsearch to get results, pluck ids from the results adn then add a wherIn clause with the plucked ids, to do this I have an additional class QueryFilters to sort and filter in a composable way.
Issue is I'm getting the following error:
Call to undefined method App\Filters\QueryFilters::merge()
Here is my code:
public function search(Request $request, QueryFilters $filters)
{
//Search with tntsearch
$posts = Post::search($request->input('search'))->get();
//Grab ids and prepare to merge with $filters
$postIds = $posts->pluck('id');
$toMerge = collect(['whereIn' => $postIds]);
$filters->merge($toMerge);
//Filter and sort results
$posts = Post::filter($filters)->with(['postcategory','author','favorites'])->paginate(10);
}
How QueryFilters works is iterating throught the request object looking for methods with the same name and returning each time an instance of the query builder.
<?php
namespace App\Filters;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class QueryFilters
{
protected $request;
protected $builder;
public function __construct(Request $request)
{
$this->request = $request;
}
public function title($term)
{
$lowerCaseTerm = strtolower($term);
return $this->builder->where('title', 'LIKE', "%$lowerCaseTerm%");
}
public function postCategory($term)
{
if($term == 0)
{
return $this->builder->whereHas('postcategory', function ($query) use ($term){
$query->where('id', '>', 0);
});
}
return $this->builder->whereHas('postcategory', function ($query) use ($term){
$query->where('id', $term);
});
}
public function sortBy($term)
{
$sortArray = explode(",", $term);
for($i = 0; $i < count($sortArray); $i++)
{
$sortBy = substr_replace($sortArray[$i], "", -1);
$sortChar = substr($sortArray[$i], -1);
$sortOrder = $sortChar == '+' ? 'ASC' : 'DESC';
$this->builder->orderBy($sortBy, $sortOrder);
}
return $this->builder;
}
public function whereIn($postIds)
{
return $this->builder->whereIn('id', $postIds);
}
public function apply(Builder $builder)
{
$this->builder = $builder;
foreach ($this->filters() as $name => $value)
{
//if method doesn't exists continue out of the loop
if ( ! method_exists($this, $name))
{
continue;
}
//method exists so check if it has a value payload so call the method with arguments
if (strlen($value))
{
$this->$name($value);
}
//it doesn't have a payload so call the method without arguments
else
{
$this->$name();
}
}
return $this->builder;
}
public function filters()
{
//returns associative array of request body key value pairs
return $this->request->all();
}
}
To know more about this class see this medium article:
https://medium.com/#mykeels/writing-clean-composable-eloquent-filters-edd242c82cc8
If I dd $filters I get this:
QueryFilters {#457
#request: Request {#43
#json: null
#convertedFiles: null
#userResolver: Closure($guard = null) {#421
class: "Illuminate\Auth\AuthServiceProvider"
this: AuthServiceProvider {#41 …}
use: {
$app: Application {#2 …}
}
file: "C:\xampp\htdocs\dog-media.es\vendor\laravel\framework\src\Illuminate\Auth\AuthServiceProvider.php"
line: "83 to 85"
}
#routeResolver: Closure() {#423
class: "Illuminate\Routing\Router"
this: Router {#26 …}
use: {
$route: Route {#220 …}
}
file: "C:\xampp\htdocs\dog-media.es\vendor\laravel\framework\src\Illuminate\Routing\Router.php"
line: "650 to 652"
}
+attributes: ParameterBag {#45
#parameters: []
}
+request: ParameterBag {#51
#parameters: array:3 [
"loaderId" => "1111"
"postCategory" => "0"
"sortBy" => "created_at+"
]
}
+query: ParameterBag {#51}
+server: ServerBag {#47
...
And my Filter scope:
<?php
namespace App\Filters;
use Illuminate\Database\Eloquent\Builder;
trait Filterable
{
public function scopeFilter($query, QueryFilters $filters)
{
return $filters->apply($query);
}
}

Related

API call in postman return message undefined index: data

{
"error": {
"title": "Internal Server Error",
"message": "Undefined index: data, in file D:\\Projects\\mika-api\\vendor\\illuminate\\support\\Collection.php at Line 1290"
}
}
i got that error message in postman when calling endpoint : localhost/mika-api/public/v1/transaksi-pindah-jurusan/
hope someone can tell me whats wrong with this, i'm already check it again and again but find nothing to fix it
This is my controller :
<?php
namespace App\Http\Controllers;
use App\UseCases\CollectionRequestModel;
use App\UseCases\TransaksiPindahJurusan\GetTransaksiPindahJurusanInteractor;
use Illuminate\Http\Request;
class TransaksiPindahJurusanController extends Controller
{
private $getTransaksiPindahJurusan;
public function __construct(GetTransaksiPindahJurusanInteractor $getTransaksiPindahJurusan)
{
$this->getTransaksiPindahJurusan = $getTransaksiPindahJurusan;
}
public function getCollection(Request $request)
{
$result = $this->getTransaksiPindahJurusan->getCollection(new CollectionRequestModel($request->all()));
$total = $result['total'];
unset($result['total']);
$pagination = $this->getPagination($request, $total, $request->except(['limit', 'offset']));
return $this->jsonResponse200Collection($result['data'], $total, $pagination);
}
}
This is my interactor
<?php
namespace App\UseCases\TransaksiPindahJurusan;
use App\Repositories\PMBP\TransaksiPindahJurusanRepository;
use App\UseCases\CollectionRequestModel;
class GetTransaksiPindahJurusanInteractor implements GetTransaksiPindahJurusanInterface
{
private $repoTransaksiPindahJurusan;
public function __construct(TransaksiPindahJurusanRepository $repoTransaksiPindahJurusan)
{
$this->repoTransaksiPindahJurusan = $repoTransaksiPindahJurusan;
}
public function getCollection(CollectionRequestModel $request)
{
$result = $this->repoTransaksiPindahJurusan->findManyBy(
['*'],
$request->getParameters(),
$request->getOrderBy(),
$request->getGroupBy(),
$request->getLimit(),
$request->getOffset()
);
return $result;
}
}
This is my interface
<?php
namespace App\UseCases\TransaksiPindahJurusan;
use App\UseCases\CollectionRequestModel;
interface GetTransaksiPindahJurusanInterface
{
public function getCollection(CollectionRequestModel $request);
}
this is my repository
<?php
namespace App\Repositories\PMBP;
use App\Repositories\BaseRepository;
use Illuminate\Support\Facades\DB;
class TransaksiPindahJurusanRepository extends BaseRepository
{
protected $tableName = 'pmbp.transaksi_pindah_jurusan';
public function findManyBy($selects = [], $parameters = [], $orders = [], $groups = [], $limit = null, $offset = null)
{
$query = $this->find($selects, $parameters, $orders, $groups);
$totalData = $query->count();
$this->addlimit($query, $limit, $offset);
$result = $query->get();
$result['total'] = $totalData;
return $result;
}
public function find($selects = [], $parameters = [], $orders = [], $groups = [])
{
$query = DB::table($this->tableName)->select($selects);
$this->addParameters($query, $this->tableName, $parameters);
$this->addOrders($query, $this->tableName, $orders);
$this->addGroups($query, $this->tableName, $groups);
return $query;
}
public function findOneBy($selects = [], $parameters = [])
{
return $this->find($selects, $parameters)->first();
}
}
Looks like $result['data'] is undefined. Before return do dd($result); and see if there is 'data' present.

how to use collection on same model laravel resources

We are currently developing a feature in codotto.com where a user can comment on an IT meetup. Each comment can have an answer to it. We are only allowing for one-level deep answers, so something like:
- Comment 1
- Answer to comment 1
- Answer to comment 1
- Comment 2
- Answer to comment 2
- Answer to comment 2
I have the following database structure:
// meetup_messages
- id
- user_id
- meetup_id
- meetup_message_id (nullable) -> comments that do not answer will have this set to nullable
In my model I define the answers as a HasMany relationship:
class MeetupMessage extends Model
{
// ...
public function answers(): HasMany
{
return $this->hasMany(self::class, 'meetup_message_id');
}
}
Then on my controller, I get all comments that do not have answers:
public function index(
IndexMeetupMessageRequest $request,
Meetup $meetup,
MeetupMessageService $meetupMessageService
): MeetupMessageCollection
{
$meetupMessages = MeetupMessage::with([
'user',
// 'answers' => function ($query) {
// $query->limit(3);
// }
'answers'
])
->whereNull('meetup_message_id')
->whereMeetupId($meetup->id)
->paginate();
return new MeetupMessageCollection($meetupMessages);
}
Then on my MeetupMessageCollection:
class MeetupMessageCollection extends ResourceCollection
{
public function toArray($request)
{
return parent::toArray($request);
}
}
Then on my MeetupMessageResource:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Collection;
class MeetupMessageResource extends JsonResource
{
public function toArray($request)
{
return collect([
// 'answers' => new MeetupMessageCollection($this->whenLoaded('answers')),
])
->when(
is_null($this->meetup_message_id) && $this->relationLoaded('answers'),
function (Collection $collection) {
$collection->put('answers', MeetupMessageCollection::collection($this->answers));
}
);
}
}
But I get the following error: Call to undefined method App\\Models\\Meetup\\MeetupMessage::mapInto(). How can I still use MeetupMessageCollection by passing the answers to it?
As #matialauriti pointed out, you cant use resource collections inside collections in Laravel
class MeetupMessageResource extends JsonResource
{
public function toArray()
{
return [
'answers' => new MeetupMessageCollction($this->answers) // ❌ You can't do this
]
}
}
My solution was to pull my resource formation to a private method and re-use it if answers is present:
class MeetupMessageResource extends JsonResource
{
public function toArray($request)
{
return collect($this->messageToArray($this->resource))
->when($this->relationLoaded('user'), function (Collection $collection) {
$collection->put('user', $this->userToArray($this->user));
})
// ✅ Now I don't need to use Resources inside my API Resource class
->when(
is_null($this->meetup_message_id) && $this->relationLoaded('answers'),
function (Collection $collection) {
$answers = $this
->answers
->map(function (MeetupMessage $answer) {
return array_merge(
$this->messageToArray($answer),
['user' => $this->userToArray($answer->user)]
);
});
$collection->put('answers', $answers);
}
);
}
private function messageToArray(MeetupMessage $meetupMessage): array
{
return [
'id' => $meetupMessage->id,
'message' => Purify::config(MeetupMessageService::CONFIG_PURIFY)->clean($meetupMessage->message),
'answersCount' => $this->whenCounted('answers'),
'createdAt' => $meetupMessage->created_at,
];
}
}

How resolve BadMethodCallException, Call to undefined method Illuminate\Database\Query\Builder::filter()

I'm trying to filter the results from eloquent query, but appear the next error BadMethodCallException. According to me, I'm doing everything right.
I'm using Laravel 5.4
The error details:
BadMethodCallException
Call to undefined method Illuminate\Database\Query\Builder::filter()
in Builder.php line 2445
at Builder->__call('filter', array(null))
in Builder.php line 1254
at Builder->__call('filter', array(null))
in web.php line 459
at Router->{closure}()
in Route.php line 189
at Route->runCallable()
in Route.php line 163
at Route->run()
in Router.php line 572
I have the next code:
public function index(SearchRequest $searchRequest, ConfigurationFilter $filters)
{
$filtered_configurations = Configuration::whereTrash(false)->with(['customs.properties', 'properties'])->filter($filters);
$types = $this->getConfigurationTypes();
$authors = $this->getAuthors();
return view('configuration.assistant.index', [
'configurations' => $filtered_configurations->paginate(10),
'authors' => $authors,
'types' => $types,
]);
}
Where SearchRequest is:
class SearchRequest extends FormRequest {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return Auth::user()->author != null;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}
Where ConfigurationFilter is:
class ConfigurationFilter extends ModelFilter
{
public function name($value)
{
return $this->builder->where('name', 'like', "%{$value}%");
}
public function author($id)
{
return $this->builder->whereIn('user_id', explode(',', $id));
}
public function type($type)
{
return $this->builder->whereIn('category_id', explode(',', $type));
}
public function status($status)
{
return $this->builder->whereEnabled($status == 'enabled');
}
}
Where ModelFilter is:
class ModelFilter extends QueryFilter implements Filter
{
public function id($id)
{
return $this->builder->whereIn('id', explode(',', $id));
}
public function from($date)
{
return $this->builder->where('created_at', '>=', $date);
}
public function to($date)
{
return $this->builder->where('created_at', '<=', $date);
}
public function enabled($status)
{
return $this->builder->whereEnabled($status === 'true');
}
public function trash($status)
{
return $this->builder->whereTrash($status === 'true');
}
public function take($limit = 100)
{
return $this->builder->take($limit);
}
}
Where Filter is:
interface Filter {
public function id($id);
public function from($date);
public function to($date);
public function enabled($status);
public function trash($status);
public function take($limit = 100);
}
What will I be missing?
Thanks in advance
I already resolved it, I added the next function to model:
public function scopeFilter($query, QueryFilter $filters)
{
return $filters->apply($query);
}

laravel trying to get property of non-object from a model inside a controller

So im trying to check if an authenticated user is already following the user, however im getting this error.
Trying to get property of non-object
if ($followers->user_id == auth()->id()){
return true;
}
8 "Trying to get property of non-object"
"/Applications/MAMP/htdocs/elipost/app/MyFollow.php" 34
I'm not sure if im using this method below properly.
$query->where('user_id', auth()->user()->id);
UserController.php
public function getProfile($user)
{
$users = User::with(['posts.likes' => function($query) {
$query->whereNull('deleted_at');
$query->where('user_id', auth()->user()->id);
}, 'follow','follow.follower'])
->with(['followers' => function($query) {
$query->with('follow.followedByMe');
$query->where('user_id', auth()->user()->id);
}])->where('name','=', $user)->get();
$user = $users->map(function(User $myuser){
$myuser['followedByMe'] = $myuser->followers->count() == 0 ? false : true;
// $myuser['followedByMe'] = $myuser->followers->count() == 0 ? false : true;
dd($owl = $myuser['followedByMe']);
return $myuser;
});
User.php
public function follow()
{
return $this->hasMany('App\MyFollow');
}
MyFollow(model)
<?php
namespace App;
use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Overtrue\LaravelFollow\Traits\CanFollow;
use Overtrue\LaravelFollow\Traits\CanBeFollowed;
class MyFollow extends Model
{
use SoftDeletes, CanFollow, CanBeFollowed;
protected $fillable = [
'user_id',
'followable_id'
];
public $timestamps = false;
protected $table = 'followables';
public function follower()
{
return $this->belongsTo('App\User', 'followable_id');
}
public function followedByMe()
{
foreach($this->follower as $followers) {
if ($followers->user_id == auth()->id()){
return true;
}
}
return false;
}
}
followedByMe is incorrectly looping a single record. Try the following changes:
public function followedByMe()
{
return $this->follower->getKey() === auth()->id();
}
Since follower is a belongsTo relationship, it will only return at most one record, not a collection.
The map function is also incorrectly using array access on a model. You cannot use ['followedByMe'] on an object, to access a property you need to use -> notation as in $myuser->followedByMe. The following shows how to use the map function:
$user = $users->map(function(User $myuser){
return ['followedByMe' => $myuser->followers->count() == 0];
});
Which would return an array similar to:
[
['followedByMe' => true],
['followedByMe' => false],
['followedByMe' => true],
]

Use eloquent relation on models returning by another method

I have a Category model which has belongsToMany relation with Product model via a pivot table called product_to_category
I can get all products in a Category with $category->products() and then apply a Filter scope to it to filter the result with parameters given in Request like this:
When I send this request :
http://site.dev/category/205?product&available&brand
I apply the parameters like this:
Category::find($id)->products()->filter($request)
The problem is when I want to get all product in a category and its children. The existing products relation gives me products in only given category.
I tried to modify the products() method in Category model as this:
public function products()
{
return DB::table('oc_product')
->join('oc_product_to_category', 'oc_product_to_category.category_id', '=', 'oc_product_to_category.category_id')
->join('oc_category_path', 'oc_category_path.category_id', '=', 'oc_category.category_id')
->whereIn('oc_product_to_category.category_id', $this->children(true));
}
But when I this code :
Category::find($id)->products()->filter($request)
I get this exception error:
(1/1) BadMethodCallException
Call to undefined method Illuminate\Database\Query\Builder::filter()
I know that filter scope is defined in Model class, but how can I apply that filter scope to QueryBuilder which is returned by modified products method?
Here are my classes :
Product model:
class Product extends Model {
public function scopeFilter( $request, QueryFilter $filters ) {
return $filters->apply( $request );
}
public function categories() {
return $this->belongsToMany( Category::class, 'product_to_category', 'product_id', 'category_id' );
}
}
Category model:
class Category extends Model
{
public function scopeFilter($query, QueryFilter $filters)
{
return $filters->apply($query);
}
public function children($id_only = false)
{
$ids = $this->hasMany(CategoryPath::class, 'path_id', 'category_id')
->join('category', 'category.category_id', '=', 'category_path.category_id')
->where('category.status', 1)
->pluck('category.category_id');
if ($id_only)
return $ids;
return self::find($ids);
}
public function parent()
{
$parent = DB::Select("SELECT cp.path_id AS category_id FROM category_path cp LEFT JOIN category_description cd1
ON (cp.path_id = cd1.category_id AND cp.category_id != cp.path_id)
WHERE cd1.language_id = '2' AND cp.category_id = " . $this->category_id);
return $parent;
}
public function products()
{
return $this->belongsToMany(Product::class, 'product_to_category', 'category_id', 'product_id');
}
}
QueryFilter class:
abstract class QueryFilter {
protected $request;
protected $builder;
public function __construct( Request $request ) {
$this->request = $request;
}
public function filters() {
return $this->request->all();
}
public function apply( Builder $builder ) {
$this->builder = $builder;
foreach ( $this->filters() as $name => $value) {
if (method_exists($this, $name)) {
call_user_func_array([$this, $name], array_filter([$value]));
}
}
return $this->builder;
}
}
CategoryFilter class:
class CategoryFilters extends QueryFilter
{
public function id($id)
{
return $this->builder->where('category_id', $id);
}
public function procons()
{
return $this->builder->with('pros', 'cons');
}
public function available()
{
return $this->builder->where('quantity', '>', 0);
}
public function optionValues()
{
return $this->builder->with('optionValues');
}
public function description()
{
return $this->builder->with('description');
}
public function images()
{
return $this->builder->with('images');
}
public function order($order)
{
$params = explode(',', $order);
$order = isset($params[0]) ? $params[0] : null;
$way = isset($params[1]) && strtolower($params[1]) == 'desc' ? $params[1] : 'asc';
if ($order) {
return $this->builder->orderBy($order, $way);
}
return $this->builder;
}
}

Categories