My set-up comprises a lib folder with classes and a view folder with PHP files, that produce output. The views are imported inside a View class similar to this:
class View {
public function render(string $basename, Array $params) : string {
extract($params, EXTR_PREFIX_INVALID, 'v');
ob_start();
include sprintf('%s/views/%s.php', dirname(__DIR__), $basename);
$out = ob_get_contents();
ob_end_clean();
return $out;
}
}
I have basically two problems with Psalm in this situation:
For View::render it reports a UnresolvableInclude. I can even type the $basename with something like
#param "view1"|"view2"|"about" $basename
without effect. The unresolvable include remains.
The extract() puts the content of $params in the local scope, where the view files are included. This allows me to have
<?=escape($foo)?>
“tags” in my view files with $params === ['foo' => 'bar']. However, Psalm doesn’t catch up on this and reports a lot of UndefinedGlobalVariable problems.
My question: How can I tell psalm about the view files and the variables? Or alternatively, how can I re-structure this code so that psalm can test it for me?
There's a demo TemlateChecker plugin in Psalm's repo that seems to do something similar: it looks at the docblock in the view file for the tag like #variablesfrom ClassName::method and makes them available in the template file. Or just properties on $this variable from that method, not sure. It's also mentioned in Psalm docs: Checking non-PHP files.
Alternatively, you could wrap your template into a minimal method/function as technically view is just a function that takes a bunch of variables and returns a string: https://psalm.dev/r/66898ee87f
<?php class HomePageView { // view starts here
/** #param list<string> $sections */
public function render(
string $title,
array $sections
): string { ob_start();
?>
<html>
<head>
<title><?=$title?></title>
</head>
<body>
<?php foreach ($sections as $section): ?>
<section><?=$section?></section>
<?php endforeach; ?>
</body>
</html>
<?php return ob_get_contents(); }} // view ends here ?>
This way any tool that analyzes code (including Psalm, but not limited to) would be able to understand it.
I'm using Slim 3 to build an application.
In a certain route, I include via include_once the header of the template(slim/php-view 2.2), and there is some variables that I send to the template that I need to use in the header.
Is there a way to do this?
My route:
$app->get('/', function ( $request, $response) {
// some code here...
$somedata = ' this is just a test';
return $this->renderer->render($response, "/home.phtml",[
'somedata' => $somedata,
]);
})
The target template(home.phtml):
<?php include_once('myheader.php'); ?>
<h1>This is my template</h1>
<p> I need to use this variable <?=$somedata?>
in the included myheader.php file</p>
My bad. Reading the docs I found that I can achieve what I want using template variables, like this:
my route:
$templateVariables = [
"title" => "Title"
];
$phpView = new PhpRenderer("./path/to/templates", $templateVariables);
my included file(myheader.php):
<?php echo $title ?>
I'm making an admin setting section of my laravel 5.2 app using the storage package from thetispro/laravel5-setting.
I'd like my admin users to be able to update email copy that get sent out to the user, but some of the emails include variables such as the users name. "Thanks for shopping with us, CUSTOMER NAME".
I can easily store the following in a setting, but when blade outputs it it just prints it out as a string instead of a variable. I've tried escaped and nonescaped the characters with {{}} and {{!! !!}. Here's what I have:
Email message an admin user can edit:
<h2>Hi, {{ $user->name }}</h2>
<p>Welcome to my web app</p>
In my view I have:
{!! Setting::get('emailuserinvite') !!}
<br /><br />
<!-- Testing both escaped and nonescaped versions -->
{{ Setting::get('emailuserinvite') }}
What blade renders is just:
echo "<h2>Hi, {{ $user->name }}</h2>
<p>Welcome to my web app</p>";
I was trying to make a custom blade directive that could close the echo, display the variable and open the echo back up, but that doesn't seem to be working correctly either.
// AppServiceProvider
Blade::directive('echobreak', function ($expression) {
// echo "my string " . $var . " close string";
$var = $expression;
return "' . $var . '";
});
// Admin user settings
Hi #echobreak($user->name)
Welcome to my web app
Any advice would be appreciated! Thanks.
Update
I mocked up a simple test case using #abdou-tahiri's example but I'm still getting errors with the eval()'d code.
ErrorException in SettingController.php(26) : eval()'d code line 1: Undefined variable: user
And here is my simple controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use Blade;
class SettingController extends Controller
{
public function index() {
$user = [
"fname" => "Sam",
"lname" => "yerkes"];
$str = '{{ $user }}';
return $this->bladeCompile($str, $user);
}
private function bladeCompile($value, array $args = [])
{
$generated = \Blade::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
try {
eval('?>'.$generated);
}
catch (\Exception $e) {
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
}
You may need to compile the string using Blade , check this helper function :
function blade_compile($value, array $args = array())
{
$generated = \Blade::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
// We'll include the view contents for parsing within a catcher
// so we can avoid any WSOD errors. If an exception occurs we
// will throw it out to the exception handler.
try
{
eval('?>'.$generated);
}
// If we caught an exception, we'll silently flush the output
// buffer so that no partially rendered views get thrown out
// to the client and confuse the user with junk.
catch (\Exception $e)
{
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
so in your view file :
{!! blade_compile(Setting::get('emailuserinvite'),compact('user')) !!}
Check this Is there any way to compile a blade template from a string?
<h2>Hi, $user->name</h2>
<p>Welcome to my web app</p>
Is this what you are trying to do?
<h2>Hi, {{$user->name}}</h2>
<p>Welcome to my web app</p>
I want to send a confirmation e-mail using laravel.
The laravel Mail::send() function only seems to accept a path to a file on the system.
The problem is that my mailtemplates are stored in the database and not in a file on the system.
How can I pass plain content to the email?
Example:
$content = "Hi,welcome user!";
Mail::send($content,$data,function(){});
update on 7/20/2022: For more current versions of Laravel, the setBody() method in the Mail::send() example below has been replaced with the text() or html() methods.
update: In Laravel 5 you can use raw instead:
Mail::raw('Hi, welcome user!', function ($message) {
$message->to(..)
->subject(..);
});
This is how you do it:
Mail::send([], [], function ($message) {
$message->to(..)
->subject(..)
// here comes what you want
->setBody('Hi, welcome user!'); // assuming text/plain
// or:
->setBody('<h1>Hi, welcome user!</h1>', 'text/html'); // for HTML rich messages
});
For Html emails
Mail::send(array(), array(), function ($message) use ($html) {
$message->to(..)
->subject(..)
->from(..)
->setBody($html, 'text/html');
});
It is not directly related to the question, but for the ones that search for setting the plain text version of your email while keeping the custom HTML version, you can use this example :
Mail::raw([], function($message) {
$message->from('contact#company.com', 'Company name');
$message->to('johndoe#gmail.com');
$message->subject('5% off all our website');
$message->setBody( '<html><h1>5% off its awesome</h1><p>Go get it now !</p></html>', 'text/html' );
$message->addPart("5% off its awesome\n\nGo get it now!", 'text/plain');
});
If you would ask "but why not set first argument as plain text ?", I made a test and it only takes the html part, ignoring the raw part.
If you need to use additional variable, the anonymous function will need you to use use() statement as following :
Mail::raw([], function($message) use($html, $plain, $to, $subject, $formEmail, $formName){
$message->from($fromEmail, $fromName);
$message->to($to);
$message->subject($subject);
$message->setBody($html, 'text/html' ); // dont miss the '<html></html>' or your spam score will increase !
$message->addPart($plain, 'text/plain');
});
Hope it helps you folks.
The Mailer class passes a string to addContent which via various other methods calls views->make(). As a result passing a string of content directly won't work as it'll try and load a view by that name.
What you'll need to do is create a view which simply echos $content
// mail-template.php
<?php echo $content; ?>
And then insert your string into that view at runtime.
$content = "Hi,welcome user!";
$data = [
'content' => $content
];
Mail::send('mail-template', $data, function() { });
I had a similar issue where the HTML and/or plain text of my email were not built by a view and I didn't want to create a dummy view for them (as proposed by #Matthew Odedoyin).
As others have commented, you can use $this->html() to set the HTML content of the message, but what if you want your email to have both HTML and plain text content?
Unfortunately $this->text() only takes a view, but I got around this by using:
$this->text(new HtmlString('Here is the plain text content'));
Which renders the content of the HTMLString instead of the view.
try
public function build()
{
$message = 'Hi,welcome user!'
return $this->html($message)->subject($message);
}
as you know
Only mailables may be queued.
meaning, if you use ShouldQueue interface
1) first, you should always do
php artisan queue:restart
2) second, in your mailable you can use html method (tested in laravel 5.8)
public function build(): self
{
return $this
->html('
<html>
<body>
ForwardEmail
</body>
</html>
')
->subject(config('app.name') . ' ' . 'email forwarded')
->attachData($this->content, 'email.eml', [
'mime' => 'application/eml',
]);
}
If you were using mailables. You can do something like this in the build method :
public function build()
{
return $this->view('email')
->with(['html'=>'This is the message']);
}
And you just go ahead and create the blade view email.blade.php in your resource folder.
Then in the blade you can reference your string using laravel blade syntax
<html>
<body>
{{$html}}
</body>
</html>
or
<html>
<body>
{!!$html!!}
</body>
</html>
If your raw text contains HTML mark up
I hope this works for those who have templates stored in the database and wants to take advantage of the Mailables class in Laravel.
To send raw html, text etc using Laravel Mailables you can
override Mailable->send() in your Mailable and in there, use the method in previous responses:
send([], [], function($message){ $message->setBody() } )
No need to call $this->view() at your build function at all.
NOTE: Below answer is for those who are looking for a flexible approach. i,e (with or without laravel template)
With Template
$payload['message'] = View::make('emails.test-mail',$data)->render();
Without Template
$payload['message'] = "lorem ipsum";
Mail::raw([], function ($mail) use ($payload) {
$mail->from($payload['from_email'])
->to($payload['to'])
->setBody($payload['message'], 'text/html')
->cc($payload['cc'])
->bcc($payload['bcc'])
->subject($payload['subject']);
foreach ($payload['attachments'] as $file){
$mail->attach($file);
}
});
This can be accomplished within a Mailable implementation, with plain text and html content parts:
public function build() {
// Text and html content sections we wish to use in place of view output
$bodyHtml = ...
$bodyText = ...
// Internally, Mailer::renderView($view) interprets $view as the name of a blade template
// unless, instead of string, it is set to an object implementing Htmlable,
// in which case it returns the result $view->toHtml()
$htmlViewAlternative = new class($bodyHtml) implements Htmlable {
protected string $html;
public function __construct($html) {
$this->html = $html;
}
public function toHtml(): string {
return $this->html;
}
};
// We can now set both the html and text content sections without
// involving blade templates. One minor hitch is the Mailable::view($view)
// documents $view as being a string, which is incorrect if you follow
// the convoluted downstream logic.
/** #noinspection PhpParamsInspection */
return $this
->to(...)
->from(...)
->subject(...)
->view([
'html' => $htmlViewAlternative,
'raw' => $bodyText
]);
}
Laravel mailable now has an ->html() function to be used instead of ->view() and works both with o without ->text()
laravel 9 has built in function to send HTML without view. Here is the example:
\Illuminate\Support\Facades\Mail::html($content, function ($message) {
$message->to("email#example.com")
->subject("Test dev 4")
->from("email#example.com");
});
and also if we use accepted answer will return:
Symfony\Component\Mime\Message::setBody(): Argument #1 ($body) must be
of type ?Symfony\Component\Mime\Part\AbstractPart, string given,
called in
/Users/yaskur/Sites/laravel/mail-builder/vendor/laravel/framework/src/Illuminate/Support/Traits/ForwardsCalls.php
on line 23
It's happened because laravel use new library to send email. Previously, use Swiftmailer and now use Symfony Mailer. To send HTML email without view you can also use below code:
Mail::raw("", function ($message) use ($content) {
$body = new \Symfony\Component\Mime\Part\TextPart($content);
$message->to("dyas#example.com")
->subject("Test dev")
->from("no-reply#example.com")
->setBody($body);
});
I'm creating a mail service within my application that has the body of the email stored in the database prior to sending it out to recipients.
Each mail body is a partial view script that has the necessary parameters injected into it via Zend_View.
What I want to do is create a 'mail' layout that can wrap around each of these partials,
but I can only seem to get either the layout content or the view content; not both at once.
What I've got
$scriptPath = 'test_mail';
$view = Zend_Controller_Front::getInstance()->getParam('bootstrap')->getResource('view');
$view->setScriptPath(APPLICATION_PATH . '/modules/mail/views/scripts/partials/');
$view->layout()->setLayout('mail');
var_dump($view->layout()->render($scriptPath));
However, all I receive is the view script content.
My layout is looking something like this:
<table class="mail">
<!-- Snip -->
<?php echo $this->layout()->content; ?>
<!-- Snip -->
</table>
I know this is possible. I don't want to do:
$layout->content = $view->render($scriptPath);
I assume I'm going the wrong way about this. Is it that I need/ don't have the layout controller plugin registered and somehow need to trigger this to get the output?
I suppose I could just create a custom layout class and take care of the rendering myself but wanted to see what others said first.
Any tips? Thanks!
I am using email layout, multiple view templates for different kinds of emails and extended Zend_Mail class for setting desirable body:
class MyMail extends Zend_Mail
{
public function setBodyView($script, $params = array())
{
$layout = new Zend_Layout(array('layoutPath' => APPLICATION_PATH . '/layouts/scripts'));
$layout->setLayout('email'); // Your email layout
$view = new Zend_View();
$view->setScriptPath(APPLICATION_PATH . PATH_TO_MAIL_TEMPLATES);
foreach ($params as $key => $value) {
$view->assign($key, $value);
}
$layout->content = $view->render($script . '.phtml');
$html = $layout->render();
$this->setBodyHtml($html);
}
}
I using %mail_body% pattern in my mail template.
$layout = Zend_Layout::getMvcInstance();
$view = $layout->getView();
$mail_template = $view->render('template.phtml');
$returnYourReadyTemplate = str_replace('%mail_body%', $mail_body, $mail_template);
in template.phtml :
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body >
<div style="margin:30px 20px 10px 20px">
%mail_body%
</div>
</body>
</html>
Hope this helps you!