Emitting a js event to Livewire with data - laravel

I have a component where each row is a separate component. I'm emitting an event using Echo + Pusher, and I want to scope the event to only refresh the relevant row.
This isn't described in the docs, but there's an old (and possibly outdated) video by Caleb Porzio (creator of Livewire) in this article (last video, ~8m30s) where this is done by adding the relevant ID into the event name.
In my livewire model, I have:
public function getListeners()
{
return [
"HitReceived:{$this->monitorId}" => 'refresh',
];
}
And in my JS I have:
Livewire.emit('HitReceived:' + data.monitorId);
I can see in the console that data.monitorId is set. When I fire an event with id #1, I get a 500 error:
ERROR: Undefined array key "HitReceived1" {"userId":1,"exception":"[object] (ErrorException(code: 0):
Undefined array key \"HitReceived1\" at /vendor/livewire/livewire/src/ComponentConcerns/ReceivesEvents.php:72)
(This all works if I don't scope my event to a particular model instance, but then everything refreshes.)

I was using $this->monitorId in my array of listeners:
public function getListeners()
{
return [
"HitReceived:{$this->monitorId}" => 'refresh',
];
}
But the variable was declared as protected:
protected $monitorId;
I can't quite see why this would have cause the error I was seeing, but changing it to a public property resolved the issue.

Related

Creating a Laravel attribute (accessor) on model but unable to access model properties

Here's my code:
protected function expires(): Attribute
{
if ($this->started_at) {
$expiry = $this->started_at->addDays(20);
}
return Attribute::make(
get: fn () => $expiry ?? null
);
}
Running this code gives me an ErrorException with the message Undefined property: Models\Job::$started_at
I have found that I can work around this error by accessing the property through $this->attributes['started_at'] as follows:
protected function expires(): Attribute
{
if ($this->attributes['started_at']) {
$expiry = Carbon::parse($this->attributes['started_at'])->addDays(20);
}
return Attribute::make(
get: fn () => $expiry ?? null
);
}
However, this code feels a little inefficient because I'm manually using Carbon to parse the property back into a Carbon object. But if I do a dd($this->started_at) right before the if statement, it's already been cast to a Carbon object by Laravel and I'd really just like to use this object to make my code as clean as in the first example above.
I'd like to know the reason why $this->started_at is apparently available as a Carbon object in this context but somehow not usable (an undefined property) in the way I'm using it, and also I would like to know if there is another way to go about achieving my goal?
you can add custom attributes with
public function getExpireAttribute()
{
if ($this->started_at) {
$this->started_at->addDays(20);
}
return $this->started_at;
}
now you can access expire attribute like other, with
$model->expire
to make Eloquent casts dates to Carbon for you, add attribute to casts:
protected $casts = [
'started_at' => 'datetime',
];
The reason you are getting an "Undefined property" error when trying to access $this->started_at in your accessor method is because Laravel's model accessor methods are executed before the model attributes are hydrated.
This means that when your expires() method is executed, the started_at attribute may not have been set yet, and thus accessing it directly on the model instance will result in an "Undefined property" error.
One way to work around this is to use the getAttribute method provided by Laravel's Model class. This method allows you to retrieve the value of an attribute, even if it has not been set yet. Here's an updated version of your expires() method that uses getAttribute:
use Carbon\Carbon;
protected function getExpiresAttribute(): ?Carbon
{
$startedAt = $this->getAttribute('started_at');
if ($startedAt) {
return $startedAt->addDays(20);
}
return null;
}
In this version, we are using the getAttribute method to retrieve the value of the started_at attribute, even if it has not been set yet. We then use Carbon to manipulate the date, and return the result.
Note that we are using the getExpiresAttribute method instead of the expires method, because Laravel automatically maps get{AttributeName}Attribute method calls to corresponding attribute accessors. So, in this case, calling
$model->expires
will automatically execute the getExpiresAttribute method.
With this approach, you can use the started_at property directly in your code, and it will be automatically cast to a Carbon object by Laravel, without the need to manually parse it with Carbon.
Hope this helps.

Calling of afterSave method in application.php file slowing down complete platform and showing memory_limit exceed error

I am calling afterSave method in application.php to perform action on all models saving event. Issue is whenever I using SAVE method inside afterSave method application showing fatal error:
SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
Point is same method working fine in specific model, without any memory exhausted error, I think there is something that need to be fixed over database connection. Below is the code which one I am trying.
//Application.php file
namespace App;
...
...
\Cake\Event\EventManager::instance()->on(
'Model.afterSave',
function (
\Cake\Event\EventInterface $event,
\Cake\Datasource\EntityInterface $entity,
\ArrayObject $options
) {
$auth_data = isset($_SESSION['Auth'])?$_SESSION['Auth']:[];
$ActionLogs = TableRegistry::get('ActionLogs');
$ActionLogsEntity = $ActionLogs->newEmptyEntity();
$ActionLogsEntity->change_log = $change_log;
$ActionLogsEntity->action_taken_by = $auth_data->username;
$ActionLogs->save($ActionLogsEntity); //This statement working fine in specific modelTable
class Application extends BaseApplication
implements AuthenticationServiceProviderInterface
{
...
...
}
Aside from the fact that the code should go into the Application class' bootstrap() method as mentioned in the comments, when you save inside of an afterSave event that listens to all models, then you naturally create a recursion, as saving the log will trigger an afterSave event too.
You have to put a safeguard in place that prevents the logging logic from running when the afterSave event belongs to the logging model, for example:
if ($event->getSubject() instanceof \App\Model\Table\ActionLogsTable) {
return;
}
// ...

OctoberCMS: How to use a component's functions without knowing the component's name

In OctoberCMS, I would like to change a page process simply by attaching a different plugin component.
I have a plugin component (makeform) which inserts a form (defined in a database table).
Clicking the form's submit button calls onSubmit() which calls process().
process() is a function defined in another plugin component.
i) Can I call process() from within makeform without knowing the name of the other plugin or its component? That is, without having to use 'Acme\Plugin\Components\ProcessForm', 'processForm';
ii) Alternatively, can I programmatically discover the name of the other attached component and it's plugin, and then somehow specify it's process() function?
iii) Or is it a case of using a static properties dropdown to choose which process, and then adding the component dynamically. Always assuming I can $this->addComponent('Acme\Plugin\Components\ProcessForm', 'processForm'); beyond init().
Edit: Experimentation
I was hoping to dynamically addComponent().
Wherever I place it, in init() or elsewhere, I get the error:
Class name is not registered for the component "Acme\Plugin\Components\ProcessForm". Check the component plugin.
Even if I'm not using one of it's classes.
Many online references to this error message, but not one that has helped me.
Edit: Further explanation
A (hopefully) simplified explanation of what I'm trying to achieve.
In essence I imagine a page process consisting of a string of components.
Each component calls a function in the next component until the process ends.
The overall process can be modified simply by replacing components.
I am guessing the only way to connect components is by standardizing the function names. So this (probably?) requires components to belong to particular stages of the process, although it would be ideal if each of them can fit in at any stage (where appropriate).
illustration
extend the base component class and add your function und let the components extends from this?
make a new helper class with the function maybe call it static or so
add a global function
https://octobercms.com/forum/post/global-php-classesfunctions
I think the best approach would be to define another property in which you set the namespace of the plugin.
public function defineProperties(){
'pluginName' => [
'label' => 'Plugin Namespace to call process method from',
'type' => 'text'
]
}
--
public function onSubmit(){
$plugin = $this->property('pluginName');
$plugin::process($formData);
}
In this way you keep the component logic clean from any hardcoded plugin names.
Edit: 30/10/17
I'm not sure there's a way to list all available components inside the application. An alternative is to set up a Settings page, with a repeater of sorts in which you declare all available components with namespaces.
You than parse this to an array inside the onSubmit method and return this to the dropdown.
public function defineProperties(){
'components' => [
'label' => 'Plugin Namespace to call process method from',
'type' => 'dropdown',
'options' => 'getComponentsOptions' // optional but easier to understand
]
}
public function getComponentsOptions(){
$components = Settings::get('components');
$options = [];
foreach ($components as $component)
{
$options[$component['namespace']] = $component['name'];
}
return $options;
}
/Models/Settings/fields.yaml
fields:
components:
type: repeater
form:
fields:
name:
placeholder: My Component Name
span: left
namespace:
placeholder: Acme\Name\Components\MyComponent;
span: right
/Models/Settings.php
class Settings extends Model
{
public $implement = ['System.Behaviors.SettingsModel'];
// A unique code
public $settingsCode = 'acme_name_settings';
// Reference to field configuration
public $settingsFields = 'fields.yaml';
}
http://octobercms.com/docs/plugin/settings#introduction

Laravel 5 queue listener exceptions

I'm trying to get queues working in laravel 5 and the queue listener is outputting:
php artisan queue:listen
[ErrorException]
Undefined index: table
The "jobs" and "failed_jobs" tables are present, config.php is set to "database".
A search of the laravel forum and google has not yielded a solution, amy ideas where to look?
This is most likely not a fault of the Laravel Queue system. Instead, this error is thrown by PHP when an array is to be accessed by an unknown/unset element.
"Notice: Undefined variable", "Notice: Undefined index", and "Notice: Undefined offset" using PHP
For example:
// Initialise an empty array
$myArray = [];
// Attempt to access an element that hasn't been set yet
echo $myArray['breadCake'];
In your case, have a look through your code that deals with queueing and search for ['table'], ["table"] or anything that would need a "table" set.
It may be handy to anyone reading this and considering using Laravel Queues to remember a few things:
Queues are asynchronous and do not have access to the variables you once set in the application unless you pass them into something queue-able.
In Laravel 5, capture all the data you require for a job to exist in the Event __construct() method. Events have access to a trait called SerializesModels. You can pass your models as instances (with attributes) to the __construct() method (such as __construct(User $user). Assign your variables to the Event class scope (for example: $this->user = $user). This is passed to the EventListener handle(Event $event) method. This is called when the queue is being processed. The __construct() should be blank (or filled in with repositories / services / other handy bits and pieces).
You can access the objects passed to the handle(Event $event) method:
public function handle(MyEvent $myEvent)
{
$this->user = $myEvent->user;
$mailData = ['user'=>$this->user];
Mail::queue('viewdir.view',$mailData, function($message) use ($mailData) {
$message->to($mailData['user']->email);
// other stuff
});
}
I hope this helps anyone reading.
So I had the same problem but I discovered that I had set my driver to sync while using the database as the sync driver. setting my driver as database solved it for me

Laravel 4 how to listen to a model event?

I want to have an event listener binding with a model event updating.
For instance, after a post is updated, there's an alert notifying the updated post title, how to write an event listener to have the notifying (with the post title value passing to the listener?
This post:
http://driesvints.com/blog/using-laravel-4-model-events/
Shows you how to set up event listeners using the "boot()" static function inside the model:
class Post extends eloquent {
public static function boot()
{
parent::boot();
static::creating(function($post)
{
$post->created_by = Auth::user()->id;
$post->updated_by = Auth::user()->id;
});
static::updating(function($post)
{
$post->updated_by = Auth::user()->id;
});
}
}
The list of events that #phill-sparks shared in his answer can be applied to individual modules.
The documentation briefly mentions Model Events. They've all got a helper function on the model so you don't need to know how they're constructed.
Eloquent models fire several events, allowing you to hook into various points in the model's lifecycle using the following methods: creating, created, updating, updated, saving, saved, deleting, deleted. If false is returned from the creating, updating, saving or deleting events, the action will be cancelled.
Project::creating(function($project) { }); // *
Project::created(function($project) { });
Project::updating(function($project) { }); // *
Project::updated(function($project) { });
Project::saving(function($project) { }); // *
Project::saved(function($project) { });
Project::deleting(function($project) { }); // *
Project::deleted(function($project) { });
If you return false from the functions marked * then they will cancel the operation.
For more detail, you can look through Illuminate/Database/Eloquent/Model and you will find all the events in there, look for uses of static::registerModelEvent and $this->fireModelEvent.
Events on Eloquent models are structured as eloquent.{$event}: {$class} and pass the model instance as a parameter.
I got stuck on this because I assumed subscribing to default model events like Event:listen('user.created',function($user) would have worked (as I said in a comment). So far I've seen these options work in the example of the default model user created event:
//This will work in general, but not in the start.php file
User::created(function($user)....
//this will work in the start.php file
Event::listen('eloquent.created: User', function($user)....
Event::listen('eloquent.created: ModelName', function(ModelName $model) {
//...
})

Resources