I am building a site where users can buy a plan for whatever estimated time they require. After they buy the plan I created a middleware to check when the plan is almost finished (70% into the plan) then send a notification . Now the issue is I am trying to find out if the Notification in question has already been sent before sending another notification. I used database notification method.
To achieve this I had to create a Notification model to interact with my notification table.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Notification extends Model
{
protected $table= "notifications";
}
The middle ware in question
public function handle($request, Closure $next)
{
// dd(Carbon::now()->format("Y-m-d"));
$plan = Booking::where("user_id", Auth::id())
->where("approve", true)
->where("end_date", '>', Carbon::now()->format("Y-m-d"))
->get();
$i =0;
$save = [];
foreach($plan as $plan){
$save[$i] = (new Plan)->percentageMeter($plan->start_date, $plan->end_date);
if($save[$i] >= 70){
$realNotify= Notification::where("notifiable_id", $plan->user_id)
->get();
foreach($realNotify as $notify){
if($notify->data['plan_id'] == $plan->id && realNotify ==true ){
continue;
}
else{
ComNotify::planAboutToExpire(Booking::find($plan->id));
}
}
continue;
}
$i++;
}
// dd($save);
}
one of the major reason I have this problem is that I cant access the "plan_id" object because it does not have its own table column.
Is there a Laravel way to do this? a neat way.
public function toArray($notifiable)
{
return [
"type"=>"plan_about_to_expire",
"message"=>"Your plan will soon expire on " . $this->plan->end_date,
"plan_id"=>$this->plan->id,
];
}
My notification toArray method
Not sure what version you are using,
however in 5.3 and beyond you could use the notifications database and check if the user has that 'type' of notification.
Related
I have this class
class Api extends \Magento\Framework\Model\AbstractModel
{
public function __construct(
\Magento\Framework\Message\ManagerInterface $messageManager,
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\MyModule\Payment\Helper\Data $helper
) {
$this->messageManager = $messageManager;
$this->scopeConfig = $scopeConfig;
$this->storeManager = $storeManager;
$this->helper = $helper;
$this->contentType = $this->helper->getConfigData('content_type');
}
.
.
.
function createOnlinePurchase($authToken, $lastOrder)
{
.
.
.
//here I pass lastOrder's increment id to my payment gateway
$lastOrder->setData('test','test data');
$lastOrder->save();
//make payment gateway api call, get payment url
return url;
}
}
this class is then used by a custom controller:
class Index extends \Magento\Framework\App\Action\Action
{
public function __construct(
\Magento\Framework\App\Action\Context $context,
\MyModule\Payment\Model\Api $moduleApi,
\Magento\Checkout\Model\Session $session
) {
parent::__construct($context);
$this->moduleApi = $moduleApi;
$this->session = $session;
}
public function execute() {
$token = $this->moduleApi->requestAuthToken();
if ($this->session->getLastRealOrder() && $token !== null) {
$url = $this->moduleApi->createOnlinePurchase($token, $this->session->getLastRealOrder());
if ($url !== null && substr($url, 0, 4) == 'http') {
$this->session->clearStorage();
return $this->resultRedirectFactory->create()->setUrl($url);
}
else {
$this->messageManager->addError(__("Url invalid: ".$url));
return $this->resultRedirectFactory->create()->setPath('checkout/onepage/failure');
}
}
}
on a SECOND custom controller Callback, which is triggered by my payment gateway, I retrieved the order object using $order = $this->getOrderFactory->create()->loadByIncrementId($incrementId)
where $this->getOrderFactory is an instance of \Magento\Sales\Model\OrderFactory I injected.
I got the increment id back from my payment gateway.
Somehow within this Callback class, when I use $order->getData('test'), I get nothing
My question is
Is there some core magento concept I'm missing here?
Or is there any other way I can retrieve this test data in Callback which only have the information of increment Id (because at the point of Callback, user have already left magento and come back)
It's weird to me beause I can edit and save the order from Callback but my extra data is not saved/associated with the order object itself
Thanks in advance!
UPDATE
I confirmed that I'm getting the same order object(row) by using the order id I get from my payment gateway as the one from session's Last Order
I called addStatusHistoryComment on lastOrder in Api class above and also called addStatusHistoryComment in my Callback class
both calls are updating the same order in my admin dashboard
I have also confirmed calling getData('test') right after I set it gives me the data I want.
So I don't understand why getData doesn't work when called from Callback
You can't just add data to order object, every model is mapped automatically to DB tables columns, object will store temporarily the data and not error out but it will not persist in database. The reason why comments work, is because they have a place in database and on save and on load there is extra code that saves and adds it to the model.
In order to persists new data in the order you need to add new order attribute or simply add a new column on sales order table. When saving, key much match exactly the name of the new column you created.
I ended up using setCustomerNote in place of setData with a custom key, which is weird that it works because it is literally doing:
return $this->setData(OrderInterface::CUSTOMER_NOTE, $customerNote);
I can only assume on magento 2.4.x (which is what i'm using btw), setData is restricted to predefined keys only.
So I have a complicated onboarding process that does several steps. I created a class that handles the process but I've added a few more steps and I'd like to refactor this into something a bit more manageable. I refactored to use Laravel's pipeline, but feel this may not be the best refactor due to the output needing to be modified before each step.
Here is an example before and after with some pseudo code.
before
class OnboardingClass {
public $user;
public $conversation;
public function create($firstName, $lastName, $email){
// Step 1
$user = User::create();
// Step 2
$conversation = Conversation::create(); // store information for new user + existing user
// Step 3
$conversation->messages()->create(); // store a message on the conversation
// Step 4
// Send api request to analytics
// Step 5
// Send api request to other service
return $this;
}
}
after
class OnboardingClass{
public $user;
public $conversation;
public function create($firstName, $lastName, $email){
$data = ['first_name' => $firstName, ...]; // form data
$pipeline = app(Pipeline::Class);
$pipeline->send($data)
->through([
CreateUser::class,
CreateNewUserConversation::class,
AddWelcomeMessageToConversation::class,
...
])->then(function($data){
// set all properties returned from last class in pipeline.
$this->user = $data['user'];
$this->conversation = $data['conversation'];
});
return $this;
}
}
Now within each class I modify the previous data and output a modified version something like this
class CreateUser implements Pipe {
public function handle($data, Closure $next) {
// do some stuff
$user = User::create():
return $next([
'user' => $user,
'other' => 'something else'
]);
}
}
In my controller I am simply calling the create method.
class someController() {
public function store($request){
$onboarding = app(OnboardingClass::class);
$onboarding->create('John', 'Doe', 'john#example.com');
}
}
So the first pipe receives the raw form fields and outputs what the second pipe needs to get the job done in its class, then the next class outputs the data required by the next class, so on and so forth. The data that comes into each pipe is not the same each time and you cannot modify the order.
Feels a bit weird and I'm sure there is a cleaner way to handle this.
Any design pattern I can utilize to clean this up a bit?
I think you could try using Laravel Service Provider, for example, you could build a login service provider; or Event & Listener, for example, you could build an listener for login and triggers a event to handle all the necessary logics. Can't really tell which one is the best since outcome is the same and it makes same amount of network requests, but it's more on personal preferences
I'm using PHPUnit to create a Unit test for a store function that stores data in a database.
Currently, i have a test verifies that it has stored data.
However, I also want to create a test that proves that a laravel log message has been produced if the model save function fails.
The code below shows the store function. The "log::info" is the line I want to test.
Thanks.
public function store(Venue $venue){
$saved = $venue->save();
if($saved == false){
Log::info('Failed to Save Venue'. $venue);
}
}
This what I have so far, i pass an empty model that will cause the save to fail due to database constraints
public function test_venue_store_failed(){
$venue = new Venue();
$venueRepo = new VenueRepository();
$this->withExceptionHandling();
$venueRepo->store($venue);
}
You can mock the Log facade in your unit test as follows, as per the docs:
public function test_venue_store_failed(){
$venue = new Venue();
Log::shouldReceive('info')
->with('Failed to Save Venue'. $venue);
$venueRepo = new VenueRepository();
$this->withExceptionHandling();
$venueRepo->store($venue);
}
Maybe you can use event listener on Models.
using this you can get logs on Create or other Events.
Check out the Example Below.
Hope to help .
Info 1.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use User;
class Venue extends Model
{
protected $fillable = ['title', 'ect..'];
public static function boot() {
parent::boot();
static::created(function($item) {
\Log::info('venue.created');
});
static::updated(function($item) {
\Log::info('venue.created');
});
static::deleted(function($item) {
\Log::info('venue.created');
});
}
}
Info 2.
Also there is an exists method on model
if ($venue->exists()) {
// saved
} else {
// not saved
}
Info 3
To get the insert queries when $venue->save(); error, you can try to catch the exception like this:
try{
$venue = new Venue;
$venue->fields = 'example';
$venue->save(); // returns false
}
catch(\Exception $e){
// do task when error
\Log::info($e->getMessage()); // insert query
}
Hope this helps :)
With Laravel, I can create database notifications.
Assuming, my notifications are about Resources (a model named Resource), I can give a resource instance to the notification constructor:
public function __construct(Resource $resource)
{
$this->resource = $resource;
}
// ...
public function toArray($notifiable)
{
return [
'resource_id' => $this->resource->id
];
}
I would like to load notifications for a user and display some resources information. I can get all notifications and do something like this:
foreach ($user->notifications as $notification) {
$resource_id = $notification->resource_id;
$resource = Resource::find($resource_id);
// Do something with resource information...
echo $resource->name;
}
But it's slow (one query for each notification) and not à la Laravel. I would prefer something like:
foreach ($user->notifications as $notification) {
// Do something with resource information...
echo $notification->resource->name;
}
Ideally, it would like to have some kind of eager loading. Is there an elegant (or at least working) way to do this?
You can reduce your queries down to one by doing:
Resource::whereIn('id', collect($user->notifications)->pluck('resource_id'))
This will only perform a single SQL query.
Digging deeper into the Laravel database notifications. The Laravel boilerplate notification may be able to work with:
class User extends Model {
// ...
public function resourceNotifications() {
$this->morphToMany(Resource::class, 'notifiable', 'notifications');
}
}
Then retrieving all the corresponding notifications is as simple as:
$notifications = $user->resourceNotifications()->get();
Have not tested that part though.
I am using Mailgun as a mail driver in my laravel application, as well as nexmo for SMS purposes.
What I am trying to achieve is to maintain the delivery status of the notifications that are sent either via Mailgun or Nexmo. Incase of Nexmo I am able achieve this, since I get the nexmo MessageId in the NotificationSent event that is fired after processing a notification.
However in the event instance for email, the response is empty.
Any idea what I am missing or, how I can retrieve the mailgun message-id?
I have found a workaround that does the job for now. Not as neat as I want it to be, but posting for future references incase anyone needs this.
I have created a custom notification channel extending Illuminate\Notifications\Channels\MailChannel
class EmailChannel extends MailChannel
{
/**
* Send the given notification.
*
* #param mixed $notifiable
* #param \Illuminate\Notifications\Notification $notification
* #return void
*/
public function send($notifiable, Notification $notification)
{
if (! $notifiable->routeNotificationFor('mail')) {
return;
}
$message = $notification->toMail($notifiable);
if ($message instanceof Mailable) {
return $message->send($this->mailer);
}
$this->mailer->send($message->view, $message->data(), function ($m) use ($notifiable, $notification, $message) {
$recipients = empty($message->to) ? $notifiable->routeNotificationFor('mail') : $message->to;
if (! empty($message->from)) {
$m->from($message->from[0], isset($message->from[1]) ? $message->from[1] : null);
}
if (is_array($recipients)) {
$m->bcc($recipients);
} else {
$m->to($recipients);
}
if ($message->cc) {
$m->cc($message->cc);
}
if (! empty($message->replyTo)) {
$m->replyTo($message->replyTo[0], isset($message->replyTo[1]) ? $message->replyTo[1] : null);
}
$m->subject($message->subject ?: Str::title(
Str::snake(class_basename($notification), ' ')
));
foreach ($message->attachments as $attachment) {
$m->attach($attachment['file'], $attachment['options']);
}
foreach ($message->rawAttachments as $attachment) {
$m->attachData($attachment['data'], $attachment['name'], $attachment['options']);
}
if (! is_null($message->priority)) {
$m->setPriority($message->priority);
}
$message = $notification->getMessage(); // I have this method in my notification class which returns an eloquent model
$message->email_id = $m->getSwiftMessage()->getId();
$message->save();
});
}
}
I am still looking for a solution to achieve this with NotificationSent event.
When looking at the code (MailgunTransport) it will do the following
$this->client->post($this->url, $this->payload($message, $to));
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
Since the Laravel contract requires the implementation to send back the number of e-mails send.
Even if you would be able to get into the mail transport it doesn't store the response from for this reason it not possible to catch the message id.
What you could do is to implement your own (or look in packagist) to adapt mail client but this is not a perfect solution and will require some ugly instanceof checks.