list more than one field in lists laravel method - php

I am using Zizaco/entrust laravel package and suppose I want to fetch users with teacher role.
I want return selected users and their name and family combination like this :
[
1 => 'ali nasiri',
2 => 'majid basirati'
]
For that, I wrote this code :
$teachers =
Role::where('name', 'teacher')->first()->users()->lists('name', 'users.user_id');
But that returns like this :
{
"1": "ali",
"2": "majid"
}
Means that only name field was returned because we can define one field in first Parameter of lists method.
How Can I do that and what is best solution ?

In your user model, add a custom attribute(accessor):
<?php
use Zizaco\Entrust\Traits\EntrustUserTrait;
class User extends Eloquent
{
use EntrustUserTrait; // add this trait to your user model
...
protected $appends = ['full_name'];
//Setter of full name attribute
public function getFullNameAttribute()
{
return $this->name.' '.$this->surname;
}
}
Then use it to access the full name:
$teachers = User::whereHas('roles',function($q){
return $q->where('name','teacher');
})
->get()
->lists('full_name');

Related

How to get only one note from relation belongsToMany?

I just have a table that has relation belongsToMany, BUT it was a mistake by developer so I can not change this structure SO I need to get only first(). However, when I take only first it return empty array but I need in object
$animals = Cat::query()->with(['types' => function($query) {
$query->first(); //wrong
}])
So how I can get only first? Because I need to order by this field and I can't because it is array
you can do this in two ways:
1- using hasOne relation:
class Cat {
public function firstType() {
return $this->hasOne(Type::class, 'type_id', 'id')->latest();
}
}
2- using staudenmeir/eloquent-eager-limit
after installing it you can write:
class Cat extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
public function firstType() {
return $this->hasMany(Type::class, 'type_id', 'id')->latest()->limit(1);
}
}
class Type extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
// ......
}
the advantage of HasEagerLimit trait is that you can limit the result not only to one but any number you want ...
now you can write:
$animals = Cat::query()->with('firstType');
You can add a attribute getter and set up into appends attribute. follow bellow example:
class Cat {
protected $appends = ['type'];
public function getTypeAttribute() {
// return the first element from your array of the belongsToMany relationship if it exists
return isset($this->types[0])? $this->types[0] : null;
}
}
That's important to remember this method will bring just one type. If you want to get the same type everytime, you create a diferente table where the cat table has the type_id column.
obs: Sorry for my english, it's still in working progress.

Laravel 8 Multiple Relationships for Factory

In Laravel 8 it is possible to quickly fill relationships with factories. However, I cannot figure out how to generate more than one relationship. How can I create a random or new relationship for each link using the new Laravel 8 syntax?
This factory syntax is only available in Laravel 8.
https://laravel.com/docs/8.x/database-testing#factory-relationships
Problem
Consider the following relationship:
Each link belongs to a website and a post.
Both websites and posts can have many links.
<?php
class Post extends Model
{
use HasFactory;
function links()
{
return $this->hasMany(Link::class);
}
}
class Website extends Model
{
use HasFactory;
function links()
{
return $this->hasMany(Link::class);
}
}
class Link extends Model
{
use HasFactory;
function post()
{
return $this->belongsTo(Post::class);
}
function website()
{
return $this->belongsTo(Website::class);
}
}
What I tried/want
What I tried below will only generate one model for all the links. How can I create a random or new relationship for each link using the new Laravel 8 syntax?
Link::factory()->count(3)->forPost()->forWebsite()->make()
=> Illuminate\Database\Eloquent\Collection {#4354
all: [
App\Models\Link {#4366
post_id: 1,
website_id: 1,
},
App\Models\Link {#4395
post_id: 1, // return a different ID
website_id: 1,
},
App\Models\Link {#4370
post_id: 1, // return a different ID
website_id: 1, // return a different ID
},
],
}
Just add this to your LinkFactory:
public function definition()
{
return [
'post_id' => function () {
return Post::factory()->create()->id;
},
.....
];
}
And now you can create new Post for each new Link:
Link::factory()->count(3)->create();//Create 3 links with 3 new posts
or attach new Links to existing Post:
Link::factory()->count(3)->create(['post_id' => Post::first()->id]); //create 3 links and 0 new posts
In Laravel 9, you can use this macro:
// database/factoryMacros.php
<?php
namespace Database\Support;
use Illuminate\Database\Eloquent\Factories\BelongsToRelationship;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
/** #param Factory|Model $factory */
Factory::macro('hasParent', function (mixed $factory, string $relationship = null): self {
return $this
->state(function () use ($factory, $relationship): array {
$belongsTo = new BelongsToRelationship(
factory: $factory,
relationship: $relationship ?? guessBelongsToMethodName($factory),
);
return $belongsTo
->recycle(recycle: $this->recycle)
->attributesFor(model: $this->newModel());
});
});
Factory::macro('hasChildren', fn (...$arguments): self => $this->has(...$arguments));
Factory::macro('hasChild', fn (...$arguments): self => $this->has(...$arguments));
/** #param Factory|Model $factory */
function guessBelongsToMethodName(mixed $factory): string
{
$modelName = is_subclass_of($factory, Factory::class)
? $factory->modelName()
: $factory::class;
return Str::camel(class_basename($modelName));
}
Usage
Use the method hasParent($factory) instead of for($factory):
// Creates 3 Link, 3 Post, 3 Website
Link::factory()
->count(3)
->hasParent(Post::factory())
->hasParent(Website::factory())
->make();
You can also use hasChildren($factory) or hasChild($factory) instead of has for name consistency:
// Creates 3 Post, 3 Link
Post::factory()
->count(3)
->hasChild(Link::factory())
->make();
The syntax of the macros is the same as for and has.
You can explicitly define the relationship name, pass complex factory chains, pass a concrete model, and use it with recycle, for example.
Installation
Add the file to your composer.json:
{
...
"autoload": {
"files": [
"database/factoryMacros.php"
]
}
}
Run a composer dump-autoload to reload the composer file.
Alternatively, you can register the macro as a service or load it as a mixin.
PS: I intend to create a library for this in the future.
Tests
/**
* Use "DatabaseEloquentFactoryTest.php" as base:
* https://github.com/laravel/framework/blob/de42f9987e01bfde50ea4a86becc237d9c8c5c03/tests/Database/DatabaseEloquentFactoryTest.php
*/
class FactoryMacrosTest extends TestCase
{
function test_belongs_to_relationship()
{
$posts = FactoryTestPostFactory::times(3)
->hasParent(FactoryTestUserFactory::new(['name' => 'Taylor Otwell']), 'user')
->create();
$this->assertCount(3, $posts->filter(function ($post) {
return $post->user->name === 'Taylor Otwell';
}));
$this->assertCount(3, FactoryTestUser::all());
$this->assertCount(3, FactoryTestPost::all());
}
}
TL;DR;
In Laravel 9, it is not possible to achieve this. The for() uses a single model for all instances.
There's a PR to fix this behavior, but the PR was closed, and I'm not sure it will ever be implemented:
https://github.com/laravel/framework/pull/44279
The laravel magic factory method for allows you to populate the database with one record from the foreign table. See link to documentation https://laravel.com/docs/8.x/database-testing#belongs-to-relationships
In your case, using forPost() and forWebsite() will allow you to populate the database with one id from the Post table and the Website table.
If you want to use different IDs use this syntax instead
Link::factory()->count(3)->make()
Had a similar problem and was only able to get it working when I attached within the afterCreating() on a single factory. This allows me to create/store the id of each model and then attach to the Link model
I'm choosing to start with WebsiteFactory but you can also start with PostFactory since those are the "highest parent" models. If you try to make a Link without the website_id and the post_id I believe you will get a error asking for both.
class WebsiteFactory extends Factory
{
public function definition(){...}
public function configure()
{
return $this->afterCreating( function (Website $website){
// the website model is created, hence after-creating
// attach Website to a new Post
$post = Post::factory()->hasAttached($website)->create();
// create new links to attach to both
$links = Link::factory()->for($website)->for($post)->count(3)->create();
});
You can leave PostFactory and LinkFactory as simple definition blocks (or add other stuff if you wanted). Now when you create a new Website via factory, it will create a new post and 3 new links. For example, you can now run
php artisan tinker
$w = Website::factory()->create(); // one website-one post-3 links
$ws = Website::factory()->count(5)->create(); // 5 website-5 post- 15 links
Check out the Factory Callbacks here (9.x docs, but they are in 8.x too):
https://laravel.com/docs/9.x/database-testing#factory-callbacks
\App\Models\Category::factory(10)
->has(Product::factory()->count(10), 'products')
->create();
It would be better if you play around with code. You will understand better.
$user = User::factory()
->has(Post::factory()->count(3), 'posts')
->create();
The above code will create three post for a single user. It will insert three post row and a user row. On the other hand the code below, seems three post will be inserted for user with name Jessica Aercher, that is it won't insert a user.
$posts = Post::factory()
->count(3)
->for(User::factory()->state([
'name' => 'Jessica Archer',
]))
->create();

Laravel Repository eager load predefined accessor

Just wanted to know if it is possible to eager-load a defined accessor in Laravel Repository. I am currently using this l5-repository package and Laravel 5.4.
In my UserInfo.php model, I have this function
public function getFullNameAttribute()
{
return ucfirst($this->first_name . " ". $this->last_name);
}
Then I am trying to eager load that accessor in my controller like this.
$user_infos = $this->repository->with('full_name')->all();
// or $user_infos = $this->repository->with('getFullNameAttribute')->all();
return response()->json([ 'data' => $user_infos ]);
I know that it is not gonna work. I am just looking for a similar way to add my accessor full_name in my collection. So I don't need to concatenate in my front end. So my expected result would be
{
id : 1,
first_name : "Sample fname",
last_name : "Sample lname",
.....
.....
full_name : "Sample fname Sample lname",
}
Use $appends in your model UserInfo.php
protected $appends = ['full_name'];
This will append this custom field in your collection.
Then you can see it here :
$user_infos = UserInfo::all();
$user_infos->toJson();
You can see the appended accessor in the json.

OctoberCms record finder with dynamic scope

I'm using OctoberCMS based on Laravel and trying to get a list of products within a form via Record Finder.
The use-case is that the record-finder must show available products based on dynamic condition.
I tried to achieve this via "Scope" option of record finder for related form model but not finding a way to pass the dynamic value to the scope.
Sample Code --
class A extends Model
{
public $belongsTo = [
'product' => [
'Plugin\Models\B',
'key' => 'id',
'scope' => 'specificProduct'
],
];
}
class B extends Model
{
public function scopeSpecificProduct($query , $product_type)
{
return $query->where('product_type', $product_type);
}
}
Here $product_type is the dynamic value which I am trying to pass via record finder and get in scope.
Can anyone suggest that is this a correct way for such requirement or how should I achieve this ?
In your fields definition you have to use the scope attribute
fields:
products:
label: Products
type: recordfinder
scope: specificProduct
With this, the second param of your scope will be the A model that is creating or updating
class B extends Model
{
public function scopeSpecificProduct($query , $model)
{
return $query->where('product_type', $model->depend_attribute);
}
}

Yii2 REST ActiveRecord deep relation not working

We've been coding an API service with Yii2 and created all model classes extending from ActiveRecord.
Next we started adding the simple relations. This is where things got strange.
We have 3 tables (limited to explain the problem) 'app_customers', 'lu_postcodes' and 'lu_countries'.
A customer has one postcode_id.
A postcode has one country_id.
In the customer model we would like to add relationships to get the postcode and country data when getting customer data.
Customer model:
namespace api\modules\v1\models;
use Yii;
use \api\common\components\BaseModel;
/**
*
*/
class Customer extends BaseModel {
public function extraFields() {
return ['postcode'];
}
public function getPostcode() {
return $this->hasOne(Postcode::className(), ['id' => 'postcode_id'])
->with(['country']);
}
....[more code]
Postcode model:
namespace api\modules\v1\models;
use Yii;
use \api\common\components\BaseModel;
/**
*
*/
class Postcode extends BaseModel {
public function extraFields() {
return ['country'];
}
public function getCountry() {
return $this->hasOne(Country::className(), ['id' => 'country_id']);
}
....[more code]
So when calling v1/customer?expand=postcode it returns all customer data and postcode is populated with a postcode object. But we can't get the country data to load together with the postcode data.
In an ideal situation we would like the index and view actions from customer to include both postcode and country data. (did not try yet .. one step at a time :) )
Trying to debug this issue We dumped the sql from Customer::getPostcode
with var_dump($q->createCommand()->sql); and got the output:
'SELECT * FROM `lu_postcodes` WHERE `id`=:qp0'
This might have something to do with missing tablenames?
In earlier attempt I managed to get the country data loaded, but it used the ID from customer which resulted in a wrong country obviously.
Any ideas? Thanks in advance!
I did some research and found https://github.com/yiisoft/yii2/issues/6844#issuecomment-131482508, which looked promising, but once implemented it still remained the same result.
----------------------- EDIT BELOW
I tried all options and in most cases I get a JSON parse error from Yii. In one case I get a result like
...
"display_name": "Rxxxxxxx xxxxxxx",
"postcode": {
"id": 361,
"country_id": 20,
"state_id": 2,
"zip": "3945",
"city": "Ham",
"alpha": "ham",
"longitude": "5.1730329000",
"latitude": "51.0966039000"
},
"country": null
...
Used option: Adding country in extraFields() in Customer.
public function extraFields() {
return ['postcode', 'country' => function($model) { return $model->postcode->country; }];
}
Yii2 itself does not directly support nested relations . It is not good idea to build such complex objects in a REST API using ActiveRecord classes. Remember you will likely have a collection API i.e. a group of customer , each customer will need multiple subqueries to satisfy the request.
There are multiple ways to address this.
Use separate controller to solve this /customer/<id>/country. This is good solution if country/ postcode is a hasMany relationship
Define country in the default fields() functions of postcode instead of extraFields(). This way when you pass expand?=postcode, both postcode and country will always show in the expanded output
Use ad-hoc expansion function in your extraFields() definition of Customer Class with something like this
.
public function extraFields()
{
return [
'postcode',
'country'=>function($model){
return $model->postcode->country;
}
];
}
Define a country function in your Customer class using Via Relation, this is more useful when you have hasMany relationships
.
public function getCountry()
{
return $this->hasOne(Country::className(), ['id' => 'country_id'])
->via('postCode');
}
Define a custom query with the exact parameters you desire and use a dataProvider as the response to your Index function something like this
..
public function actionIndex()
{
$query = (new \yii\db\Query())
// This can also be a ActiveQuery
// using Customer::find() with addSelects() etc
// ... additional query conditions
return new ActiveDataProvider(['query'=>$query]);
}
Similarly
public function actionView($id)
{
$query = (new \yii\db\Query()) // simlar to above
$query->andWhere(['customer.id'=>(int)$id]); // or similar condition
return $query->one();
}
I got it working.
It was an encoding issue...
'charset' => 'UTF-8', in the db config array did the trick.

Categories