How to create column aliases in a resource - laravel

I am creating an API using Laravel.
I created a table called transaction. I also created a resource called transactionresource.
I want to change the id column to be the key column in the API, and I don't want id to show in the API; instead key should show.
My code
class TransactionResource extends JsonResource
{
public function toArray($request)
{
$array = parent::toArray($request);
// set the new key with data
$array['key'] = $array['id'];
// unset the old key
unset($array['id']);
return $array;
}
}
I am getting this error
{"success":false,"message":"Undefined array key \"id\""}

you can return array of custom fields like this
public function toArray($request){
return [
'key' => $this->id,
...
];}
see documentation

Related

Polymorphic relationship with pivot data

I would like to solve the following issue:
I got multiple models like:
Product
Customer
Each model should be able to have one or more Fields with pivot data.
Field:
id
title
type
required
Example:
Product has a field called video_url, type should be string containing the pivot value http://youtube.com/....
Customer has a field called external_id, type should be integer containing the pivot value 242.
The fields should be added dynamically by the user. The user should be able to decide, whether the field is morphing to Product or Customer (or even more later).
Maybe this helps to understand:
What I am doing right now
At the moment I created a new Model for each, product and customer
For customers:
class CustomerField extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function customers()
{
return $this->belongsToMany(Customer::class)->withPivot('value');
}
}
For products:
class ProductField extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function products()
{
return $this->belongsToMany(Product::class)->withPivot('value');
}
}
At the moment this works out, but of course, it's not the most elegant way to solve it.
My question
Is there a possibility to morph a field dynamically to Product or Customer with an additional pivot?
I think this is what you want Polymorphic:Many-to-Many
You don't need to add ProductField and CustomerField models,
you just need to add Product, Customer and Field model.
The fields will dynamically belongs to product or customer by fieldable_type. Even you have more models, it will store the model name to this fieldable_type.
And the tables you need to be created like this below:
fieldables table has fieldable_id and fieldable_type;
fieldable_type will set your model name automatically, like App\Product, and you can custom that by yourself in AppServiceProvider:
Relation::morphMap([
'products' => 'App\Product',
'customers' => 'App\Customer',
]);
In Product Model:
class Product extends Model
{
/**
* Get all of the fields for the product.
*/
public function fields()
{
return $this->morphToMany('App\Field', 'fieldable')->withPivot('value');
}
}
In Customer Model:
class Customer extends Model
{
/**
* Get all of the fields for the customer.
*/
public function fields()
{
return $this->morphToMany('App\Field', 'fieldable')->withPivot('value');
}
}
In Field Model:
class Field extends Model
{
/**
* Get all of the products that are assigned this field.
*/
public function products()
{
return $this->morphedByMany('App\Product', 'fieldable');
}
/**
* Get all of the customers that are assigned this field.
*/
public function customers()
{
return $this->morphedByMany('App\Customer', 'fieldable');
}
}
CRUD with Pivot Value:
After that, you can easily create, get, update, delete pivot value like:
Field::first()->products; # return the products with pivot value
Field::first()->customers; # return the customers with pivot value
Customer::first()->fields;
$field = Field::first();
# create new relationship with pivot value between customer and fields:
Customer::first()->fields()->attach($field, ['value' => 'customer new value field']);
# update pivot with value:
Customer::first()->fields()->sync([$field->id => ['value' => 'update customer value field']]);
# Delete pivot
Customer::first()->fields()->detach($field->id);
The best practice is to use a separate table to hold meta information so that you can easily add/remove "columns" as needed
For example, you could set your meta table up like this:
create table `ProductField` (
products_id int(11),
column_name varchar(255),
value varchar(255),
)
Then in your products model, add functionality to get, insert, check if exists, etc.
public function getMeta($column) {
$meta = DB::table('ProductField ')
->select('column_name', 'value')
->where('products_id', '=', $this->id)
->where('column_name', '=', $column)
->get();
if (!$meta->isEmpty()) {
return $meta;
} else {
return null;
}
}
public function addMeta($column, $value) {
DB::table('ProductField ')->insert(
[
'products_id' => $this->id,
'column_name' => $column,
'value' => $value,
]
);
}
The same way you can achieve dynamic nature for Customers too.
You can also use an array to store the feilds and then dynamically add them to the model
foreach ($request->input('cost') as $key=>$cost) {
Price::create([
'product_id' => $request->product_id[$key],
'date' => Carbon::now(),
'cost' => $cost,
'trend' => 0
]);
}
If you know that there will only be certain dynamic fields ahead of time, you could opt to create accessor methods for them

Is there a way to silently abort creating a model in Laravel?

I have a ProductTags model that is in a one to many relationships to my product. I need to prevent the same model id and tag from being duplicated in multiple records. In the migration I have $table->unique(['product_uuid', 'tag']); but that means if I attempt to create a model that is a duplicate of an existing model I get an integrity constraint violation.
So when a ProductTags model is created, before it gets saved it needs to check the database for an existing version then abort its own creation. I would prefer to do this in the model so I only have to implement it once instead of both the controllers and factory.
I tried to override the create method to intercept it, but when I seeded, it doesn't seem to be called at all.
class ProductTag extends Model
{
protected $fillable = [
'product_uuid', 'tag',
];
public function create($data){
if(
ProductTag::where('product_uuid', '=', $data['product_uuid'])->
where('tag','=', $data['tag'])
){
return false;
}
return parent::create($data);
}
}
You can use the ->exists() function on the query builder to check if a record exists for that query:
public function create($data){
$productTag = self::where('product_uuid', $data['product_uuid'])
->where('tag', $data['tag']);
if ($productTag->exists()) {
return false;
}
return self::create($data);
}
What about using firstOrCreate
public function create($data)
{
return self::firstOrCreate( // self or use ProductTag
[
'product_uuid' => $data['product_uuid'],
'tag' => $data['tag']
],
$data
);
}
I am just not sure if you can invoke it on self, but you can do it on the model for sure.
This will return an instance of ProductTag back in any case so you can chain other methods

HasMany relation(or any relation) data inserted but not return on the same time | Laravel | Eloquent Model

I have a laravel application in which the setting_types and user's settings are saved into different models.
User.php:
/*
* Getting the user's notification setting.
*/
public function notificationSetting()
{
return $this->hasMany('App\NotificationSetting');
}
/*
* Controller function to get the user's settings
*/
public function getSetting(Request $request)
{
$userSetting = $user->notificationSetting;
// check new settings are inserted for user or not.
if (someCondition) {
// add new settings for user.
$user->notificationSetting()->save(new NotificationSetting(['user_id' => $user_id, "notification_type_id" => 121]));
print_r($user->notificationSetting); // still rec. Old values.
}
return $user->notificationSetting;
}
As you can see that I insert the relation object but I didn't receive on the same time. and if I hit again (this time my someCondition become false) so it will return the update records.
Since the save() method returns a boolean, you could write it like this:
$user->notificationSetting()
->save(
$notificationSetting = new NotificationSetting([
'user_id' => $user_id,
'notification_type_id' => 121
])
);
return $notificationSetting;
You might also be able to use the create() method instead, that will return the instance of the model, but only if the attributes are fillable of course.
If you want to retrieve all the related records of a model at any time, you can use the load() method like this:
$user->load('notificationSetting');
It is also important the use the plural form for a hasMany relation in order to distinguish it from a hasOne or a belongsTo relation:
public function notificationSettings()
{
return $this->hasMany('App\NotificationSetting');
}

Yii 2 getOldAttribute() method not working in afterSave

I have a model and I want to take an old the value of that model's field after save, I was using getOldAttribute() method and it always returns the latest value?
Code sample
class User extends ActiveRecord
{
public function fields()
{
return [
'id',
'name',
'email' //old value: example#example.com
];
}
}
class UserService extends BaseService
{
/**
* Current user have email is `test#test.com`
*/
public function update(User $user)
{
//before save
$oldEmailBeforSave = $user->getOldAttribute('email'); //work fine and value is `example#example.com`
//handle update user
$this->saveEntity($user)
//after save
$oldEmailAfterSave = $user->getOldAttribute('email'); //not work and value is `test#test.com`
}
}
According to the docs you can use the second parameter of the afterSave( $insert, $changedAttributes) which holds the old values of the attributes that were changed and were saved.
Your code should look like below if you want to get the email fields old value if it was changed
public function afterSave( $insert, $changedAttributes ){
parent::afterSave();
if(!$insert){
$oldEmail = $changedAttributes['email'];
//do something here with the old email value
}
}
When you save new attribute, it is no longer "old". getOldAttribute() represents state from database and allows you to compare whether and how attribute was changed between find() and save(). After calling save(), old attributes are refreshed, so getOldAttribute() will return the same as getAttribute().
If you want to access old state of object, you need to clone it and use these methods on clone - $cloned will represent state of object before calling save():
public function update(User $user)
{
//before save
$oldEmailBeforSave = $user->getOldAttribute('email'); // `example#example.com`
$cloned = clone $user;
//handle update user
$this->saveEntity($user)
//after save
$oldEmailAfterSave = $user->getOldAttribute('email'); // `test#test.com`
$oldEmailAfterSave = $cloned->getOldAttribute('email'); // `example#example.com`
}

Laravel custom authentication user fields

I created a table fields with costume fields names fld_ID, fld_Username, fld_Password but i can't use this fields type on simple laravel Authentication, so i defined my ID field name on user model except laravel-4:
protected $primaryKey = 'fld_ID';
and for Password name:
public function getAuthPassword() {
return $this->attributes['fld_Password'];
}
and finally for my username on Post Action Defined a username type for attempt:
$input = Input::all();
$user['fld_Username'] = $input['fld_Username'];
$user['fld_Password'] = $input['fld_Password'];
if (Auth::attempt($user)){
....Some Code Here... :)
}
but still i have problem with and Auth::attempt return false, my last Query log is this:
Array ( [query] => select * from `tbl_users` where `fld_Username` = ? and `fld_Password` = ? limit 1 [bindings] => Array ( [0] => username [1] => password )
and password is Hashed before save.
Because you changed the password field's name, you need to make a custom user provider and register it in config file.
As you can see in vendor\laravel\framework\src\Illuminate\Auth\EloquentUserProvider.php line 138 the password field is hard coded and there is no other way to change it.
So make a class somewhere is app folder like this:
class CustomUserProvider extends EloquentUserProvider
{
public function retrieveByCredentials(array $credentials)
{
if (isset($credentials['fld_Password'])) {
unset($credentials['fld_Password']);
}
return parent::retrieveByCredentials($credentials);
}
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['fld_Password'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
}
Now you need to introduce this user provider to laravel. just add this to app\Providers\AuthServiceProvider.php:
class AuthServiceProvider extends ServiceProvider
//...
public function boot()
{
\Auth::provider('CustomUserProvider', function ($app, array $config) {
return new CustomUserProvider($app['hash'], $config['model']);
});
}
//...
}
Just one more step. in config/auth.php edit the providers section like this:
'providers' => [
'users' => [
'driver' => 'CustomUserProvider', // make sure you change this
'model' => CustomUserModel::class, // here is your custom model
],
],
I hope this will help you.
The array you pass to attempt is simply an array; it has nothing to do with your database table. you don't have to use 'fld_Username', and 'fld_Password'. Instead just use $user['username'] and $user['password'].
Laravel is looking for the word "password" as the key in the array you pass to see which is the password.
(Proof)
Simply override the username function from LoginController and return your username field, just like this:
class LoginController extends Controller
{
.
.
.
public function username()
{
return "fld_Username";
}
}
No need to change anything else except the getAuthPassword method that you just did correctly.
I hope this will help you.

Resources