Laravel attribute accessor capital letter is ignored - php

I am using Lumen framework. I have one problem, I need to set custom accessor for attribute, but the problem is that column in database starts with capital letter.
For example Logo.And in case of first capital letter, accessor is not called while retrieving object, I have tried with several columns, columns with name starts from small letter works perfectly.
public function getLogoAttribute($value)
This accessor doesn't work ,because name of column is Logo
I cannot change the name of columns in the database, but need to use accessors in my application.
I understand that I can change sources of Eloquent framework, but maybe there any other way to get it working.
Thanks.

I have spent several hours trying to find answer surfing the net, but than decided to find this part in code by myself.
I have found it.
It is located in vendor/illuminate/database/Eloquent/Model
Method public function attributesToArray()
Modify the part of this method like this
$mutatedAttributes = $this->getMutatedAttributes();
// We want to spin through all the mutated attributes for this model and call
// the mutator for the attribute. We cache off every mutated attributes so
// we don't have to constantly check on attributes that actually change.
foreach ($mutatedAttributes as $key) {
if (! array_key_exists($key, $attributes) ) {
if(array_key_exists(ucfirst($key), $attributes)) {
$key = ucfirst($key);
}
else {
continue;
}
}
If you have several capital letters in column name this will not work.
This bad solution for this problem, just name you database columns according to convention and you won't have any problems ( I have ability to change column name in my case).
UPDATE
You can also modify class like this
/**
* Indicates if the model mutated attributes can have different case from ex. User_comments instead of user_comments.
*
* #var bool
*/
public $upperCaseMutatedAttributes = false;
if($this->upperCaseMutatedAttributes && array_key_exists(ucfirst($key), $attributes)) {
$key = ucfirst($key);
}
You can override this variable in your class.

I did like this
protected $appends = ['image'];
public function getImageAttribute(){
$img = null;
$value = $this->attributes['Image'];
if ($value) {
if (strpos($value, 'baseurl') !== false) {
$img = $value;
} else {
$img = 'prefixurl' . $value;
}
}
return $img;
}

Related

In Laravel, how can I boot trait non-statically

Is there any reason why we only have this static way to boot traits in Laravel:
static function bootMyTrait ()
{...}
Is there any way to boot trait and have model instance in the boot function? Like this:
function bootMyTrait ()
{
if ($this instanceOf awesomeInterface)
{
$this->append('nice_attribute');
}
}
I need this AF, and for a very long time haven't found any solution.
Since Laravel 5.7 you can use trait initializers, instead of trait booters. I've had the same task and was able to solve it like this:
public function initializeMyTrait()
{
if ($this instanceOf awesomeInterface)
{
$this->append('nice_attribute');
}
}
Well, no one seems to care :D
Good news, is that within 15 min, I've solved my problem with this in base model:
public function __construct(array $attributes = [])
{
foreach (class_uses_recursive($this) as $trait)
{
if (method_exists($this, $method = 'init'.class_basename($trait))) {
$this->{$method}();
}
}
parent::__construct($attributes);
}
Edit
Instead of relying on traits for this, use Eloquent's accessors and mutators. For example, define the following methods on a User model:
// Any time `$user->first_name` is accessed, it will automatically Uppercase the first letter of $value
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
This appends the $user->first_name attribute to the model. By prefixing the method name with get and suffixing it with Attribute you are telling Eloquent, Hey, this is an actual attribute on my model. It doesn't need to exist on the table.
On the other hand you can define a mutator:
// Any string set as first_name will automatically Uppercase words.
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = ucwords($value);
}
This will apply anything you do to $value before setting it in the $attributes array.
Of course, you can apply these to attributes that do exist on your database table. If you have raw, unformatted data, say a telephone number 1234567890, and you wanted to apply a country code, you could use an accessor method to mask the number without modifying the raw value from the database. And going the other way, if you wanted to apply a standard formatting to a value, you could use a mutator method so all your database values conform to a common standard.
Laravel Accessor and Mutators

how does laravel UPDATE method work

I am working on this laravel project where user can upload an avatar image. My users table does not have any column yet to store the file location. So i was testing this in phpunit following the TDD series in laracast.
After the file is uploaded successfully and moved to the desired location in the server, i called the update method on the authenticated user like below:
$user = auth()->user();
$user->update(['avatar_location' => 'avatars/avatar.jpg']);
Note that avatar_location is not yet there on the users table. I expected this to fail but it didn't. I tried to find out what was going on so i followed through to the update() method in the model class:
//file Illuminate/Database/Eloquent/Model.php
public function update(array $attributes = [], array $options = [])
{
//dd($attributes); //prints 'avatar_location"=>"avatars/avatar.jpg'
if (! $this->exists) {
//dd($attributes);
return false;
}
return $this->fill($attributes)->save($options);
}
till this point the dd($attribute) prints the value that i passed to the update() method.
So i followed into the fill() method that is being called with the attribute parameter. However when i die dumped the received parameter from inside the fill() method i am not seeing the key=>value pair that i passed. Instead it was showing the other attributes of the user:
/**
* Fill the model with an array of attributes.
*
* #param array $attributes
* #return $this
*
* #throws \Illuminate\Database\Eloquent\MassAssignmentException
*/
public function fill(array $attributes)
{
//dd($attributes);
//does not print 'avatar_location"=>"avatars/avatar.jpg'
//rather, prints:
//array:4 [
// "name" => "Armand Mraz"
// "email" => "akautzer#example.net"
// "password" => "$2y$10$h7OG9/Toh31MsyFQc8lfg.wHeQC7maP4Bh37bea.DXU//IuRuXZi."
// "remember_token" => "X0udISwEEM"
]
$totallyGuarded = $this->totallyGuarded();
foreach ($this->fillableFromArray($attributes) as $key => $value) {
$key = $this->removeTableFromKey($key);
// The developers may choose to place some attributes in the "fillable" array
// which means only those attributes may be set through mass assignment to
// the model, and all others will just get ignored for security reasons.
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
} elseif ($totallyGuarded) {
throw new MassAssignmentException($key);
}
}
return $this;
}
I spent a lot of time trying to figure out why?
can anyone please explain?
And why the update method is not failing even though i am trying to update a column that does not exist?
Thanks,Yeasir
When you're updating an object, Laravel is trying to match the keys of the array of data and the list of fillable fields. All pairs of key/valye missing from the fillable fields are not considered. It's the reason why it's not failing.
You have to update the fillable field list (property $fillable) in your user model.
Have a look at the documentation.
If you add avatar_location in your fillable fields list and the field doesn't exist, in this case, it will throw an exception.

Yii2: Assigning dynamic properties to model and displaying them in a grid

To keep things short, the application itself is really simple, having only three tables.
Product:
id
name
Attribute
id
name
slug
Product Attribute
id
attribute_id
product_id
value
As you may guess the attributes may be totally random, with any content and there might be any number of them. Every product has the same set of global attributes assigned. Some are empty, some are filled.
What I want to do is displaying them in a standard Gridview, the same way as if it was a normal model, with dynamic-preassigned columns and values. Basically speaking - the attributes should serve as the columns.
I've tried to extend the main Product Model and use ActiveDataProvider class on it, but no avail, I'm getting the custom attributes values repeated for each row, as if something was missing. Here's the class I've created basing on another question from SO.
namespace common\models;
use Yii;
use common\models\Attribute;
use common\models\Product;
class ProductDynamic extends Product {
public function init() {
parent::init();
$attrExists = ProductAttribute::find()->select(['slug','value'])->joinWith(['attributeModel'])->all();
if ($attrExists) {
foreach ($attrExists as $at) {
$this->dynamicFields[$at['slug']] = $at['value'];
}
}
}
private $dynamicFields = [];
public function __get($name) {
if (array_key_exists($name, $this->dynamicFields))
return $this->dynamicFields[$name];
else
return parent::__get($name);
}
public function __set($name, $value) {
if (array_key_exists($name, $this->dynamicFields))
$this->dynamicFields[$name] = $value;
else
parent::__set($name, $value);
}
public function rules() {
return array_merge(parent::rules, $this->dynamicRules);
}
}
My question is - how would you do it? How to assign properties to a model so they act as a standard database-loaded properties usable by ActiveDataProvider? I think I need to retrieve the ProductAttributes some other way so they are not repeated. See below.
Ok, i think i got your idea...
So, I think you could generate an attribute in the model of Product (we will call it ProductAttributeArr) which would be an array, then retrieve the attributes from the Attribute table in the database according to Product Attribute table and store them in ProductAttributeArr.
Then you could create a function that dynamically generates the parameters for the GridView base on the content of ProductAttributeArr. This way it should work.
Answering it myself since there's no feedback and after some more digging i've found the quickest, but maybe not the nicest solution.
Since the properties in main Product model are added thanks to the ProductDynamic class I have added the following in Product to assign correct column values. For sure, there must be another, easier way, if you know any feel free to comment.
public function afterFind() {
$attrs = ProductAttribute::find()->select(['slug', 'value'])->joinWith(['attributeModel'])->where(['product_id' => $this->id])->all();
foreach ($attrs as $a) {
$this->{$a['slug']} = $a['value'];
}
}

Get enum options in laravels eloquent

In my migration file, I gave my table pages a enum field with 2 possible values (as seen below). My question is, if it's possible to select these values with Laravels Eloquent?
$table->enum('status', array('draft','published'));
There are several Workarounds that I found, but there must be some "eloquent-native" way to handle this. My expected output would be this (that would be perfect!):
array('draft','published')
Thank you in advance!
Unfortunately, Laravel does not offer a solution for this. You will have to do it by yourself. I did some digging and found this answer
You can use that function and turn it into a method in your model class...
class Page extends Eloquent {
public static function getPossibleStatuses(){
$type = DB::select(DB::raw('SHOW COLUMNS FROM pages WHERE Field = "type"'))[0]->Type;
preg_match('/^enum\((.*)\)$/', $type, $matches);
$values = array();
foreach(explode(',', $matches[1]) as $value){
$values[] = trim($value, "'");
}
return $values;
}
}
And you use it like this
$options = Page::getPossibleStatuses();
If you want you can also make it a bit more universally accessible and generic.
First, create a BaseModel. All models should then extend from this class
class BaseModel extends Eloquent {}
After that, put this function in there
public static function getPossibleEnumValues($name){
$instance = new static; // create an instance of the model to be able to get the table name
$type = DB::select( DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$name.'"') )[0]->Type;
preg_match('/^enum\((.*)\)$/', $type, $matches);
$enum = array();
foreach(explode(',', $matches[1]) as $value){
$v = trim( $value, "'" );
$enum[] = $v;
}
return $enum;
}
You call this one like that
$options = Page::getPossibleEnumValues('status');
Made a small improvement to lukasgeiter's function. The foreach loop in his answer is parsing the string. You can update the regex to do that for you.
/**
* Retrieves the acceptable enum fields for a column
*
* #param string $column Column name
*
* #return array
*/
public static function getPossibleEnumValues ($column) {
// Create an instance of the model to be able to get the table name
$instance = new static;
// Pulls column string from DB
$enumStr = DB::select(DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$column.'"'))[0]->Type;
// Parse string
preg_match_all("/'([^']+)'/", $enumStr, $matches);
// Return matches
return isset($matches[1]) ? $matches[1] : [];
}
This throws an error if the column does not exist. So I added a small check in the code
public static function getPossibleEnumValues ($column) {
// Create an instance of the model to be able to get the table name
$instance = new static;
$arr = DB::select(DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$column.'"'));
if (count($arr) == 0){
return array();
}
// Pulls column string from DB
$enumStr = $arr[0]->Type;
// Parse string
preg_match_all("/'([^']+)'/", $enumStr, $matches);
// Return matches
return isset($matches[1]) ? $matches[1] : [];
}
As of L5.17 Eloquent does not include this functionality, instead you need to fall back to native QL. Here's an example that will work with SQL and in one line - returning an array like you asked.
In the spirit of one liner complexity ;)
I threw this in one of my view composers - it fetches the column from the table, explodes it and assembles the values in an array.
I iterate over that in my views using a foreach.
explode (
"','",
substr (
DB::select(" SHOW COLUMNS
FROM ".(new \Namespace\Model)->getTable()."
LIKE 'colName'"
)[0]->Type,
6,
-2
)
);

Zend framework -> database table field prefix like users.us_name

I'm dealing with database containing of many tables, with many field prefixes (two first letters of every table), so when I have users table I cannot use "name" property ($user->name) but I can use: $user->us_name.
I's there a way to simplify things and set automagic prefix for every field of a table ?
You'd have to extend Zend_Db_Table_Row to accomplish this. Fortunately, ZF includes a _transformColumn() method expressly for this purpose. But I'm getting ahead of myself. First, set up your table class. This assumes your database has a table called "foo_mytable":
class MyTable extends Zend_Db_Table_Abstract {
protected $_name = 'foo_mytable';
protected $_rowClass = 'My_Db_Table_Row';
}
Next, create your custom Row class:
class My_Db_Table_Row extends Zend_Db_Table_Row {
protected function _transformColumn($columnName) {
$columnName = parent::_transformColumn($columnName);
$prefix = 'us_';
return $prefix . $columnName;
}
}
Now, you can do something like this (for simplicity, this example ignores MVC design ideals):
$table = new MyTable();
$records = $table->fetchAll();
foreach ($records as $record) {
echo $record->name;
}
Assuming your table has a column named "us_name", this should work. I tested it myself. Note that in your custom table row, you might want to grab the table prefix from a config file. If you've got it stored in your registry, you could replace $prefix = 'us_'; with $prefix = Zend_Registry::get('tablePrefix');.
I didn't know about _transformColumn(). I'm gonna hop on top of #curtisdf 's example.
I think you should override with this (not tested):
protected function _transformColumn($columnName)
{
$tblName = $this->_table->info(Zend_Db_Table::NAME);
$prefix = substr($tblName, 0, 2);
return $prefix . '_' . parent::_transformColumn($columnName);
}
Using this, you won't need to store prefixes/table-names, as they are retrieved dinamically.

Categories