Im having trouble trying to get my model observer to work.. It is working as expected for create and deleted, but not for updating. Im guessing the event never fires. The thing is all of then are being done exactly the same way. Any ideas?
Below, my observer.
class GenericObserver extends AbstractObserver {
protected $events;
public function __construct(Dispatcher $dispatcher){
$this->events = $dispatcher;
}
public function saved($model) {
dd($this->events);
$user_id = Auth::user()->usr_id;
$user_nome = Auth::user()->usr_nome;
$user_email = Auth::user()->usr_email;
dd($model);
}
public function deleted($model) {
$user_id = Auth::user()->usr_id;
$user_nome = Auth::user()->usr_nome;
$user_email = Auth::user()->usr_email;
echo($model->getTable());
dd($model->getKeyName());
}
public function updated($model) {
$user_id = Auth::user()->usr_id;
$user_nome = Auth::user()->usr_nome;
$user_email = Auth::user()->usr_email;
dd($model);
}
public function saving($model){
echo 'Saving';
}
public function deleting($model){
echo 'Deleting';
}
public function updating($model){
echo 'Updating';
}
And here, my model class
Aplicacao extends Model {
protected $table = 'gst_aplicacoes';
protected $primaryKey = 'app_id';
protected $fillable = ['app_nome', 'app_key', 'app_observacao'];
public static function table() {
$model = new static;
return $model->getTable();
}
public static function boot() {
parent::boot();
Aplicacao::observe(new GenericObserver(new Dispatcher));
}
If anyone ever faces this issue, the reason the event was not firing was because the update method, only fire its events when the update happens directly on the model, since i was using an intermediary repository to represent my model, it wasn't working.
for more details.
https://github.com/laravel/framework/issues/11777#issuecomment-170388067
Observer work on only save() method. And it's not working on function of query builder that you call by magic methods. So on update() and created() observer is not work.
This will work:
Model::find($id)->update(['column' => $value]);
Related
Context: Trying to extend Laravel models from a database table. Table models linked to App\Models\BootableModel, App\Models\User extends this class.
I have the following code:
<?php
class BootableModel extends Model
{
protected $table = 'models';
protected $fillable = [
'name',
'class',
'table',
];
public function __construct(array $attributes = [])
{
$this->bootFromDatabase();
parent::__construct($attributes);
}
private function bootFromDatabase()
{
$class = static::class;
$bModClass = self::class;
Log::debug($class);
Log::debug($bModClass);
//$bootableModel = DB::table('models')->where('class', $class)->first();
$bootableModel = $bModClass::where('class', $class)->first();
if(!$bootableModel) {
return;
}
Log::debug($bootableModel->id);
The debug of $bModClass shows App\Models\BootableModel as expected (self vs static), but for some reason the $bModClass::where is trying to query the users table. Using a direct reference to App\Models\BootableModel::class does not change this, so it's not self::class that is the issue. The debug output as proof:
[2021-02-21 17:33:39] local.DEBUG: App\Models\User
[2021-02-21 17:33:39] local.DEBUG: App\Models\BootableModel
It should never try to access User::where(), and as such, it should never try to use User::$table either, but somehow it does.
Is Laravel doing some weird reflection, or is this normal PHP behavior? Is there a way around this?
Update:
I have found a workaround, but I'm not satisfied with this being the correct/only solution:
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
static::bootFromDatabase();
}
public static function bootFromDatabase()
{
$class = static::class;
$bModClass = self::class;
if($class === $bModClass) {
return;
}
Have you ever tried self::where(...) instead of $bModClass::where(...) ?
Similar situation:
class Base {
public static function where()
{
return 'where from base';
}
public static function getName()
{
return self::where();
}
}
class User extends Base {
public static function where()
{
return 'where from user';
}
}
echo User::getName();
Output: where from base
I have three models, Advertiser, PtcAd, and PtcCampaign. When deleting a Advertiser I want to delete all related PtcAds and PtcCampaigns. The Advertiser has many PtcCampaigns through PtcAds.
Advertiser Model
use SoftDeletes;
protected $dates = ['deleted_at'];
public function ptcAds()
{
return $this->hasMany('App\PtcAd');
}
public function ptcCampaigns()
{
return $this->hasManyThrough('App\PtcCampaign', 'App\PtcAd');
}
public function delete()
{
$this->ptcAds()->delete();
// I'VE TRIED WITH AND WITHOUT THIS
$this->ptcCampaigns()->delete();
return parent::delete();
}
PtcAd Model
use SoftDeletes;
protected $fillable = ['advertiser_id', 'title'];
protected $dates = ['deleted_at'];
public function advertiser()
{
return $this->belongsTo('App\Advertiser');
}
public function ptcCampaigns()
{
return $this->hasMany('App\ptcCampaign');
}
public function delete()
{
$this->ptcCampaigns()->delete();
return parent::delete();
}
PtcCampaign Model
use SoftDeletes;
public $timestamps = false;
protected $fillable = ['ptc_ad_id', 'clicks'];
protected $dates = ['paused_at', 'deleted_at'];
public function ptcAd()
{
return $this->belongsTo('App\PtcAd');
}
My tests:
public function test_delete_advertiser()
{
$advertiser = factory(Advertiser::class)->create();
$ptcAd = factory(PtcAd::class)->create(['advertiser_id' => $advertiser->id]);
$ptcCampaign = factory(PtcCampaign::class)->create(['ptc_ad_id' => $ptcAd->id]);
$this->assertTrue($advertiser->delete());
$this->assertFalse(Advertiser::all()->contains($advertiser));
$this->assertFalse(PtcAd::all()->contains($ptcAd));
// THE FOLLOWING TEST DOESN'T WORK!
$this->assertFalse(PtcCampaign::all()->contains($ptcCampaign));
}
// ALL OF THE FOLLOWING TESTS WORK!
public function test_delete_ad()
{
$ptcAd = factory(PtcAd::class)->create();
$ptcCampaign = factory(PtcCampaign::class)->create(['ptc_ad_id' => $ptcAd->id]);
$this->assertTrue($ptcAd->delete());
$this->assertFalse(PtcAd::all()->contains($ptcAd));
$this->assertFalse(PtcCampaign::all()->contains($ptcCampaign));
}
The $this->assertFalse(PtcCampaign::all()->contains($ptcCampaign)) in the test_delete_advertiser() test fails, why?
I have more tests to make sure all the relationships work so I really don't know what could possibly be wrong. My next attempt would be to make foreach in the Advertiser's delete() method but maybe there's something simpler and I want to understand why this doesn't work.
It looks the problem is with the sequence of delete statement.
Try by changing the sequence like below:
public function delete()
{
$this->ptcCampaigns()->delete();
$this->ptcAds()->delete();
return parent::delete();
}
You can use Laravel's Model Events (deleting) to delete related models like this:
class Advertiser extends Eloquent
{
public function ptcAds()
{
return $this->hasMany('PtcAd');
}
// this is a recommended way to declare event handlers
protected static function boot() {
parent::boot();
static::deleting(function($adv) { // before delete() method call this
$adv->ptcAds()->delete();
// do the rest of the cleanup...
});
}
}
// Same for PtcCompaigns
class PtcAd extends Eloquent
{
public function ptcCompaigns()
{
return $this->hasMany('PtcCompaigns');
}
// this is a recommended way to declare event handlers
protected static function boot() {
parent::boot();
static::deleting(function($ptc_ad) { // before delete() method call this
$ptc_ad->ptcCompaigns()->delete();
// do the rest of the cleanup...
});
}
}
Hope this helps!
I have function showItemList inside User class
class User extends Eloquent {
//...
protected $item = ['axe', 'sword', 'knife'];
public function showItemList() {
return $this->$item;
}
}
In my controller it's possible to use this.
$id = 1;
$user = User::find($id);
$user -> showItemList();
But how can I just call this function directly(irrelevant to $id query)?
I looks for something like below (sure now it's not working):
$list = User::showItemList();
You need to use the static protected variable and return it from static method.
class User extends Eloquent {
//...
static protected $item = ['axe', 'sword', 'knife'];
public static function showItemList() {
return self::$item;
}
}
I have a model with this code:
<?php
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class Intervention extends Eloquent {
use SoftDeletingTrait;
protected $fillable = array('start_date','stove_id','description','operation_mode','store_id','user_id','intervention_status_id','code');
public function operations()
{
return $this->hasMany('InterventionOperation');
}
public function store()
{
return $this->belongsTo('Store');
}
public function stove()
{
return $this->belongsTo('Stove');
}
public function user()
{
return $this->belongsTo('User');
}
public function statues()
{
return $this->hasMany('InterventionStatus');
}
then the boot
public static function boot()
{
parent::boot();
static::creating(function($intervention)
{
exit("creating");
});
static::created(function($intervention){
exit("created");
});
static::updating(function($intervention)
{
exit("updating");
});
}
the controller:
$intervention = new \Intervention(\Input::all());
$status = \Status::find(\Input::get('status')['id']);
$interventionStatus = new \InterventionStatus();
$interventionStatus->change_status_date = new \DateTime();
$interventionStatus->status()->associate($status);
$interventionStatus->description = "";
$user = \Auth::user();
$store = $user->store;
$intervention->store()->associate($store);
$intervention->user()->associate($user);
$intervention->request_date = new \DateTime();
$intervention->save();
...
but when save model, creating callback is not call.
I have try put exit("test") after parent::boot(); and exit is triggered.
If I put event's code in app/start/global.php it work.
I have try use the code in another model and work.
I do not know why it does not work.
Resolved:
I recreated the database and now everything works. Probably, in the various attempts to save, some relationship was skipped.
Thank you all for the help!
I think this has something to with the namespaces and registering the correct class in the event. Let's hack the source code a bit :)
In: /vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
Add:
public function getAllEvents()
{
return array_keys($this->listeners);
}
And call/dump Event::getAllEvents();
Try this for both cases (boot in the model and in the global.php) and compare.
I'm using laravel (4.2) framework to develop a web application (PHP 5.4.25). I've create a repository-interface that was implemented with eloquent-repository, I use that repository inside a UserController:
# app/controllers/UsersController.php
use Gas\Storage\User\UserRepositoryInterface as User;
class UsersController extends \BaseController {
protected $user;
public function __construct(User $user) {
$this->user = $user;
}
public function store() {
$input = Input::all();
$validator = Validator::make(Input::all(), $this->user->getRoles());
if ( $validator->passes() ) {
$this->user->getUser()->username = Input::get('username');
$this->user->getUser()->password = Hash::make(Input::get('password'));
$this->user->getUser()->first_name = Input::get('first_name');
$this->user->getUser()->last_name = Input::get('last_name');
$this->user->getUser()->email = Input::get('email');
$this->user->save();
return false;
} else {
return true;
}
}
}
My Repository implementation:
namespace Gas\Storage\User;
# app/lib/Gas/Storage/User/EloquentUserRepository.php
use User;
class EloquentUserRepository implements UserRepositoryInterface {
public $_eloquentUser;
public function __construct(User $user) {
$this->_eloquentUser = $user;
}
public function all()
{
return User::all();
}
public function find($id)
{
return User::find($id);
}
public function create($input)
{
return User::create($input);
}
public function save()
{
$this->_eloquentUser->save();
}
public function getRoles()
{
return User::$rules;
}
public function getUser()
{
return $this->_eloquentUser;
}
}
I've also create a UsersControllerTest to testing the controller and all works fine, the user was added to the DB. After I mocked my UserRepositoryInterface because I don't need to test the DB insert, but I just want to test the controller
class UsersControllerTest extends TestCase {
private $mock;
public function setUp() {
parent::setUp();
}
public function tearDown() {
Mockery::close();
}
public function mock($class) {
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function testStore() {
$this->mock = $this->mock('Gas\Storage\User\UserRepositoryInterface[save]');
$this->mock
->shouldReceive('save')
->once();
$data['username'] = 'xxxxxx';
$data['first_name'] = 'xxxx';
$data['last_name'] = 'xxxx';
$data['email'] = 'prova#gmail.com';
$data['password'] = 'password';
$data['password_confirmation'] = 'password';
$response = $this->call('POST', 'users', $data);
var_dump($response->getContent());
}
}
My ruote file:
Route::resource('users', 'UsersController');
When I run the test I get the following error:
Mockery\Exception\InvalidCountException : Method save() from Mockery_0_Gas_Storage_User_UserRepositoryInterface should be called
exactly 1 times but called 0 times.
Why the mocked method save has not be called?
What is wrong?
EDIT: without partial mock all works fine, now the question is: why with partial mock it doesn't work?
Thanks
Looking back at your code, it seems like you should be able to use partial mocks just by changing your mock function to something like this:
public function mock($class) {
$mock = Mockery::mock($class);
$ioc_binding = preg_replace('/\[.*\]/', '', $class);
$this->app->instance($ioc_binding, $mock);
return $mock;
}
You are telling the mock to expect the save() method, but the save() is on the Eloquent model inside the Repository, not the Repository you are mocking.
Your code is currently leaking details of the implementation of the Repository.
Instead of calling:
$this->user->getUser()->username = Input::get('username');
You need to pass an instance of the User into the Repository:
$this->user->add(User::create(Input::all());
Or you pass the array of Input into the Repository and allow the Repository to create a new User instance internally:
$this->user->add(Input::all());
You would then mock the add() method in your test:
$this->mock->shouldReceive('add')->once();
The comments about Laravel not being suited for mocking or unit testing are wrong.