I am coding on Laravel 6, and stumbled up on this problem. I have accessors for a field that i need in my model in some cases, but it does not exist in my database. As i read in documentation, the variable $appends make the field in question to be serialized with the model, but not saved in database.
Then, when i update or save entries of the specific model in database, Laravel gives me back an error saying that field x is not present in field list. I searched here, googled a lot, but didn't found any answer.
This pluggable_data field is not sent by client-side. I create it on server-side because i need its information to do some tasks. But it is not relevant enough to create a column in DB just to store it.
Model
/**
* #var array|null
*/
protected $appends = [
'pluggable_data'
];
/**
* #param array $pluggableData
*/
public function setPluggableDataAttribute(array $pluggableData)
{
$this->attributes['pluggable_data'] = $pluggableData;
}
/**
* #return array
*/
public function getPluggableDataAttribute(): array
{
return $this->attributes['pluggable_data'] ?? []; // Sometimes there is no pluggable data, then i return an empty array
}
Where the error occurs
$activity->setRegisterDate($fields['register_date']);
$activity->setExtraAttribute($fields['extra']);
$activity->update(); <----- Here
return $activity;
The error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'pluggable_data' in 'field list'
Summarizing what i want (in case that you have a better idea): i want a field that will be present in model serializations, even if it is an empty array, but that does not be saved on database.
As #nay said in the comments, all i had to do was to replace the $this->attributes['pluggable_data'] by a real variable, like $this->pluggable_data. This way, Laravel will not think that it is a database column
Related
I am creating a custom ACL class that would check whether the relationship exists between the records and if so load all the related records to that particular bean. I have looked at the sugar documentation which says to use load_relationship($relationshipName) for checking if relationship exists and getBeans() to load all the related records (as an array of objects). I have implemented this into my class but for some reason whichever module and relationship I use it always returns an empty array.
The data I use for checking has 3 parts:
The Module accessing the data
The relationship name with the target module (not module name)
The ID of the record accessing the data
The link here at sugar community shows a similar problem that I'm having, but the answer to this does not so solve my problem
Here is my custom ACL:
namespace Sugarcrm\Sugarcrm\custom\clients\base;
class CustomACL
{
const ACL_NONE = 0;
const ACL_READ_ONLY = 1;
const ACL_READ_WRITE = 2;
public static function checkRelated($module, $linkedRelationshipName, $id)
{
$bean = \BeanFactory::getBean($module);
if ($bean->load_relationship($linkedRelationshipName)) {
return self::checkRecordRelated($bean, $id,$linkedRelationshipName);
} else {
return false;
}
}
/**
* Checks if record is related
* #param $bean
* #param $id
* #param $linkedModule
* #return bool
*/
protected static function checkRecordRelated($bean, $id, $linkedModule)
{
$bean->retrieve_by_string_fields(array(
"id" => $id
));
if ($bean->load_relationship($linkedModule)) {
$relatedRecords = $bean->$linkedModule->getBeans();
return $relatedRecords;
} else {
return false;
}
}
}
This class should be working for any module, even if it is custom or non custom. I have tried using my custom module and even the default modules (leads, accounts etc) but none of them returns anything except an empty array.
I suspect the problem is that you are reusing the previously empty bean, for which you already loaded the same link using load_relationship() before.On the second load_relationship() call, Sugar probably returns the cached result from the first call (as the link is already internally flagged as having been loaded), therefore returning the same empty array again.
Therefore instead of using
$bean->retrieve_by_string_fields(array(
"id" => $id
));
I'd suggest creating a new bean e.g. using
if (empty($id)) {
return false;
}
$bean = BeanFactory::retrieveBean($module, $id);
if (!$bean) {
return false;
}
(which should actually not be too slow, as the bean is probably cached already)
Notes:
Your variable names are somewhat confusing. $linkedRelationshipName and $linkedModule should contain neither the relationship name nor the module name, but the name of the link-type field.
EDIT:
To reiterate:
The documentation may be misleading there, but load_relationship() does not expect the relationship name as parameter. What it expects is the link name!.
from data/SugarBean.php:
/**
* Loads the request relationship. This method should be called before performing any operations on the related data.
*
* This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
* link then it creates a similary named variable and loads the relationship definition.
*
* #param string $link_name link/attribute name.
*
*#return nothing.
*/
function load_relationship($link_name)
So make sure to check the VarDefs of each module for the correct link name.
E.g.
relationship name: accounts_contacts
link field for this relationship in account: contacts,so you should be calling $accountBean->load_relationship('contacts')
link field for this relationship in contact: accounts,so you should be calling $contactBean->load_relationship('accounts')
Note: link names are basically arbitrary across different modules, don't rely on them being lowercase singular/plural of the linked module. In some cases (and for custom relationships) they will not be.
I am using $hidden and $appends to hide pivot keyword from json array and pull the objects into main array. In the game model I added below script to do functionality as I said above
protected $hidden = ['pivot'];
protected $appends = ['user_id','highscore', 'level'];
public function getUserIdAttribute()
{
return $this->pivot->user_id;
}
public function getHighScoreAttribute()
{
return $this->pivot->highscore;
}
public function getLevelAttribute()
{
return $this->pivot->level;
}
Now the problem is that I want to fetch all games and I know it is so simple I just added into controller below script
$Games = Game::all();
return new GameResource($Games); /* GameResource is for API */
But the this one is return following error after adding above script into model.
ErrorException: Trying to get property user_id of non-object
Anyone can guide me please how to manage both of functionality because I need both of. I would appreciate if someone kindly guide.
After #emix Comment
#emix it is not duplicated because the reference is about core php and I am using laravel. Off course laravel is built with php but still there is complicated structure so I think this one is not same as you refered
The pivot is only available when you're fetching the models through the many-to-many relationship (BelongsToMany). The pivot represents the fields on the pivot table (eg. games_users) and thus fields that exist for that specific relationship.
When you're just pulling directly from the Game model (aka. games table), you have no relationship or pivot table to pull data from.
CakePHP 2.6.x
I used the bake CLI to create my models, which created fields named ID. Notice it's uppercase.
So in my models, I was expecting to reference that property like this: $this->ID, as the property names usually match the field names (in my experience). It's definitely working that way in the controllers. For example, I have lots of controller code that looks like this:
$this->SomeModel->findById( $model['SomeModel']['ID'] );
However this didn't work in the model. After a lot of head scratching and experimenting, I finally figured out that the model property is named id (notice the lower case).
//in SomeModel.php
var_dump( $this->ID ); //NULL
var_dump( $this->id ); 33
Is this the expected behavior? Do all model properties get converted to lower case? If so, why is the controller different? Did I defy a CakePHP convention somehow? Any explanation of what is going on here would be most welcome.
When you call $this->id you're accessing the Model's id property, not the value of the field in the database.
From source;
<?php
/**
* Value of the primary key ID of the record that this model is currently pointing to.
* Automatically set after database insertions.
*
* #var mixed
*/
public $id = false;
As Mark's suggested in his comment, use $this->primaryKey = 'ID' in your model to achieve the desired result, you could then do something like this in 2.6:
<?php
$this->id = 33; // Set the active record
$this->field('ID'); // Returns 33 (If you really want to use uppercase)
$this->id; // Returns 33
$this->read(); // Returns all of record 33
Suppose I have category table and I have used soft delete on it. Now first time I have added one category "Test" after that I have delete this category so it will update my deleted_at column from the database.
Now when again I am trying to add category with name "Test" it is saying me that this name has been taken. I have tried with rules which are posted Here.
But it is not working. I have used trait in my model. Below is my model code.
<?php
namespace App\Models;
use Illuminate\Support\Facades\Validator as Validator;
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class Category extends \Eloquent {
use SoftDeletingTrait;
protected $dates = ['deleted_at'];
/**
* Guarded fields which are not mass fillable
*
* #var array
*/
protected $guarded = array('id');
/**
* Name of the table used
*
* #var string
*/
protected $table = 'travel_categories';
/**
* Validating input.
*
* #param array $input
* #return mixed (boolean | array)
*/
public static function validate($input, $id = null) {
$rules = array(
'name' => array('required','min:2','max:100','regex:/[a-zA-z ]/','unique:travel_categories,name,NULL,id,deleted_at,NULL'),
'description' => 'Required|Min:2',
'image' => 'image'
);
if ($id) {
$rules['name'] = 'Required|Between:3,64|Unique:travel_categories,name,'.$id;
}
$validation = Validator::make($input,$rules);
return ($validation->passes())?true : $validation->messages();
}
}
Did you understand the soft deleting purpose? It will only flag the data to be inactive. It will be there though.
So, if you define the values of that column must be unique, it is right you could not create other one alike.
If it needs to be unique, so you should restore and update the data.
If it can have many ones, so you should remove the unique key applied on it (and call it by relationship for instance).
Look at: Laravel Eloquent Soft Deleting
First: I don't understand a couple of things. Are you trying to validate for create and update? Then why do you allow name to be of length 2 till 100 for creation, and only 3 till 64 for after updates?
Second: I recommend dropping this:
protected $dates = ['deleted_at'];
I don't see the goal of that.
Third, and I'm getting to the point here, what are you trying to do? I guess, what you are trying to do with this filter 'unique:travel_categories,name,NULL,id,deleted_at,NULL' is to check the uniqueness of the name among the active categories. In that case, that should work.
As noted above, a unique index on [category, deleted_at] will not work because when deleted_at is null, many SQL RDBMS will allow multiple records to be inserted despite a unique index existing.
In case anyone is interested I have now created a Laravel extension to handle the SQL unique-index constraints correctly: https://packagist.org/packages/tranzakt/laravel-softdeletesunique
If anyone tries this, please give feedback on Github, thanks.
I know this question is old, but I had a similar issue and I stumbled upon this. I wanted to mention how I fixed it for anyone, who is reading it in the future. The problem I had was that Laravel did not allow me to insert a value in a unique column when there was an old record with the same value, but was deleted using soft_delete.
To summarize, the goal is to ignore old soft deleted records for a unique column when inserting a new record. The solution I found is in the migration for the table. For example, let us assume we have these columns:
category - unique
deleted_at - keeps tracks of the deleted rows
Both should be specified as unique in the migration like so:
Schema::create('table_name', function (Blueprint $table) {
$table->string("category");
$table->softDeletes();
$table->unique(["category", "deleted_at"]);
});
Side note: If you already have the table like I did, you need to change the migration and create the table again (obviously the data will be lost):
Remove the table
Change the migration
Remove the record about it from the migrations table
run "php artisan migrate" to create the table again
In Doctrine2.0.6, I keep getting an error: "Column VoucherId specified twice".
The models in question are:
Basket
BasketVoucher
Voucher
Basket links to BasketVoucher.
Voucher links to BasketVoucher.
In Voucher and BasketVoucher, there is a field called VoucherId. This is defined in both models and exists with the same name in both DB tables.
The error occurs when saving a new BasketVoucher record:
$basketVoucher = new BasketVoucher;
$basketVoucher->setVoucherId($voucherId);
$basketVoucher->setBasketId($this->getBasket()->getBasketId());
$basketVoucher->setCreatedDate(new DateTime("now"));
$em->persist($basketVoucher);
$em->flush();
I've checked the models and VoucherId is not defined twice. However, it is used in a mapping. Is this why Doctrine thinks that the field is duplicated?
Here's the relevant code - I haven't pasted the models in their entirety as most of the code is get/set.
Basket
/**
* #OneToMany(targetEntity="BasketVoucher", mappedBy="basket")
* #JoinColumn(name="basketId", referencedColumnName="BasketId")
*/
private $basketVouchers;
public function getVouchers()
{
return $this->basketVouchers;
}
BasketVoucher
/**
* #ManyToOne(targetEntity="Basket", inversedBy="basketVouchers")
* #JoinColumn(name="basketId", referencedColumnName="BasketId")
*/
private $basket;
public function getBasket()
{
return $this->basket;
}
/**
* #OneToOne(targetEntity="Voucher", mappedBy="basketVoucher")
* #JoinColumn(name="voucherId", referencedColumnName="VoucherId")
*/
private $voucherEntity;
public function getVoucher()
{
return $this->voucherEntity;
}
Voucher
/**
* #OneToOne(targetEntity="BasketVoucher", inversedBy="voucherEntity")
* #JoinColumn(name="voucherId", referencedColumnName="VoucherId")
*/
private $basketVoucher;
public function getBasketVoucher()
{
return $this->basketVoucher;
}
Any ideas?
EDIT: I've found that the same issue occurs with another model when I save it for the first time. I am setting the primary key manually. The main issue appears to be saving a relationship within an entity.
In this case, I have a field - DraftOrderId - which is used as the primary key on three models. The first model - DraftOrder - has DraftOrderId as a primary key, which is an auto incrementing value. The other two models - DraftOrderDeliveryAddress, and DraftOrderBillingAddress - also use DraftOrderId as a primary key, but it isn't auto incremented.
What's happening is one of the following issues:
If I save the delivery address entity with a draft order id and set it to persist, I get an error: Column DraftOrderId specified twice. Code:
try {
$addressEntity->getDraftOrderId();
} catch (\Doctrine\ORM\EntityNotFoundException $e) {
if ($addressType == "delivery") {
$addressEntity = new Dpp\DraftOrderDeliveryAddress;
} elseif ($addressType == "billing") {
$addressEntity = new Dpp\DraftOrderBillingAddress;
}
$addressEntity->setDraftOrderId($draftOrder->getDraftOrderId());
$em->persist($addressEntity);
}
(It would also help to know if there's a better way of checking if a related entity exists, rather than trapping the exception when trying to get a value.)
If I remove the line that sets the draft order id, I get an error: Entity of type Dpp\DraftOrderDeliveryAddress is missing an assigned ID.
If I keep the line that sets the draft order id but I remove the persist line, and I also keep the lines later on in the code that sets the name and address fields, I don't get an error - but the data is not saved to the database. I am using flush() after setting all the fields - I'm just not using persist(). In the previous examples, I do use persist() - I'm just trying things out to see how this can work.
I can paste more code if it would help.
I think I've fixed it! A couple of findings:
For a primary key that is not an auto-incrementing value, you need to use:
#generatedValue(strategy="IDENTITY")
You also have to explicitly set the mapped entities when creating them for the first time. At first, I was trying to create the address entity directly, but I wasn't setting the mapped entity within the parent model to reference the address entity. (if that makes any sense)
I'm fairly sure it was mostly due to the lack of the IDENTITY keyword, which for some reason was either saying the key wasn't set, or saying it was set twice.