I basically want to do this: Add custom field to Laravel queued job records?
However the top-voted answer there doesn't really address how to do this, rather it advocates for a work-around.
I know in order to actually accomplish this I need to tap into the dispatch function. The issue is it isn't being called directly in my (in this case) event listener.
According to this and this dispatch is a global helper function. Where in the source code is this registered and how should I overwrite it in my event listener in order to run handle() (that which dispatch itself calls) in my Job class after a specified delay and yet be able to add additional entries/fields to the database?
My prime reason for this is because:
1). The job is sending a notificaiton via email.
2). Emails should only go out after a delay IF the user hasn't logged in/been active during that delay.
3). AND emails should only go out if that user doesn't have another email queued up since the first was dispatched, in which case the first should be consolidated and deleted into the second email.
Therefore I need additional database fields in order to find, delete and modify entries in the jobs table.
These ideas come from an article on Medium which teaches that you don't want to spam users with emails/notifications and you need to consolidate/group/prioritize them and I'm trying to do this in Laravel.
I believe it is possible in Laravel but I am unsure how to overwrite the functionality when dispatch is a global and I don't know from where it is invoked. The Laravel docs on the Command Bus don't go beyond Laravel 5.0.
Edit: I have to use Redis now, according to this (because I am getting that beginTransaction() error in my queue):
Redis Driver > Database Driver
I think a model watcher would be best but I'm not sure if Job is an Eloquent model. I need something that will work consistently across database drivers.
When you create a job instance everything assigned to a public property is serialized and thereafter available to you during the job processing. You can pass in parameters to the job constructor and then assign them to properties like
public $foo;
public function __constructor($bar) {
$this->foo = $bar;
}
Then in the handle function you can get the value $bar from $this->foo
Edit: If found this method within the DatabaseQueue class. Perhaps you could override to suit your needs:
/**
* Create an array to insert for the given job.
*
* #param string|null $queue
* #param string $payload
* #param int $availableAt
* #param int $attempts
* #return array
*/
protected function buildDatabaseRecord($queue, $payload, $availableAt, $attempts = 0)
{
return [
'queue' => $queue,
'attempts' => $attempts,
'reserved_at' => null,
'available_at' => $availableAt,
'created_at' => $this->currentTime(),
'payload' => $payload,
];
}
Related
I am writing a plug-on that should add an (dynamic) attachment to the email that is send to the end user. But I am stuck on one thing.
Firstly I was using the EMAIL_ON_SEND hook to add an attachment to the email. But it seems that it will add a attachment to each email everytime it is called.
For each email it is called two times. So to the first mail it will add 2 attachments and for the second one 4, etc etc.
The second approach was to use the ON_SENT_EMAIL_TO_USER hook. But this one does not seems to be called before the email (in a segment) is send.
class EmailSubscriber extends CommonSubscriber
{
protected $helper;
public function __construct(IntegrationHelper $helper)
{
$this->helper = $helper;
$this->parser = new ApiParser();
}
/**
* #return array
*/
public static function getSubscribedEvents()
{
return [
// EmailEvents::EMAIL_ON_SEND => ['onEmailSend', 100],
EmailEvents::ON_SENT_EMAIL_TO_USER => ['onEmailSend', 100],
];
}
/**
* Search and replace tokens with content
*
* #param EmailSendEvent $event
*/
public function onEmailSend(EmailSendEvent $event)
{
error_log('123');
}
Someway I have to hook on the actual action that is sending the email instead of the event (?). But I can't figure out which one
I can't answer directly but might be able to point you at some useful resources!
Firstly, are you trying to send the email to the Mautic user (e.g. the administrator or the owner of the lead), or to the lead? Just wanted to double check we're looking at the right thing as they are often confused!
It also depends on what you're trying to do, attach a file which isn't currently part of Mautic (e.g. an invoice or something like that) or if you're trying to attach a file which you want to track in Mautic as an asset.
In terms of attachments, these resources from the developer documentation may be useful:
Mailhelper - https://developer.mautic.org/#mail-helper
Attachments - https://developer.mautic.org/#attachments
It references attachFile() but there is also attachAsset() which allows you to attach a Mautic asset you have already uploaded (\Mautic\AssetBundle\Entity\Asset).
You may also want to take a look at https://forums.mautic.org where there may be more developers from the community able to give some further insight!
Hey Firstly thank you for the response.
The hook is called multiple times so I needed to tweak it.
So we track where it is called and filter it.
Besides that we need to clean the attachments every time.
Anyway even if it is not that clean, it does the trick
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
if (strpos($trace[4]['file'], 'SendEmailToContact.php') !== false) {
$helper = $event->getHelper();
$messageChildren = $helper->message->getChildren();
if (count($messageChildren) > 0) {
$helper->message->detach($messageChildren[0]);
}
For context:
I am setting up a PubSub Emitter for snowplow. (For other readers PubSub is a simple queue on Google Cloud Platforms that takes in messages which are an array as input).
['data' => 'Name', 'attributes' => 'key pair values of whatever data you are sending']
The above is irrelevant except that I must create a custom Emitter class in order to achieve this goal since Google Cloud PubSub has some different connectors than the stereotypical http request/sockets/others that snowplow provides.
Actual problem:
I want to set a specific schema for each event I am sending. How do you associate the schema to each payload?
The PHP Tracker SyncEmitter (the most standard snowplow provided Emitter) doesn't allow any custom setting for the schema (as shown below)
private function getPostRequest($buffer) {
$data = array("schema" => self::POST_REQ_SCEHMA, "data" => $buffer);
return $data;
}
It is hardcoded in to every event tracked.
So I investigated. And read up on snowplow trackers a bit more. I am still baffled, and I know I can extend the Payload class and force my own schemas as a variable, but why is it not this way already? I am asking because I am assuming the opensource programmer did it right, and I am not understanding it correctly.
I figured it out.
The Tracker class contains trackUnstructuredEvent:
/**
* Tracks an unstructured event with the aforementioned metrics
*
* #param array $event_json - The properties of the event. Has two fields:
* - A "data" field containing the event properties and
* - A "schema" field identifying the schema against which the data is validated
* #param array|null $context - Event Context
* #param int|null $tstamp - Event Timestamp
*/
public function trackUnstructEvent($event_json, $context = NULL, $tstamp = NULL) {
$envelope = array("schema" => self::UNSTRUCT_EVENT_SCHEMA, "data" => $event_json);
$ep = new Payload($tstamp);
$ep->add("e", "ue");
$ep->addJson($envelope, $this->encode_base64, "ue_px", "ue_pr");
$this->track($ep, $context);
}
Which accepts the schema as input. Snowplow wants you to use the Tracker's default function and provided the above as a solution to my issue.
But it still has a schema wrapped around the data(that contains the input schema).... More questions from my own answer...
I am implementing PHP application with CQRS.
Let's say I have CreateOrderCommand and when I do
$command = new CreateOrderCommand(/** some data**/);
$this->commandBus->handle($command);
CommandBus now just pass command to proper CreateOrderCommandHandler class as easily as:
abstract class SimpleCommandBus implements CommandBus
{
/** #var ICommandHandlerLocator */
protected $locator;
/**
* Executes command
*
* #param Command $command
*/
public function handle(Command $command)
{
$handler = $this->locator->getCommandHandler($command);
$handler->handle($command);
}
}
Everything ok.
But handling is void method, so I do not know anything about progress or result. What can I do to be able to for example fire CreateOrderCommand and then in same process acquire newly created entity id (probably with some passive waiting for its creation)?
public function createNewOrder(/** some data**/){
$command = new CreateOrderCommand(/** some data**/);
$this->commandBus->handle($command);
// something that will wait until command is done
$createdOrder = // some magic that retrieves some adress to result data
return $createdOrder;
}
And to get closer of what CQRS can provide, command bus should be able to have RabbitMqCommandBus implementation that just serializes command and sends it to rabbit queue.
So, then the process that finally handles command might be some running consumer and some kind of communication between processes is needed here - to be able to somehow inform original user process from consumer, that it is done (with some information, for example id of new entity).
I know that there is solution with GUID - I could mark command with GUID. But then what:
public function createNewOrder(/** some data**/){
$command = new CreateOrderCommand(/** some data**/);
$this->commandBus->handle($command);
$guid = $command->getGuid();
// SOME IMPLEMENTATION
return $createdOrder;
}
SOME IMPLEMENTATION should do some checking of events (so I need to implement some event system too) on command with specific GUID, to be able to for example echo progress or on OrderCreatedEvent just return it's ID that I would get from that event. Consumer process that asynchronously handles command might for example feed events to rabbit and user client would taking them and do proper response (echo progress, return newly created entity for example).
But how to do that? And is solution with GUID the only one? What are acceptable implementations of solutions? Or, what point am I missing? :)
The easiest solution to get information about id of created aggregate/entity is to add it to the command. So the frontend generates the id and pass it with the data. But to make this solution works, you need to make use of uuid instead of normal database integers, otherwise you may find yourself duplicating identifiers on the db side.
If the command is async and perform so time consuming actions, you can for sure publish events from the consumer. So the client via.e.g. websockets receives the informations in real time.
Or ask the backend about existance of the order with the id from the command, from time to time and when the resource exists, redirect him to the right page.
I'm trying to write a functional test to check if one of the methods of our REST api correctly dispatches a job.
I know Laravel includes the expectsJobs method. But that only checks if the job was dispatched. I also need to check if the params it was instantiated with are correct.
That way, I could rely on this functional test + another unit test that checks if the job itself runs fine when instantiated with the correct parameters. Otherwise, I only know that:
the rest api call dispatches the job
the job works fine when instantiated with the correct params
But I don't know if the job gets instantiated with the correct params by the rest api call.
For clarity's sake, here is a bit of pseudo code:
Functional test for REST api call:
$this->expectsJobs(\App\Jobs\UpdatePricesJob);
$this->json("POST", "/api/resource/{$someresource->id}", [
"update_prices" => 1,
"prices" => [
/** the prices list **/
]
])->seeJson(["success" => true]);
/** The test succeeds if an UpdatePricesJob is dispatched **/
Unit test for UpdatePricesJob:
$someresource = Resource::find($someResourceId);
$newPrices = [ /** properly-formatted prices **/ ]
$this->dispatch(new UpdatePricesJob($someresource, $newPrices));
$this->assertEquals($someresource->prices, $newPrices);
/** The test succeeds if running UpdatePricesJob properly updates the resource prices to the ones specified in the job params **/
As you can see, there is no way to know if the REST api call instantiates UpdatePricesJob with some properly formatted prices.
Thanks in advance!
I'm using Propel 1 in a fairly sizeable project, and the live version presently uses the Archivable behaviour. Thus, when a row is deleted, the behaviour transparently intercepts the call and moves the row into an archive table. This works fine.
I'm looking to change how this table works so that all saves are versioned. On a feature branch I have therefore removed the Archivable and added the Versionable behaviour. This drops the (table)_archive auto-generated table and adds a (table)_version table instead.
However, interestingly, the version table has a PK of (id, version) with a foreign key to the live table from id to id. This means that versions cannot exist without a live row, which is not what I want: I want to be able to delete a row and preserve the versions.
I thought this behaviour would act like Archivable i.e. the delete() method would be intercepted and modified from its usual approach. Unfortunately, as confirmed by the documentation, this method deletes the live row and any prior versions:
void delete(): Deletes the object version history
I tried mixing both Archivable and Versionable, but this seems to generate code that crashes in the Query API: it tries to call an archive() method that does not exist. I expect this behaviour mix was never intended to work (ideally it should be caught at schema build-time, and perhaps that will be fixed in Propel 2).
One solution is to try the SoftDelete behaviour instead of Archivable - this just marks records as deleted rather than moving them to another table. However this can be problematic because joining to a table with this behaviour can give the wrong counts for non-deleted rows (and the Propel team decided to deprecate it for this reason). It also feels like a rabbit-hole I don't want to go down, since the amount of refactoring may spiral out of control.
Thus, I am left with seeking a better approach to implement a versioning system that does not delete old versions when the live copy is deleted. I can do this manually by intercepting save and delete methods in the model class, but it seems a waste when Versionable nearly does what I want. Are there relevant parameters I can tweak, or is there value in writing a custom behaviour? A quick look at the template generation code for core behaviours makes me want to run away from the latter!
Here is the solution I came up with. My memory is rather hazy but it looks like I've taken the existing VersionableBehaviour and derived from it a new behaviour, which I have called HistoryVersionableBehaviour. It thus uses all the features of the core behaviour and then just overrides the generated delete with its own code.
Here is the behaviour itself:
<?php
// This is how the versionable behaviour works
require_once dirname(__FILE__) . '/HistoryVersionableBehaviorObjectBuilderModifier.php';
class HistoryVersionableBehavior extends VersionableBehavior
{
/**
* Reset the FKs from CASCADE ON DELETE to no action
*
* (I expect all future migration diffs will incorrectly try to re-add the constraint
* I manually removed from the migration that introduced versioning, may try to fix
* that another time. 'Tis fine for now).
*/
public function addVersionTable()
{
parent::addVersionTable();
$this->swapAllForeignKeysToNoDeleteAction();
$this->addVersionArchivedColumn();
}
protected function swapAllForeignKeysToNoDeleteAction()
{
$versionTable = $this->lookupVersionTable();
$fks = $versionTable->getForeignKeys();
foreach ($fks as $fk)
{
$fk->setOnDelete(null);
}
}
protected function addVersionArchivedColumn()
{
$versionTable = $this->lookupVersionTable();
$versionTable->addColumn(array(
'name' => 'archived_at',
'type' => 'timestamp',
));
}
protected function lookupVersionTable()
{
$table = $this->getTable();
$versionTableName = $this->getParameter('version_table') ?
$this->getParameter('version_table') :
($table->getName() . '_version');
$database = $table->getDatabase();
return $database->getTable($versionTableName);
}
/**
* Point to the custom object builder class
*
* #return HistoryVersionableBehaviorObjectBuilderModifier
*/
public function getObjectBuilderModifier()
{
if (is_null($this->objectBuilderModifier)) {
$this->objectBuilderModifier = new HistoryVersionableBehaviorObjectBuilderModifier($this);
}
return $this->objectBuilderModifier;
}
}
This needs something called a modifier, which is run at generation time to produce the base instance classes:
<?php
class HistoryVersionableBehaviorObjectBuilderModifier extends \VersionableBehaviorObjectBuilderModifier
{
/**
* Don't do any version deletion after the main deletion
*
* #param \PHP5ObjectBuilder $builder
*/
public function postDelete(\PHP5ObjectBuilder $builder)
{
$this->builder = $builder;
$script = "// Look up the latest version
\$latestVersion = {$this->getVersionQueryClassName()}::create()->
filterBy{$this->table->getPhpName()}(\$this)->
orderByVersion(\Criteria::DESC)->
findOne(\$con);
\$latestVersion->
setArchivedAt(time())->
save(\$con);
";
return $script;
}
}
The parent class has 798 lines, so my approach does seem to have saved a great deal of code, over building it all from scratch!
You'll need to specify the behaviour in your XML file for each table you want to activate it for:
<table name="job">
<!--- your columns... -->
<behavior name="timestampable" />
<behavior name="history_versionable" />
</table>
I am not sure whether my behaviour requires the presence of the timestampable behaviour - my guess is no, since it looks like the parent behaviour just adds columns to the versioned table and not the table itself. If you are able to try this without the timestampable behaviour do let me know how you get on, so I can update this post.
Finally you'll need to specify the location of your class so the Propel 1 custom autoloader knows where to find it. I use this in my build.properties:
# Declare a custom behaviour
propel.behavior.history_versionable.class = ${propel.php.dir}.WebScraper.Behaviours.HistoryVersionable.HistoryVersionableBehavior