Laravel Eloquent Model inserts all values as NULL - php

I have run into problems with Laravel Eloquent Model
I have a model as follow:
class Activity extends Eloquent {
protected $table = 'activity';
protected $timestamps = false;
public $item;
public $content;
public $year;
protected $fillable = array('item', 'content', 'year');
}
And the corresponding controller:
class ActivityController extends \BaseController {
public function create()
{
$activity = new Activity();
$actitity->item = 'Example';
$activity->content = 'Example content';
$activity->year = 2015;
$activity->save();
}
}
The above code should work fine and there should be a record in 'activity' table. However, all the value of columns of activity table are inserted as NULL when I run this code (except for the id column which is auto_increment).
In addition, when I var_dump the $activity (just before calling $activity->save()), the $activity with all of its properties are shown as expected (I mean, with values I've assigned before)
Is there any subtle error in my code?

You must not define database fields as actual class properties. The problem is that Laravel uses an $attributes array internally, not the models properties.
When doing
$activity->content = 'Example content';
Laravel uses the magic __set() method to update the value in it's $attributes array. But that setter method is never called because you have an actual property with that name.
What you need to do to resolve this problem is remove the properties:
class Activity extends Eloquent {
protected $table = 'activity';
protected $timestamps = false;
protected $fillable = array('item', 'content', 'year');
}
If you want to document the properties and have autocomplete support you can use the #property annotation:
/**
* #property string $item
* #property string $content
* #property int $year
*/
class Activity extends Eloquent {

This is because Eloquent uses magic setters/getters. If you did $model->randomAttribute then it would look into the models attributes array for the data.
Because you have explicitly defined each attribute it directly accesses the property and not the magic getter. When you call save(), the function saves all the data in the attributes array which contains nothing.
Remove the attribute definitions and it will work.
If you call $model->getAttributes() you will see there will be no data contained within.

Remove:
public $item;
public $content;
public $year;
from:
class Activity extends Eloquent {
protected $table = 'activity';
protected $timestamps = false;
public $item;
public $content;
public $year;
protected $fillable = array('item', 'content', 'year');
}

Related

mocking hasMany relation in laravel 5.1 unit test

For a unit test in laravel 5.1 I am trying to test a cascading delete function of the Client model, which, with the recursive flag set, should also delete all users associated with the client.
I want to use a mock user adn test only wether the delete function on the user is called, so I wont have to use the database, and to apply the same principle to other tests in the future.
at the moment the test fails because I cannot find a way to make the client model retreive the associated user without firing a query.
I think I need to mock the hasMany relation defining function of the client, but i have not found a way.
the client model:
class Client extends Model
{
protected $table = 'clients';
protected $fillable = [];
public function casDelete($recursive = false){
if($recursive) {
$users = $this->users()->get();
foreach($users as $user) {
$user->casDelete($recursive);
}
}
$this->delete();
}
public function users(){
return $this->hasMany('App\User');
}
}
the user model:
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable, CanResetPassword;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name', 'email', 'password', 'client_id'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = ['password', 'remember_token'];
public function casDelete($recursive = false){
$this->delete();
}
public function client(){
return $this->belongsTo('App\Client');
}
}
the test:
class ClientModelTest extends TestCase
{
use DatabaseTransactions;
function testCasDelete(){
$client = factory(Client::class)->create();
$user = factory(User::class)->make(['client_id' => $client->id]);
$observer = $this->getMock('user');
$observer->expects($this->once())->method('casDelete');
$client->casDelete(true);
}
}
When you are using DatabaseTransactions, this mean you want to persist the data in the database. And when you are using create() from the factory still you are using the database, so either you should not use the database at all or if you want you to use the database then you can simply solve the problem. but what I can suggest is this solution, which I'm not using the database init.
$user = \Mockery::mock();
$user->shouldReceive('casDelete')->andReturnNull();
$queryMock = \Mockery::mock();
$queryMock->shouldReceive('get')->andReturn([$user]);
$clientMock = \Mockery::mock(Client::class)->makePartial();
$clientMock->shouldReceive('users')->andreturn($queryMock);
$clientMock->casDelete(true);
This way you can be sure that you have called casDelete on each user model.
this is a very simple test case, you can extend it in the way you like base on what you want to achieve.

laravel Illegal offset type error

I have a page that shows the details of a single test case. For some reason, I can't get past this error, even to send the $id. Here's my controller:
public function show($id)
{
$data =DB::table('TestCase')->where('TestCaseID', $id);
return view('managements.testcase-details')->with($data);
}
Here's the error:
in View.php line 180
at HandleExceptions->handleError('2', 'Illegal offset type', 'C:\xampp\htdocs\terkwazmng\vendor\laravel\framework\src\Illuminate\View\View.php', '180', array('key' => object(Builder), 'value' => null))
You forgot a little bit. A get and to set up data variable name. Your error means, that you pass a query builder rather than its results. The second error is that you passing a NULL value (second param in with).
$data =DB::table('TestCase')->where('TestCaseID', $id)->get();
return view('managements.testcase-details')->with('data', $data);
In view use data like you use an array: foreach($data ...).
This method solve my problem, i am showing it here as an example -
Class that we want to use -
<?php
namespace App;
use App\Helpers\ModelMPK; //MPK stands for Multi-column Primary Key handling
class AccountSession extends ModelMPK
{
protected $hidden = ["account_id", "id"];
protected $primaryKey = ['account_id', 'session'];
public $incrementing = false;
}
Customized model class, I copied the function from somewhere, i can't refer him here because I can't resource URL I get this from -
<?php
namespace App\Helpers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class ModelMPK extends Model
{
/**
* Set the keys for a save update query.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
protected function setKeysForSaveQuery(Builder $query)
{
$keys = $this->getKeyName();
if(!is_array($keys)){
return parent::setKeysForSaveQuery($query);
}
foreach($keys as $keyName){
$query->where($keyName, '=', $this->getKeyForSaveQuery($keyName));
}
return $query;
}
/**
* Get the primary key value for a save query.
*
* #param mixed $keyName
* #return mixed
*/
protected function getKeyForSaveQuery($keyName = null)
{
if(is_null($keyName)){
$keyName = $this->getKeyName();
}
if (isset($this->original[$keyName])) {
return $this->original[$keyName];
}
return $this->getAttribute($keyName);
}
}
my problem: My table had no auto-increment column and the laravel was trying to access the auto-increment column because Laravel assume every table has an auto-increment id, so Laravel sent me this error.
solution : add public $incrementing = false; to your Model Class
i added this in model
<pre>
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class holding extends Model
{
public $timestamps = false;
public $incrementing = false;
public $keyType = 'string';
protected $table = 'tb_holding';
protected $primaryKey = ['qsymbol','id_user'];
protected $fillable = ['qsymbol','qlotbuy','qbuyprice','qstoploss','qlaststopls','qbuydate','idnote','id_user'];
//---> Illegal offset type while updating model
//---> because primary key more than 1 --> add this
//https://laracasts.com/discuss/channels/laravel/illegal-offset-type-while-updating-model?page=1
protected function setKeysForSaveQuery(Builder $query)
{
return $query->where('qsymbol', $this->getAttribute('qsymbol'))
->where('id_user', $this->getAttribute('id_user'));
}
</pre>
If you want to return an specific id record use this
use app\Model;
public function show($id){
$data =Model::select('n.data')->findOrFail($id);
return view('managements.testcase-details')->with($data);
}

Globally Hiding Attributes From Array Or JSON Conversion in laravel?

Sometimes you may wish to limit the attributes that are included in your model's array or JSON form, such as passwords. To do so, add a hidden property definition to your model:
class User extends Model {
protected $hidden = ['password'];
}
This is model specific.
Is there any method to hide globally?
ie,I want to hide deleted_at and created_by from all model json result.
The easiest way to do that is by creating a base model. Like this:
class BaseModel extends Model {
protected $hidden = ['deleted_at', 'created_by'];
}
And then all your models extend from that:
class User extends BaseModel {
}
Note that this way if you wanted to add some hidden fields for a specific model you would have to as well specify those two global hidden attributes:
class User extends BaseModel {
protected $hidden = ['deleted_at', 'created_by', 'password'];
}
If that bothers you, you could merge the global attributes in from the contructor:
class BaseModel extends Model {
private $globalHidden = ['deleted_at', 'created_by'];
public function __construct(array $attributes = array()){
$this->hidden = array_merge($this->globalHidden, $this->hidden);
parent::__construct($attributes);
}
}

Laravel 4 to 5 upgrade: Eloquent relationships not working

I'm trying to upgrade my existing Laravel 4 project to version 5.
Model relationships are not working fine. Every time I try to access a property from property_price table it returns null.
My models are located in App/Models directory.
Property Model
class Property extends \Eloquent {
protected $guarded = array('id');
protected $table = 'properties';
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $softDelete = true;
public function propertyPrice()
{
return $this->hasOne('PropertyPrice','pid');
}
}
PropertyPrice Model
class PropertyPrice extends \Eloquent {
protected $guarded = array('id');
protected $table = 'property_pricing';
public function property()
{
return $this->belongsTo('Property');
}
}
Usage
$property = Property::find($id);
$price = $property->property_price->per_night_price; // null
The code is working fine in Laravel 4.
You need to specify namespace in relation methods.
If you're using php5.5+ then use ::class constant, otherwise string literal:
// App\Models\PropertyClass
public function property()
{
return $this->belongsTo(Property::class);
// return $this->belongsTo('App\Models\Property');
}
// App\Models\Property model
public function propertyPrice()
{
return $this->hasOne(PropertyPrice::class,'pid');
// return $this->hasOne('App\Models\PropertyPrice','pid');
}
Of course you need to namespace the models accordingly:
// PSR-4 autoloading
app/Models/Property.php -> namespace App\Models; class Property

Laravel Eloquent::firstOrCreate Problems

Ok so I am trying to have use the Eloquent method "firstOrCreate" within another Eloquent model.
FriendRequest Eloquent
class FriendRequest extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
public $table = 'requests';
protected $guarded = array('id');
protected $softDelete = true;
public function friend() {
return $this->hasOne('User', 'id', 'friend_id');
}
public function user() {
return $this->hasOne('User', 'id', 'user_id');
}
public function accept() {
// FIRST YOU MUST MARK REQUEST AS ACCEPTED
// THEN SOFT DELETE REQUEST SO IT DOESN'T
// SHOW UP AS ACTIVE FRIEND REQUEST
$this->accepted = '1';
$this->save();
// CREATE FRIENDSHIP USER -> REQUESTED
$friend = Friend::firstOrNew(array('user_id' => Auth::user()->id, 'friend_id' => $this->friend_id));
$friend->save();
// CREATE FRIENDSHIP REQUESTED -> USER
$friend2 = Friend::firstOrNew(array('user_id' => $this->friend_id, 'friend_id' => Auth::user()->id));
$friend2->save();
// SOFT DELETE REQUEST BEING MARKED ACCEPTED
$status = $this->delete();
if (!$status):
return false;
else:
return true;
endif;
}
}
I've tried both firstOrCreate and firstOrNew as shown but with both times 'friend_id' and 'user_id' given in the array are set as '0'.
There is no default on the rows or indexes.
Here's the Friend Eloquent Model
class Friend extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
public $table = 'friends';
protected $guarded = array('id');
public function user() {
return $this->hasOne('User', 'id', 'user_id');
}
public function friend() {
return $this->hasOne('User', 'id', 'friend_id');
}
}
The create() method does mass assignment and this is a big security issue, so Laravel has a protection against it. Internally it has guarded = ['*'], so all your columns will be protected against mass assignment. You have some options:
Set the fillable columns of your model:
class User extends Eloquent {
protected $fillable = array('first_name', 'last_name', 'email');
}
Or set only the ones you want to keep guarded:
class User extends Eloquent {
protected $guarded = array('password');
}
You may, at your own risk also do:
class User extends Eloquent {
protected $guarded = array();
}
Also on the difference between the firstorcreate, and firstornew:
The firstOrNew method, like firstOrCreate will attempt to locate a record in the database matching the given attributes. However, if a model is not found, a new model instance will be returned. Note that the model returned by firstOrNew has not yet been persisted to the database. You will need to call save manually to persist it:
You can also go through the Facade and use the follwing:
class Settings extends Eloquent
{
protected $table = 'settings';
protected $primaryKey = 'name';
public static function get($settingName)
{
return Settings::firstOrCreate(array('name' => $settingName));
}
}
I believe you should put your accept() function in one of your controllers instead of the model. I'm not sure how and where you're calling this function, but I think it's in the wrong place.

Categories