my codes are constructed by Laravel+dingo.
I have two models which are one to many relationship:
Reservation.php (Master)
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Reservation extends Model
{
protected $table = 'dbo.Reservation';
public function hasManyReservationDetails()
{
return $this->hasMany('App\Models\ReservationDetail', 'ReservationID', 'ReservationID');
}
}
ReservationDetail.php (Detail)
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ReservationDetail extends Model
{
protected $table = 'dbo.ReservationDetail';
public function belongsToReservation()
{
return $this->belongsTo('App\Models\Reservation', 'ReservationID', 'ReservationDetailID');
}
}
And two Transformers for the two models as following:
ReservationTransformer
public function transform(Reservation $reservation)
{
return [
'reservation_id' => (int) $reservation->ReservationID,
'reservation_no' => $reservation->ReservationNo,
];
}
ReservationDetail Transformer
public function transform(ReservationDetail $reservation_detail)
{
return [
'reservation_detail_id' => (int) $reservation_detail->ReservationDetailID,
'reservation_id' => (int) $reservation_detail->ReservationID,
'room_no' => $reservation_detail->RoomNo,
];
}
My controller and inquire
$reservation = Reservation::where('ReservationNo', '=', $reservation_no)
->with('ReservationDetails')
->get();
return $reservation;
I get the following return
{
"Reservations": [
{
"ReservationID": "1",
"ReservationNo": "2016-06-01 16:50:59.0659",
"reservation_details": [
{
"ReservationDetailID": "1",
"ReservationID": "1",
"RoomNo": "001",
},
{
"ReservationDetailID": "2",
"ReservationID": "1",
"RoomNo": "002",
}
]
}
]
}
I try the following but only return the translation of master table.
$reservation = $this->collection($reservation, new ReservationTransformer());
**
How can I transform the the data of master and detail table together?
**
I am not really understand how 'Custom Transformation Layer' works, anyone who can give me an example?
Many Thanks.
You can use fractals build in support for transformer includes. See http://fractal.thephpleague.com/transformers/
I would start by renaming public function hasManyReservationDetails() to public function reservationDetails().
And then your ReservationTransformer will take care of the rest:
use League\Fractal\TransformerAbstract;
class ReservationTransformer extends TransformerAbstract
{
/**
* List of resources to automatically include
*
* #var array
*/
protected $defaultIncludes = [
'reservationDetails',
];
public function transform(Reservation $reservation)
{
return [
'reservation_id' => (int) $reservation->ReservationID,
'reservation_no' => $reservation->ReservationNo,
];
}
/**
* Include ReservationDetail
*
* #param Reservation $reservation
*
* #return League\Fractal\Resource\Collection
*/
public function includeReservationDetails(Reservation $reservation)
{
return $this->collection($reservation->reservationDetails, new ReservationDetailTransformer);
}
}
Related
When inserting models into the database I'd like to add 5 minutes to the timestamp property after each single model insert.
I thought that using Sequence like this would do the trick but it's not working: all models that are inserted still have the same timestamp.
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Factories\Sequence;
class TestSeeder extends Seeder
{
public function run()
{
\App\Models\Test::factory(1000)
->state(new Sequence(
fn () => ['timestamp' => now()->addMinutes(5)->toDateTimeString()],
))
->create();
}
}
I will suggest this approach maybe with the need for some adjustments:
class TestSeeder extends Seeder
{
private $testData = [];
public function run()
{
$now = now();
for ($i=0; $i < 100; $i++) {
$testData[] = [
'key1' => Str::random(10),
'key2' => Str::random(10),
'timestamp' => $now->addMinutes(5)->toDateTimeString()
];
}
foreach ($testData as $test) {
\App\Models\Test::create($test);
}
}
}
Use Factory Callbacks;
class TestFactory extends Factory
{
protected $model = Test::class;
/**
* Configure the model factory.
*
* #return $this
*/
public function configure()
{
return $this->afterCreating(function (Test $test) {
$test->update(['created_at' => Test::last()->created_at->addMinutes(5)]);
});
}
}
Seeder Class
public function run()
{
\App\Models\Test::factory(1000)->create();
}
You can use Eloquent Model Events: https://laravel.com/docs/8.x/eloquent#events
Ex:
class User extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::created(function ($user) {
// update the time or any fields else
});
}
}
I have problem creating a seeding hierarchy in Laravel, is there way i can achieve hierarchy by just creating a seeding data. I have 3 column [Vehicle,Model,Variant] so each vehicle has different model and variant. I will show you the sample table that I created on excel that I wanted to achieve.
Expected Output:
Here is my seeding function:
<?php
use Illuminate\Database\Seeder;
use App\Variant;
use App\Vehicle;
use App\VehicleModel;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class CarsSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
foreach (Vehicle::all() as $vehicle_data) {
foreach (VehicleModel::all() as $vehicle_model_data) {
foreach (Variant::all() as $variants_data) {
DB::table('cars')->insert([
[
'make' => $vehicle_data->name,
'model_id' => $vehicle_model_data->id,
'variant_id' => $variants_data->id,
'created_at'=>date('Y-m-d H:i:s'),
'updated_at'=>date('Y-m-d H:i:s')
]
]);
}
}
}
}
}
Car Model:
class Car extends Model
{
//
protected $table = 'cars';
protected $fillable = [
'id',
'make',
'variant_id',
'model_id'
];
public function variants() {
return $this->hasMany('App\Variant','id','variant_id');
}
public function models() {
return $this->hasMany('App\VehicleModel','id','model_id');
}
}
Variant Model:
class Variant extends Model
{
//
protected $table = 'variants';
protected $fillable = [
'name'
];
}
Vehicle Model:
class Vehicle extends Model
{
//
protected $table = 'vehicles';
protected $fillable = [
'name'
];
}
Vehicle Model:
class VehicleModel extends Model
{
//
protected $table = 'vehicle_models';
protected $fillable = [
'name'
];
}
Assuming the following relations
Vehicle hasMany VehicleModel(s)
VehicleModel hasMany Variant(s)
class Vehicle extends Model
{
public function vehicle_models()
{
return $this->hasMany(VehicleModel::class);
}
}
class VehicleModel extends Model
{
public function variants()
{
return $this->hasMany(Variant::class);
}
}
Try this
public function run()
{
$vehicles = Vehicle::with('vehicle_models.variants')->get();
foreach($vehicles as $vehicle) {
foreach($vehicle->vehicle_models as $model) {
foreach($model->variants as $variant){
Car::create([
'make' => $vehicle->name,
'model_id' => $model->id,
'variant_id' => $variant->id
]);
}
}
}
}
I need your help.
I'm working with Laravel Framework and I have a trouble with a belongsTo relationship.
My project have to tables, address book and delivery types, the columns in tables are:
address_book
id
name
mail
delivery_1
deliverytype_id_1
delivery_2
deliverytype_id_2
...
delivery_types
id
name
...
The code of delivery types model is this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class DeliveryType extends Model
{
protected $table = 'delivery_types';
protected $guarded = ['id'];
}
This is the address book model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class AddressBook extends Model
{
protected $table = 'address_book'; // table
protected $guarded = ['id']; // primary key
protected $appends = ['delivery', 'actions']; // accessors
protected $delivery = '';
public function del1() {
return $this->belongsTo('App\DeliveryType', 'deliverytype_id_1', 'id')->withDefault();
}
public function del2() {
return $this->belongsTo('App\DeliveryType', 'deliverytype_id_2', 'id');
}
/**
* Accessor: get the actions column information.
*
* #return string
*/
public function getActionsAttribute() {
$actions = '<a href='. route('admin.addressbook.show', $this->id) .'>'.
'show<i class="livicon" data-name="info" data-size="18" data-loop="true" data-c="#428BCA" data-hc="#428BCA" title="view contact"></i></a>';
return $actions;
}
/**
* Accessor: get the deliveries information.
*
* #return string
*/
public function getDeliveryAttribute () {
$deliveries = [
['val' => $this->delivery_1, 'type' => $this->del1()->name], //row error
['val' => $this->delivery_2, 'type' => $this->del2()->name]
];
foreach($deliveries as $delivery) {
$this->delivery = (strlen($delivery['val']) > 0) ?
$this->appendString($this->delivery, '<strong>'.$delivery['type'].'</strong> '.$delivery['val']) :
$this->delivery;
}
return $this->delivery;
}
protected function appendString(string $str, string $val) {
return (strlen($str) > 0) ? $str.'<br>'.$val : $val;
}
In the html page the data is loaded through ajax call to the controller function. This is the code of function:
public function data(Request $request) {
// Init data
$this->addressbooks = AddressBook::get(
['address_book.id',
'address_book.name',
'address_book.email',
'address_book.delivery_1',
'address_book.delivery_2');
// Return json array
header("Content-Type: application/json");
return $this->addressbooks;
}
When the page call the function through ajax, the framework return the error "Undefined property" in the accessor getDeliveryAttribute, where I try to call relationship belongs to about delivery type ID and its reference in the delivery types table.
What I'm doing wrong? Thanks in advance to those who can help me.
Here is how I would write the AddressBook model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
//use Illuminate\Support\Facades\Log;
class AddressBook extends Model
{
protected $table = 'address_book'; // table
//protected $guarded = ['id']; // primary key
// always load these accessors
protected $appends = [
'delivery',
'actions',
];
protected $mail = '';
// No need for $delInfo, use $this->delivery (same data)
// protected $delInfo = '';
public function category() {
/*
You can use `AddressBookCategory::class` instead of `'App\DeliveryType'`
http://php.net/manual/en/migration55.new-features.php#migration55.new-features.class-name
Because models are in the same napespace, we don't need to write \App\AddressBookCategory
*/
return $this->belongsTo(AddressBookCategory::class, 'address_book_category_id', 'id');
}
public function deliveryType1() {
return $this->belongsTo(DeliveryType::class, 'deliverytype_id_1', 'id')
/*
Specify empty string for the name, so that when we access
$this->deliveryType1->name it's equal ''
*/
->withDefault(['name' => '']);
}
public function deliveryType2() {
return $this->belongsTo(DeliveryType::class, 'deliverytype_id_2', 'id')
->withDefault(['name' => '']);
}
/**
* Accessor: get the actions column information.
*
* Access by using: $this->action
*
* #return string
*/
public function getActionsAttribute() {
// moved this into multi line, easier to read
return '<a href='. route('admin.addressbook.show', $this->id) .'>'
.'show'
.'<i class="livicon" data-name="info" data-size="18" data-loop="true" data-c="#428BCA" data-hc="#428BCA" title="view contact"></i>'
.'</a>';
}
/**
* Accessor: get the deliveries information
*
* Access by using: $this->delivery
*
* #return string
*/
public function getDeliveryAttribute () {
// I've updated logic here, it should be easier to read...
$delivery = [];
if ( ! empty($this->delivery_1) ) {
$delivery[] = '<strong>'.$this->deliveryType1->name.'</strong> '.$this->delivery_1;
}
if ( ! empty($this->delivery_2) ) {
$delivery[] = '<strong>'.$this->deliveryType2->name.'</strong> '.$this->delivery_2;
}
// join array elements with a string `<br>`
return implode('<br>', $delivery);
}
}
I have create a morphMany relationship for ratings and I'm having a problem loading the ratings relationship data inside the model using the model->load or model::with method both of them aren't letting me use the collections model builder.
if I do this inside a method of a model it throws an error:
$all = this->ratings()->get();
return $all;
Call to undefined method Illuminate\Database\Query\Builder::ratingInfo()
I need the ratings query builder so I can then query and filter the results but It's not using the query builder and even if I make this a scope it's still throws the same error.
all code:
class Product extends Model
{
use Rateable;
protected $table = "products";
protected $fillable = [
'title',
'sku',
'quantity',
'unit_price',
'created_by', 'updated_by'
];
public function created_by() {
return $this->belongsTo('App\User', 'created_by', 'id');
}
public function updated_by() {
return $this->belongsTo('App\User', 'updated_by', 'id');
}
public function ratings() {
return $this->morphMany('App\Rating', 'rateable');
}
public function ratingInfo() {
$all = $this->ratings()->get() error using get request for eager loading;
// i want to query like this
$two_star = $all->filter(function ($item, $key) {
return $item->rating === 2;
});
return $all;
}
}
public function show($id) {
$product = Product::findOrFail($id);
// it doesn't seem to matter if use the keyword ::with('ratingInfo')
$product->load('ratingInfo', 'created_by', 'updated_by');
return response()->json($product, 200, ['Content-Length' => strlen(json_encode($product))]);
}
class Rating extends Model
{
protected $table = 'ratings';
protected $fillable = ['rating', 'comment', 'user_id', 'rateable_id', 'rateable_type'];
public function rating()
{
return $this->morphTo();
}
}
Using phone numbers and user and companies as an example:
class PhoneNumber extends Model
{
/**
* Get all of the owning callable models.
*/
public function callable()
{
return $this->morphTo();
}
}
class Company extends Model
{
/**
* Get all of the model's phone numbers.
*
* #return mixed
*/
public function phoneNumbers()
{
return $this->morphMany(PhoneNumber::class, 'callable');
}
}
class User extends Model
{
/**
* Get all of the model's phone numbers.
*
* #return mixed
*/
public function phoneNumbers()
{
return $this->morphMany(PhoneNumber::class, 'callable');
}
}
To save a phone number to a user or company would be like this:
$phoneNumber = new PhoneNumber(['number' => '555-555-5555']);
$user->phoneNumbers()->save(phoneNumber);
$phoneNumber = new PhoneNumber(['number' => '555-555-5555']);
$company->phoneNumbers()->save(new PhoneNumber(phoneNumber));
Then to access the phone number collections associated with each, simply:
$user->phoneNumbers // this is a Collection
$company->phoneNumbers // this is a Collection
$user->phoneNumbers->count() // access to all Collection methods as this point
is it there an easy way to remove the "data" envelope from the Dingo API response.
When I use this Transformer to transform user models:
class UserTransformer extends EloquentModelTransformer
{
/**
* List of resources possible to include
*
* #var array
*/
protected $availableIncludes = [
'roles'
];
protected $defaultIncludes = [
'roles'
];
public function transform($model)
{
if(! $model instanceof User)
throw new InvalidArgumentException($model);
return [
'id' => $model->id,
'name' => $model->name,
'email' => $model->email
];
}
/**
* Include Roles
*
* #param User $user
* #return \League\Fractal\Resource\Item
*/
public function includeRoles(User $user)
{
$roles = $user->roles;
return $this->collection($roles, new RoleTransformer());
}
I get this response:
{
data : [
"id": 102,
"name": "Simo",
"email": "mail#outlook.com",
"roles": {
"data": [
{
"id": 1
"name": "admin"
}
]
}
}
]
}
I read some articles about RESTful APIs and a lot of them stated that such enveloped responses arent very modern (You should use the HTTP Header instead).
How can I disable this behaviour at least for the includes?
Thank you
For those who fall on this later and as I had really hard time to make it, I'd like to share how I made it working in my API :
1) Create a Custom Serializer, NoDataArraySerializer.php :
namespace App\Api\V1\Serializers;
use League\Fractal\Serializer\ArraySerializer;
class NoDataArraySerializer extends ArraySerializer
{
/**
* Serialize a collection.
*/
public function collection($resourceKey, array $data)
{
return ($resourceKey) ? [ $resourceKey => $data ] : $data;
}
/**
* Serialize an item.
*/
public function item($resourceKey, array $data)
{
return ($resourceKey) ? [ $resourceKey => $data ] : $data;
}
}
2) Set new the Serializer. In bootstrap/app.php, add :
$app['Dingo\Api\Transformer\Factory']->setAdapter(function ($app) {
$fractal = new League\Fractal\Manager;
$fractal->setSerializer(new App\Api\V1\Serializers\NoDataArraySerializer);
return new Dingo\Api\Transformer\Adapter\Fractal($fractal);
});
That's it.
Now, in your UserController (for instance), you can use it like this :
namespace App\Api\V1\Controllers;
use App\Api\V1\Models\User;
use App\Api\V1\Transformers\UserTransformer;
class UserController extends Controller
{
public function index()
{
$items = User::all();
return $this->response->collection($items, new UserTransformer());
}
}
And the response will look like :
[
{
"user_id": 1,
...
},
{
"user_id": 2,
...
}
]
Or, I you want to add an enveloppe, you just need to set the resource key in the Controller. Replace :
return $this->response->collection($items, new UserTransformer());
by
return $this->response->collection($items, new UserTransformer(), ['key' => 'users']);
And the response will look like :
{
"users": [
{
"user_id": 1,
...
},
{
"user_id": 2,
...
}
]
}
One addition to the solution of YouHieng. The preferred way to register the NoDataArraySerializer in Laravel 5.3 and above is to write a custom ServiceProvider and add the logic into the boot() method and not the bootstrap/app.php file.
For Example:
php artisan make:provider DingoSerializerProvider
Then:
public function boot(){
$this->app['Dingo\Api\Transformer\Factory']->setAdapter(function ($app) {
$fractal = new League\Fractal\Manager;
$fractal->setSerializer(new App\Http\Serializers\NoDataArraySerializer());
return new Dingo\Api\Transformer\Adapter\Fractal($fractal);
});
}
Have a look to http://fractal.thephpleague.com/serializers/#arrayserializer. They explain exactly what to do when
Sometimes people want to remove that 'data' namespace for items