Add custom property to Mail in Laravel - php

I'm trying to get Postmark's new Message Streams working with Laravel.
Is there a way of adding the property + value '"MessageStream": "notifications" to the JSON body of an email sent using Laravel mail? I'm guessing I will need to extend the Mailable class in some way to do this.
Ideally, i'd like to be able to do something like the following in my Mailable class:
DiscountMailable.php
public function build()
{
return $this->from('hello#example.com')
->markdown('emails.coupons.created')
->subject('🎟 Your Discount')
->with([
'coupon' => $this->coupon,
])
->messageStream(
'notifications',
);
}

Just to make sure this question has an answer, this is what was necessary to make it work (and it worked for my issue in Laravel 7 today as well, where I wanted to use different Postmark Streams for different mail notification types).
public function build()
{
return $this->from('hello#example.com')
->markdown('emails.coupons.created')
->subject('🎟 Your Discount')
->with([
'coupon' => $this->coupon,
])
->withSwiftMessage(function ($message) {
$message->getHeaders()
->addTextHeader('X-PM-Message-Stream', 'notifications')
});
}

Related

Laravel 9: Malformed characters for emails in laravel.log

If I adjust in my Laravel 9 project (PHP 8), the .env - file to MAIL_MAILER=log.
The mail is saved in the laravel.log file, but the problem is, that some characters are malformed. For example the mail-<head> looks like this:
<head>
<meta charset=3D"utf-8">
<meta name=3D="viewport" content=3D"width=3Ddevice-width, initial-scale=3D1.0">
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3DUTF-8">
<meta name=3D"color-scheme" content=3D"light">
<meta name=3D"supported-color-schemes" content=3D"light">
</head>
The malformed characters also occur for äöü and other UTF-8 characters (e.g. ä resolves to =C3=A4).
If I'm using MAIL_MAILER=smtp the mail isn't malformed at all.
This makes local email debugging hard.
Anyway, this probably causes another problem on production. I'm using the package (https://github.com/shvetsgroup/laravel-email-database-log) to save all sent mails of Laravel in the database. Here the malformed characters are also saved in the database.
I'm sending mails like this:
\Illuminate\Support\Facades\Mail::to('mail#mail.de')->queue(new \App\Mail\ContactConfirmation(
$name,
$message
));
class ContactConfirmation extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public string $name,
public string $text
)
{
//
}
public function build()
{
return $this->markdown('mails.contact_confirmation')
->subject('Your message')
->with([
'name' => $this->name,
'text' => $this->text
]);
}
}
This problem looks similar to https://github.com/laravel/framework/issues/32954, but the mails sent by SMTP have no problem and Laravel 9 uses the Symfony Mailer. There is also a similar question (Why does Laravel replace a tab by a "=09" string when sending mails?) from 2014, but here is SwiftMailer used.
Is there some way to fix the malformed characters on production and local? And if not, are there alternatives to save the mail without the package and malformed characters into the database?
A work around to this it could be to create a new channel for logging. On laravel documentation https://laravel.com/docs/9.x/logging#creating-custom-channels-via-factories it is explained it how to do it.
The idea is that when a log for mailer is used for sending emails, if the message is quoted-printable, then it will be decoded it back to '8bit' string.
To achieve this a new Logger is created that extends from Illuminate\Log\Logger and override the debug method with the code that convert to '8bit'string.
First, create a class that extends from Illuminate\Log\Logger. For example App\Mail\Logging\CustomLogger.php This contains the override to debug method.
namespace App\Mail\Logging;
use Illuminate\Log\Logger;
class CustomLogger extends Logger
{
public function debug($message, array $context = []): void
{
$message = str_contains($message, "=3D") ? quoted_printable_decode($message) : $message;
$this->writeLog(__FUNCTION__, $message, $context);
}
}
Then create the logger class that will resolve the customLogger previously. Example app\Logging\CreateCustomMailLogger.php
namespace App\Logging;
use App\Mail\Logging\CustomLogger;
class CreateCustomMailLogger
{
public function __invoke(array $config)
{
/** #var \Illuminate\Log\Logger $log **/
$log = resolve('log');
return new CustomLogger($log->getLogger(), $log->getEventDispatcher());
}
}
In config\logging.php file add a new channel using the CreateCustomMailLogger created before.
'maillog' => [
'driver' => 'custom',
'via' => \App\Logging\CreateCustomMailLogger::class
],
In the .env file set MAIL_LOG_CHANNEL to maillog, the channel in logging.php created before
MAIL_MAILER=log
MAIL_LOG_CHANNEL=maillog
This way when you set mailer to log, then this channel will be used and it will be logged as '8bit' string and not quoted-printable.
For futher details, check this reply from the same issue.
https://github.com/laravel/framework/issues/32954#issuecomment-1335488478
Hope this help!

Telegram Laravel Bot Reply

I'm pretty new to laravel and Telegram API and I want to develop a bot that can reply your input messages.
For now I can send messages when I refresh a route, but I would like it to send a message and reply once your start a conversation with him.
I'm using this sdk: https://telegram-bot-sdk.readme.io/docs
and so far I managed to call the send message method:
class TelegramController extends Controller {
public function index(){
$chatID = 'xxxxxxx';
Telegram::sendMessage([
'chat_id' => $chatID,
'text' => '[▼皿▼]'
]);;
}
}
How can I add some sort of listener that can reply to user input?
Thanks in advance.
At first, you should add the route in routes/web.php, like this:
Route::post('/bot_webhook', 'TelegramController#index')->middleware('api');
And you should get update improve TelegramController like this:
class TelegramController extends Controller {
public function index(){
$update = json_decode(file_get_contents('php://input'));
# I'm not sure what library do you use, but it should work if it was like this
Telegram::sendMessage([
'chat_id' => $update->message->chat->id,
'text' => '[▼皿▼]'
]);;
}
}

How to check for required fields in object?

i have example object with fields
name => John
surname => Dow
job => engineer
and output form with placeholders. some required, some not.
what is best practice for check if it requred and show error with null fields?
There are multiple ways actually you can do that inside of controller method or make use of Laravels Request Classes for me I prefer to use Request Classes
look below I will list the two examples
Validate inside the controller's method
public function test(Request $request){
if($request->filled('name){
/*filled will check that name is set on the current
request and not empty*/
//Do your logic here
}
}
Second way is by using the Validator Facade inside your controller
use Validator;
class TestController{
public function test(Request $request){
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
/*continue with your logic here if the request failed on
validator test Laravel will automatically redirect back
with errors*/
}
}
Third way my favorite one personally
you can generate a Request class using this command
php artisan make:request AddBookRequest
that will generate the request class under "app/Http/Requests/AddBookRequest" , inside of any generated request class you will find two methods authorize() and rules()
in the authorized method you have to return truthy or falsy value this will detect if the current user making the request has authorization to fire this request inside of the rules method you do pretty much as you did in the Validator in the second way check the example
public function authorize(){
return true;
}
public function rules(){
return [
'title' => 'required|string',
'author_id' => 'required|integer'
];
}
then simply in your controller you can use the generated request like this
use App\Http\Requests\AddBookRequest;
public function store(AddBookRequest $request){
/* do your logic here since we uses a request class if it fails
then redirect back with errors will be automatically returned*/
}
Hope this helps you can read more about validation at
https://laravel.com/docs/5.6/validation
I think "simple is the best", just through object and check if properties exists
Ref: property_exists
Example:
if (property_exists($object, 'name')) {
//...do something for exists property
} else {
//...else
}

How can i created unit testing to Graphql application with Laravel?

I created an application with Laravel using Graphql. But I dont how to create my unit tests. Only do it simple request with PHPUnit or have other better way to do it?
Example Query Graphql in Laravel:
class ClientQuery extends Query
{
protected $attributes = [
'name' => 'ClientQuery',
'description' => 'A query'
];
public function type()
{
return GraphQL::type("Client");
}
public function args()
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
],
];
}
public function resolve($root, $args, $context, ResolveInfo $info)
{
return Client::find($args['id']);
}
}
I'm currently using GraphQL like you.
Because I've used GraphQL files to describe my data model, I have only my resolver in PHP (like you no?).
I think you can create Unit test but it is not necessary because is the role of data model to avoid errors of Type. But you can make it to perform a better product/platform yes.
In this way, Integration tests are very important, to test resolvers.
I'm currently using API call to test resolvers, but it can change because I'm not an expert and I don't think it's the better way but for now that make the job.

How to perform additional tasks in Yii2 restful controller?

Here is how my RESTful controller looks like.
<?php
namespace backend\controllers;
use yii\rest\Controller;
use yii;
use yii\web\Response;
use yii\helpers\ArrayHelper;
class UserController extends \yii\rest\ActiveController
{
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
[
'class' => 'yii\filters\ContentNegotiator',
'only' => ['view', 'index'], // in a controller
// if in a module, use the following IDs for user actions
// 'only' => ['user/view', 'user/index']
'formats' => [
'application/json' => Response::FORMAT_JSON,
],
'languages' => [
'en',
'de',
],
],
[
'class' => \yii\filters\Cors::className(),
'cors' => [
'Origin' => ['*'],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => true,
'Access-Control-Max-Age' => 86400,
],
],
]);
}
public $modelClass = 'backend\models\User';
public function actions()
{
}
public function sendMail(){
//Need to call this function on every create
//This should also have the information about the newly created user
}
}
It works very well with default behavior but it is not very practical that you will just create the user and exit. You need to send email with verification link SMS etc, may be update some other models based on this action.
I do not want to completely override the create method as it works well to save data and return back JSON.
I just want to extend its functionality by adding a callback kind of a function which can accept the newly created user and send an email to the person.
Take a look here: https://github.com/githubjeka/yii2-rest/blob/bf034d26f90faa3023e5831d1eb165854c5c7aaf/rest/versions/v1/controllers/PostController.php
As you can see this is using the prepareDataProvider to change the normal way the index action is using. This is very handy. You can find the definition of prepareDataProvider here: http://www.yiiframework.com/doc-2.0/yii-rest-indexaction.html#prepareDataProvider()-detail
Now as you can see there are 2 additional methods afterRun() and beforeRun() that are also available for the create action. http://www.yiiframework.com/doc-2.0/yii-rest-createaction.html
You may be able to use these 2 functions and declare them similar to prepareDataProvider to do more things like sending an email. I have not tried them myself but I believe that should be the way to go.
The easiest way would be getting benefit from afterSave() method in your model. This method will be called after each save process.
public function afterSave($insert, $changedAttributes) {
//calling a send mail function
return parent::afterSave($insert, $changedAttributes);
}
Another advantage of this method is the data you have stored in your object model. For example accessing email field:
public function afterSave($insert, $changedAttributes) {
//calling a send mail function
\app\helpers\EmailHelper::send($this->email);
return parent::afterSave($insert, $changedAttributes);
}
the value of $this->email is containing the saving value into database.
Note
You can benefit from $this->isNewRecord to detect whether the model is saving new record into database or updating an existing record. Take a look:
public function afterSave($insert, $changedAttributes) {
if($this->isNewRecord){
//calling a send mail function
\app\helpers\EmailHelper::send(**$this->email**);
}
return parent::afterSave($insert, $changedAttributes);
}
Now, it only sends mail if new record is being saved into database.
Please note that you can also benefit from Yii2's EVENTS.
As official Yii2's documentation:
This method is called at the end of inserting or updating a record.
The default implementation will trigger an EVENT_AFTER_INSERT event when $insert is true, or an EVENT_AFTER_UPDATE event if $insert is false. The event class used is yii\db\AfterSaveEvent. When overriding this method, make sure you call the parent implementation so that the event is triggered.

Categories