I use laravel 8 & have 3 table:
Products, ProductPrice & ProductsPublisher:
this is my Products model for this relationship:
public function lastPrice(){
return $this->hasMany(ProductPrice::class)->where('status','active')->orderBy('created_at','DESC')->distinct('publisher_id');
}
and this is my productsPrice model for publisher relationship:
public function getPublisher(){
return $this->belongsTo(ProductsPublisher::class,'publisher_id');
}
now, i want to use laravel resource for my api, i wrote products resource:
public function toArray($request)
{
return [
'id' => $this->id,
'price' => lastPrice::make($this->lastPrice),
'status' => $this->status,
'slug' => $this->slug,
'title' => $this->title,
'description' => $this->description,
'txt' => $this->txt,
'lang' => $this->lang,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
but in lastPrice resource, when i wrote like this:
return [
'id' => $this->id,
'main_price' => $this->main_price
];
it give me this error:
Property [id] does not exist on this collection instance.
when i use this code:
return parent::toArray($request);
get response but because i need to use another relationship in my lastPirce for publishers, i cant use that code and should return separately my data.
What i should to do?
thanks
Edit 1:
this is my Controller Code:
$products = Product::where('id',$id)->where('slug',$slug)->where('status','confirm')->first();
if(!$products){
return $this->sendError('Post does not exist.');
}else{
return $this->sendResponse(new \App\Http\Resources\Products\Products($products), 'Posts fetched.');
}
and this is sendResponse & sendError:
public function sendResponse($result, $message)
{
$response = [
'success' => true,
'data' => $result,
'message' => $message,
];
return response()->json($response, 200);
}
public function sendError($error, $errorMessages = [], $code = 404)
{
$response = [
'success' => false,
'message' => $error,
];
if(!empty($errorMessages)){
$response['data'] = $errorMessages;
}
return response()->json($response, $code);
}
thanks.
Edit 2:
i change my lastPrice Resource toArray function to this and my problem solved, but i think this isn't a clean way, any better idea?
$old_data = parent::toArray($request);
$co = 0;
$new_data = [];
foreach ($old_data as $index){
$publisher_data = Cache::remember('publisher'.$index['publisher_id'], env('CACHE_TIME_LONG') , function () use ($index) {
return ProductsPublisher::where('id' , $index['publisher_id'])->first();
});
$new_data[$co]['main_prices'] = $index['main_price'];
$new_data[$co]['off_prices'] = $index['off_price'];
$new_data[$co]['publisher'] = SinglePublisher::make($publisher_data);
$new_data[$co]['created_at'] = $index['created_at'];
$co++;
}
return $new_data;
Related
I have an API resources in my code,
what i wanted to do is to pass a new data to be return in Resource Collection
Controller:
public function index(Request $request)
{
// #TODO implement
$books = Book::with('authors')->paginate(5);
$pass = 1;
return BookResource::collection($books, $pass, 200);
}
my Resources:
public function toArray($request)
{
return [
// #TODO implement
'id' => $this->id,
'isbn' => $this->isbn,
'title' => $this->title,
'description' => $this->description,
'published_year' => $this->published_year,
'authors' => AuthorResource::collection($this->authors),
'review' => BookReviewResource::collection($this->reviews),
'pass' => $this->pass
];
}
I want the $pass, can be returned in Resources,
how do i achieve that?
Try adding metadata to the collection using additional:
return BookResource::collection($books)->additional([
‘pass’ => $pass,
]);
BookResource
class BookResource extends Resource {
protected $pass;
public function pass($value){
$this->pass = $value;
return $this;
}
public function toArray($request){
return [
'id' => $this->id,
'isbn' => $this->isbn,
'title' => $this->title,
'description' => $this->description,
'published_year' => $this->published_year,
'authors' => AuthorResource::collection($this->authors),
'review' => BookReviewResource::collection($this->reviews),
'pass' => $this->pass
];
}
public static function collection($resource){
return new BookResourceCollection($resource);
}
}
BookResourceCollection
class BookResourceCollection extends ResourceCollection {
protected $pass;
public function pass($value){
$this->pass = $value;
return $this;
}
public function toArray($request){
return $this->collection->map(function(BookResource $resource) use($request){
return $resource->pass($this->pass)->toArray($request);
})->all();
}
Pass params
BookResource::collection($books)->pass(1);
Or using this:
http://localhost:3000/api/books?pass=1
public function toArray($request) {
$pass = $request->pass;
return [
'id' => $this->id,
'isbn' => $this->isbn,
'title' => $this->title,
'description' => $this->description,
'published_year' => $this->published_year,
'authors' => AuthorResource::collection($this->authors),
'review' => BookReviewResource::collection($this->reviews),
'pass' => $pass
];
}
I'm inserting a record to a polymorphic imageable table, however it says column thread_id not found. I have not declared this thread_id column and I don't know where it's pulling it from. Here is the code it's trying to run.
protected static function bootRecordImage()
{
if (auth()->guest()) return;
foreach (static::getMethodToRecord() as $event) {
static::$event(function ($model) use ($event) {
$body = request()->body;
preg_match_all('/<img .*?(?=src)src=\"([^\"]+)\"/si', $body, $matches);
$images = $matches[1];
if($event == 'created') {
foreach ($images as $image) {
$model->images()->create([
'user_id' => auth()->id(),
'imageable_id' => $model->id,
'imageable_type' => get_class($model),
'path' => $image
]);
}
}
if($event == 'deleting') {
foreach ($images as $image) {
$model->images()->delete([
'user_id' => auth()->id(),
'imageable_id' => $model->id,
'imageable_type' => get_class($model),
'path' => $image
]);
if (File::exists(public_path($image))) {
File::delete(public_path($image));
}
}
}
});
}
}
My store method:
public function store(Request $request, Channel $channel, Spam $spam)
{
if (!auth()->user()) {
return back()->withInput()->with('flash', 'Sorry! You must be logged in to perform this action.');
}
if (!auth()->user()->confirmed) {
return back()->withInput()->with('flash', 'Sorry! You must first confirm your email address.');
}
$this->validate($request, [
'title' => 'required',
'body' => 'required',
'channel_id' => 'required|exists:channels,id',
'g-recaptcha-response' => 'required'
// yes it's required, but it also needs to exist on the channels model, specifically on the id
]);
$response = Zttp::asFormParams()->post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => config('services.recaptcha.secret'),
'response' => $request->input('g-recaptcha-response'),
'remoteip' => $_SERVER['REMOTE_ADDR']
]);
// dd($response->json());
if (! $response->json()['success']) {
throw new \Exception('Recaptcha failed');
}
$spam->detect(request('title'));
$spam->detect(request('body'));
$thread = Thread::create([
'user_id' => auth()->id(),
'channel_id' => request('channel_id'),
'title' => request('title'),
'body' => request('body'),
//'slug' => str_slug(request('title'))
]);
return redirect('/forums/' . $thread->channel->slug . '/' . $thread->slug);
}
As you can see, no where is a thread_id mentioned, yet in the error it looks like it's trying to insert into a thread_id column that I've never declared.
Thanks for reading.
I put the polymorphic relation in the model and the trait. Remove it from the Model and you're good to go.
So what I am trying to do is store notifications to the database with Notifiable, but I have a saveMany contents and I don't know how to declare the data in the notifications. getting a null from what I've declared like the picture below
this is my saveMany store
$post = new Post;
$post->user_id = Auth::guard('webcontrol')->user()->id;
$post->lob = $request->lob;
$post->type = 'post';
$post->category_id = $request->category_id;
$post->is_published = $request->is_published;
$post->published_at = ($request->published_at) ? $request->published_at : Carbon::now();
$post->metas = '{}';
$post->save();
$post->contents()->saveMany([
new PostContent([
'lang' => 'id',
'slug' => str_slug($request->contents['id']['title'], '-'),
'title' => $request->contents['id']['title'],
'body' => $request->contents['id']['body'],
'image' => $request->contents['id']['image'],
'tag' => $request->contents['id']['tag'],
'metas' => serialize($request->contents['id']['metas']),
]),
new PostContent([
'lang' => 'en',
'slug' => str_slug($request->contents['en']['title'], '-'),
'title' => $request->contents['en']['title'],
'body' => $request->contents['en']['body'],
'image' => $request->contents['en']['image'],
'tag' => $request->contents['en']['tag'],
'metas' => serialize($request->contents['en']['metas']),
])
]);
Notification::send($user, new OneSignalNotification($post));
dd('done');
this is my Notification
class OneSignalNotification extends Notification
{
use Queueable;
public $post;
public function __construct($post)
{
$this->post = $post;
}
public function via($notifiable)
{
return ['database'];
}
public function toDatabase($notifiable)
{
return [
'body' => $this->post['body'], //this is where i confused how to declare the saveMany contents so its not returning null
];
}
}
So I'm not sure what you're trying to achieve but the issue is with the fact that you want to retrieve the body from the post, but the way I see it, the $post doesn't have a body property. The PostContent seems to have the body property, but the problem is that you saved many PostContent to the same $post, so therefore there are multiple body properties, one for each PostContent. So that's why I would recommend use the first() method to get the first PostContent from $post and then return it. If you want to return all the body property, you would have to get all of them and add them into an array or a string, or whatever you want. I hope this makes sense.
You should try using this:
$post = new Post;
$post->user_id = Auth::guard('webcontrol')->user()->id;
$post->lob = $request->lob;
$post->type = 'post';
$post->category_id = $request->category_id;
$post->is_published = $request->is_published;
$post->published_at = ($request->published_at) ? $request->published_at : Carbon::now();
$post->metas = '{}';
$post->save();
$post->contents()->saveMany([
new PostContent([
'lang' => 'id',
'slug' => str_slug($request->contents['id']['title'], '-'),
'title' => $request->contents['id']['title'],
'body' => $request->contents['id']['body'],
'image' => $request->contents['id']['image'],
'tag' => $request->contents['id']['tag'],
'metas' => serialize($request->contents['id']['metas']),
]),
new PostContent([
'lang' => 'en',
'slug' => str_slug($request->contents['en']['title'], '-'),
'title' => $request->contents['en']['title'],
'body' => $request->contents['en']['body'],
'image' => $request->contents['en']['image'],
'tag' => $request->contents['en']['tag'],
'metas' => serialize($request->contents['en']['metas']),
])
]);
Notification::send($user, new OneSignalNotification($post->fresh()));
dd('done');
And then inside the Notification, like I said, we take the first PostContent from the $post and get it's body property:
class OneSignalNotification extends Notification
{
use Queueable;
public $post;
public function __construct($post)
{
$this->post = $post;
}
public function via($notifiable)
{
return ['database'];
}
public function toDatabase($notifiable)
{
return [
'body' => $this->contents()->first()->body,
];
}
}
i'm working on a graphql API using Laravel GraphQL.
As shown in the documentation "Privacy" section, it should be possible to add callback function to a GraphQLType fields privacy attribute. The field is supposed to return null, when the callback returns false.
Similar to the example in the laravel graphql Docs, i've added a privacy callback like so:
public function fields(): array {
return [
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'privacy' => function(User $user): bool {
return $user->isMe();
}
],
];
}
It appears to me, that this callback function never gets called.
I read something about a possible requirement, that i should resolve my query using the $getSelectFields function to query the $fields manually $with the selected columns. But unfortunately the $select
public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) {
$fields = $getSelectFields();
$with = $fields->getRelations(); // empty array
$select = $fields->getSelect(); // empty array
return User::select($select)->with($with)->get();
}
In my case this does not make any difference.
In my query resolver i do as following:
public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) {
/** #var SelectFields $fields */
$fields = $getSelectFields();
$select = $fields->getSelect();
$with = $fields->getRelations();
exit(var_dump($fields)); // #RESULT
}
My result looks like this:
object(Rebing\\GraphQL\\Support\\SelectFields)#4668 (2) {
[\"select\":\"Rebing\\GraphQL\\Support\\SelectFields\":private]=> array(0) {}
[\"relations\":\"Rebing\\GraphQL\\Support\\SelectFields\":private]=> array(0) {}
}
So my question is: "How do i use the privacy attribute callback in Laravel Rebing GraphQL?"
I'm using:
PHP 7.3
Laravel 7.17
Rebing Graphql Laravel 5.1
Thanks in advance,
greets Jules
Some more Details about my use case
EpUser.php
namespace App\GraphQL\Type;
use App\CommunityImage;
use App\User;
use Carbon\Carbon;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Facades\Auth;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;
class EpUser extends GraphQLType {
protected $attributes = [
'name' => 'EpUser',
'description' => 'A type',
'model' => User::class,
];
public function fields(): array {
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the user',
'privacy' => function(User $user): bool {
return false;
}
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'privacy' => function(User $user): bool {
return $user->isMe();
}
],
'firstName' => [
'type' => Type::string(),
'description' => 'The firstName of user'
],
'lastName' => [
'type' => Type::string(),
'description' => 'The lastName of user'
],
'fullName' => [
'type' => Type::string(),
'description' => 'The fullName of user',
'selectable' => false,
'resolve' => function(User $user) {
return $user->firstName . " " . $user->lastName;
}
],
'gender' => [
'type' => Type::string(),
'description' => 'The gender of the user'
],
'isOnline' => [
'type' => Type::boolean(),
'description' => '',
'selectable' => false,
'resolve' => function(User $user, $args) {
return $user->isOnline();
}
]
];
}
[...]
And this is the UsersQuery which should respond with a user pagination object, that contains an array of users with a privacy attribute:
UsersQuery.php
namespace App\GraphQL\Query;
use App\Artist;
use App\FilePath;
use Closure;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
use Illuminate\Support\Facades\Auth;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Facades\GraphQL;
use App\User;
class UsersQuery extends Query {
protected $attributes = [
'name' => 'UsersQuery',
'description' => 'A query',
'model' => User::class,
];
public function type(): Type {
return GraphQL::type('userPagination');
}
public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = NULL, $getSelectFields = NULL): bool {
return Auth::check();
}
public function args(): array {
return [
'id' => [
'type' => Type::int(),
'description' => 'The id of the user'
],
'slug' => [
'type' => Type::string(),
'description' => 'The slug of the user'
],
'pagination' => [
'type' => Type::nonNull(GraphQL::type('paginationInput')),
'description' => 'The pagination of the users to query',
'rules' => 'required',
],
'search' => [
'type' => Type::string(),
'description' => 'a string to search for users'
],
'roles' => [
'type' => Type::listOf(Type::string()),
'description' => 'The roles of the user',
'rules' => 'sometimes|required|array|in:user,developer,administrator'
]
];
}
public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) {
if(isset($args['id']) || isset($args['slug'])) {
if(isset($args['slug'])) {
$user = User::where('slug', $args['slug'])->first();
} else {
$user = User::find($args['id']);
}
return [
'items' => $args['pagination']['limit'] > 0 && $user ? [$user] : NULL,
'itemTotal' => $user ? 1 : 0
];
}
$sortBy = $args['pagination']['sortBy'] ?? 'id';
$sortByDesc = isset($args['pagination']['sortByDesc']) ? $args['pagination']['sortByDesc'] : true;
$sortByType = $sortByDesc ? 'desc' : 'asc';
$search = false;
if(isset($args['search']) && $args['search']) {
$search = true;
$query = User::search($args['search']);
} else {
$query = User::query();
}
if(!empty($sortBy)) {
$query->orderBy($sortBy, $sortByType);
}
// Todo: eloquent search can't serach for whereHas
if(isset($args['roles']) && !$search) {
if(is_array($args['roles'])) {
foreach($args['roles'] as &$role) {
$query->whereHas('roles',
function($q) use ($role) {
$q->where('name', $role);
});
}
} else {
$query->whereHas('roles',
function($q) use ($args) {
$q->where('name', $args['roles']);
});
}
}
if($search) {
$userPaginationObject = [
'itemTotal' => $query->count(),
'items' => $query->getWithLimitAndOffset($args['pagination']['limit'],
$args['pagination']['offset'])
];
} else {
$userPaginationObject = [
'itemTotal' => $query->count(),
'items' => $query->limit($args['pagination']['limit'])->offset($args['pagination']['offset'])->get()
];
}
return $userPaginationObject;
}
}
I want to test my AR model without connect to database in Yii 2 so I use mockBuilder() but I dont know how can I pass the mock object to the model exist validator, for example:
class Comment extends ActiveRecord
{
public function rules()
{
return [
[['id', 'user_id', 'post_id'], 'comment'],
['comment', 'string',
'max' => 200
],
['user_id', 'exist',
'targetClass' => User::className(),
'targetAttribute' => 'id'
],
['post_id', 'exist',
'targetClass' => Post::className(),
'targetAttribute' => 'id'
]
];
}
}
class CommentTest extends TestCase
{
public function testValidateCorrectData()
{
$user = $this->getMockBuilder(User::className())
->setMethods(['find'])
->getMock();
$user->method('find')->willReturn(new User([
'id' => 1
]));
$post = $this->getMockBuilder(Post::className())
->setMethods(['find'])
->getMock();
$post->method('find')->willReturn(new Post([
'id' => 1
]));
// How can I pass to $user and $post to exist validator in Comment model?
$comment = new Comment([
'user_id' => 1,
'post_id' => 1,
'comment' => 'test...'
]);
expect_that($comment->validate());
}
}
ok, It's not a best code just I'd like to introduce what I want to do.
Yii2 ExistValidator uses ActiveQuery::exists() for check existence and you should replace generated validator to mockobject where the method createQuery returns mockobject of ActiveQuery where ::exists() return something you want (true/false) e.g.
$activeQueryMock = $this->getMockBuilder(ActiveQuery::className())
->disableOriginalConstructor()
->setMethods(['exists'])
->getMock();
$activeQueryMock->expects($this->any())
->method('exists')
->willReturn($value); //Your value here true/false
$model = $this->getMockBuilder(Comment::className())
->setMethods(['getActiveValidators'])
->getMock();
$model->expects($this->any())
->method('getActiveValidators')
->willReturnCallback(function () use ($activeQueryMock) {
$validators = (new Comment())->activeValidators;
foreach ($validators as $key => $validator) {
if (($validator instanceof ExistValidator) && ($validator->attributes = ['user_id'])) {
$mock = $this->getMockBuilder(ExistValidator::className())
->setConstructorArgs(['config' => get_object_vars($validator)])
->setMethods(['createQuery'])
->getMock();
$mock->expects($this->any())
->method('createQuery')
->willReturn($activeQueryMock);
$validators[$key] = $mock;
break;
}
}
return $validators;
});
$model->validate();