In my controller i'm using a email function with the following code:
public function email($mail = null){
$email = new CakeEmail('default');
$email->config('default');
$email->from(array('test#test.com' => 'testing'));
$email->to('$mail');
$email->subject('Approved');
$email->send('Approved');
At the top i have
App::uses('AppController', 'Controller', 'CakeEmail', 'Network/Email');
However, i receive the error Fatal error: Class 'CakeEmail' not found in.
I'm not sure where i have gone wrong. Can anybody please assist?
You need to change your App::uses and separate the two:
App::uses('AppController', 'Controller');
App::uses('CakeEmail', 'Network/Email');
App::uses() does only allow two arguments: $className and $location. You passed 4 arguments, that's why CakeEmail is not loaded.
See http://api20.cakephp.org/class/app#method-Appuses and http://book.cakephp.org/2.0/en/core-utility-libraries/app.html#App::uses for more information
the documentation is pretty clear about it:
http://book.cakephp.org/2.0/en/core-utility-libraries/email.html?highlight=cakeemail#CakeEmail
"First of all, you should ensure the class is loaded"
on a second look: your app::uses() is wrong. check out the way it is documented.
You can use the Email component in the controller
public $components = array('Email');
public function email(){
$this->Email->to = 'yourmail#mail.com';
$this->Email->subject = 'Subject - ';
$this->Email->from = 'sender#mail.com';
$this->Email->send('message');
}
Related
I have a file named
Helper.php and I have put it in composer.
"autoload": {
"files": [
"app/Http/Helper.php",
"app/Notification.php"
]
},
You can see that I have also put a model that is Notification model. but when I call this Notification in Helper.php it says class Notification not found..
function notify()
{
$notify = new App\Notifcation;
$notify = new Notifcation; //Also tried this
}
First of all you don't need to add it in composer.
Secondly, check what you have written twice thrice because there can be typos that will stop your program from being executed
remove "app/Notification.php" from composer.json and dump-autoload it. Then use like this.
function notify()
{
$notify = new App\Notification;
}
If you add this Notification Model in composer then it will always be autoloaded even if it is not used thus putting unnecessary pressure on your project.
Hope this helps
You have a typo in Notifcation. Try this:
function notify()
{
$notify = new Notification;
}
Write use App\Notification in your Helper.php
than
$notify = new Notification();
Or you can use this:
$notify = new \App\Notification;
And just in case check your namespaces
I have an API written using Symfony2 that I'm trying to write post hoc tests for. One of the endpoints uses an email service to send a password reset email to the user. I'd like to mock out this service so that I can check that the right information is sent to the service, and also prevent an email from actually being sent.
Here's the route I'm trying to test:
/**
* #Route("/me/password/resets")
* #Method({"POST"})
*/
public function requestResetAction(Request $request)
{
$userRepository = $this->get('app.repository.user_repository');
$userPasswordResetRepository = $this->get('app.repository.user_password_reset_repository');
$emailService = $this->get('app.service.email_service');
$authenticationLimitsService = $this->get('app.service.authentication_limits_service');
$now = new \DateTime();
$requestParams = $this->getRequestParams($request);
if (empty($requestParams->username)) {
throw new BadRequestHttpException("username parameter is missing");
}
$user = $userRepository->findOneByUsername($requestParams->username);
if ($user) {
if ($authenticationLimitsService->isUserBanned($user, $now)) {
throw new BadRequestHttpException("User temporarily banned because of repeated authentication failures");
}
$userPasswordResetRepository->deleteAllForUser($user);
$reset = $userPasswordResetRepository->createForUser($user);
$userPasswordResetRepository->saveUserPasswordReset($reset);
$authenticationLimitsService->logUserAction($user, UserAuthenticationLog::ACTION_PASSWORD_RESET, $now);
$emailService->sendPasswordResetEmail($user, $reset);
}
// We return 201 Created for every request so that we don't accidently
// leak the existence of usernames
return $this->jsonResponse("Created", $code=201);
}
I then have an ApiTestCase class that extends the Symfony WebTestCase to provide helper methods. This class contains a setup method that tries to mock the email service:
class ApiTestCase extends WebTestCase {
public function setup() {
$this->client = static::createClient(array(
'environment' => 'test'
));
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
$this->mockEmailService = $mockEmailService;
}
And then in my actual test cases I'm trying to do something like this:
class CreatePasswordResetTest extends ApiTestCase {
public function testSendsEmail() {
$this->mockEmailService->expects($this->once())
->method('sendPasswordResetEmail');
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
}
}
So now the trick is to get the controller to use the mocked version of the email service. I have read about several different ways to achieve this, so far I've not had much luck.
Method 1: Use container->set()
See How to mock Symfony 2 service in a functional test?
In the setup() method tell the container what it should return when it's asked for the email service:
static::$kernel->getContainer()->set('app.service.email_service', $this->mockEmailService);
# or
$this->client->getContainer()->set('app.service.email_service', $this->mockEmailService);
This does not effect the controller at all. It still calls the original service. Some write ups I've seen mention that the mocked service is 'reset' after a single call. I'm not even seeing my first call mocked out so I'm not certain this issue is affecting me yet.
Is there another container I should be calling set on?
Or am I mocking out the service too late?
Method 2: AppTestKernel
See: http://blog.lyrixx.info/2013/04/12/symfony2-how-to-mock-services-during-functional-tests.html
See: Symfony2 phpunit functional test custom user authentication fails after redirect (session related)
This one pulls me out of my depth when it comes to PHP and Symfony2 stuff (I'm not really a PHP dev).
The goal seems to be to change some kind of foundation class of the website to allow my mock service to be injected very early in the request.
I have a new AppTestKernel:
<?php
// app/AppTestKernel.php
require_once __DIR__.'/AppKernel.php';
class AppTestKernel extends AppKernel
{
private $kernelModifier = null;
public function boot()
{
parent::boot();
if ($kernelModifier = $this->kernelModifier) {
$kernelModifier($this);
$this->kernelModifier = null;
};
}
public function setKernelModifier(\Closure $kernelModifier)
{
$this->kernelModifier = $kernelModifier;
// We force the kernel to shutdown to be sure the next request will boot it
$this->shutdown();
}
}
And a new method in my ApiTestCase:
// https://stackoverflow.com/a/19705215
protected static function getKernelClass(){
$dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
$finder = new Finder();
$finder->name('*TestKernel.php')->depth(0)->in($dir);
$results = iterator_to_array($finder);
if (!count($results)) {
throw new \RuntimeException('Either set KERNEL_DIR in your phpunit.xml according to http://symfony.com/doc/current/book/testing.html#your-first-functional-test or override the WebTestCase::createKernel() method.');
}
$file = current($results);
$class = $file->getBasename('.php');
require_once $file;
return $class;
}
Then I alter my setup() to use the kernel modifier:
public function setup() {
...
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
static::$kernel->setKernelModifier(function($kernel) use ($mockEmailService) {
$kernel->getContainer()->set('app.service.email_service', $mockEmailService);
});
$this->mockEmailService = $mockEmailService;
}
This works! However I now can't access the container in my other tests when I'm trying to do something like this:
$c = $this->client->getKernel()->getContainer();
$repo = $c->get('app.repository.user_password_reset_repository');
$resets = $repo->findByUser($user);
The getContainer() method returns null.
Should I be using the container differently?
Do I need to inject the container into the new kernel? It extends the original kernel so I don't really know why/how it's any different when it comes to the container stuff.
Method 3: Replace the service in config_test.yml
See: Symfony/PHPUnit mock services
This method requires that I write a new service class that overrides the email service. Writing a fixed mock class like this seems less useful than a regular dynamic mock. How can I test that certain methods have been called with certain parameters?
Method 4: Setup everything inside the test
Going on #Matteo's suggestion I wrote a test that did this:
public function testSendsEmail() {
$mockEmailService = $this->getMockBuilder(EmailService::class)
->disableOriginalConstructor()
->getMock();
$mockEmailService->expects($this->once())
->method('sendPasswordResetEmail');
static::$kernel->getContainer()->set('app.service.email_service', $mockEmailService);
$this->client->getContainer()->set('app.service.email_service', $mockEmailService);
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
}
This test fails because the expected method sendPasswordResetEmail wasn't called:
There was 1 failure:
1) Tests\Integration\Api\MePassword\CreatePasswordResetTest::testSendsEmail
Expectation failed for method name is equal to <string:sendPasswordResetEmail> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
Thanks to Cered's advice I've managed to get something working that can test that the emails I expect to be sent actually are. I haven't been able to actually get the mocking to work so I'm a bit reluctant to mark this as "the" answer.
Here's a test that checks that an email is sent:
public function testSendsEmail() {
$this->client->enableProfiler();
$this->post(
"/me/password/resets",
array(),
array("username" => $this->user->getUsername())
);
$mailCollector = $this->client->getProfile()->getCollector('swiftmailer');
$this->assertEquals(1, $mailCollector->getMessageCount());
$collectedMessages = $mailCollector->getMessages();
$message = $collectedMessages[0];
$this->assertInstanceOf('Swift_Message', $message);
$this->assertEquals('Reset your password', $message->getSubject());
$this->assertEquals('info#example.com', key($message->getFrom()));
$this->assertEquals($this->user->getEmail(), key($message->getTo()));
$this->assertContains(
'This link is valid for 24 hours only.',
$message->getBody()
);
$resets = $this->getResets($this->user);
$this->assertContains(
$resets[0]->getToken(),
$message->getBody()
);
}
It works by enabling the Symfony profiler and inspecting the swiftmailer service. It's documented here: http://symfony.com/doc/current/email/testing.html
ok, So I am still a little new to Symfony 2 but I have been all over the web trying to see what I am doing wrong, but going over and over the Symfony docs, I can't see why its not working.
So I have tow points in my app that will need to send two new user emails, one with a password and one with an active path to verify the email. I am using MailGun to send the emails, this works fine. But has I have two controllers that will send these emails, I thought that if I wanted to change/edit them, it would be best if they where in the same place. So I build my own class for them to go in.
This is the problem, as it is not an controller I am trying to 'render' an Standard Email template layout. And for the life of me can not figure out why its not working.
So my Class:
namespace xxxBundle\Classes;
//use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class NewUserEmails {
//private $templating;
//public function __construct(EngineInterface $templating) {
// $this->templating = $templating;
//}
private $container;
public function __construct(ContainerInterface $container) {
$this->templating = $container->get('templating');
}
public function SendActivePathEmail($GetNewUserID, $Token) {
/* Send Active Path To New User - Before Password */
$Email_Header_ActiveUser = 'Access';
$Email_Content_ActiveUser = 'Please active your account, by clicking the link below.';
$Email_Content_ActiveUser .= '<br/><br/><strong>PLEASE NOTE:</strong> Your account is not active untill you have vifyied your email';
$Email_Content_ActiveUser .= '<br/><br/><p>Active Account Link</p>';
$ActiveUserEmail = $this->templating->render('xxxBundle:Emails:StandardEmail.html.twig',
['EmailContent' => $Email_Header_ActiveUser,
'EmailMessage' => $Email_Content_ActiveUser],
'text/html');
return $ActiveUserEmail;
}
public function SendPswEmail($PlainPwd) {
/* Send Password To New User */
$Email_Header_NewPsw = 'Access';
$Email_Content_NewPsw = 'This is your password <strong>' .$PlainPwd. '</strong> for xxxxx login';
$PasswordEmail = $this->templating->render('xxxBundle:Emails:StandardEmail.html.twig',
['EmailContent' => $Email_Header_NewPsw,
'EmailMessage' => $Email_Content_NewPsw],
'text/html');
return $PasswordEmail;
}
} //Class End
Now this is what I have in my services YML file,
new_user_emails:
class: xxxBundle\Classes\NewUserEmails
arguments: [#service_container]
This is the services file within my bundle, which I know is being loaded has I have a login handler which works without any problems.
And this is how I am calling the class within my Controller,
$GetNewUserEmails = new NewUserEmails();
$ActiveUserEmail = $GetNewUserEmails->SendActivePathEmail($GetNewUserID, $Token);
$PasswordEmail = $GetNewUserEmails->SendPswEmail($PlainPwd);
So has far as I can tell I am doing it right, but when I try to save the user (which does save without any problems) I get the following error,
Catchable Fatal Error: Argument 1 passed to xxxxBundle\Classes\NewUserEmails::__construct() must implement interface Symfony\Component\DependencyInjection\ContainerInterface, none given
P.S. I have tried to insert just the tempting, but that gave me the same error!
All help most welcome,
What am I doing wrong?
Thanks,
You need to retrieve your service from the symfony container.
try this:
$GetNewUserEmails = $this->get('new_user_emails');
$ActiveUserEmail = $GetNewUserEmails->SendActivePathEmail($GetNewUserID, $Token);
$PasswordEmail = $GetNewUserEmails->SendPswEmail($PlainPwd);
Instead of this:
$GetNewUserEmails = new NewUserEmails();
$ActiveUserEmail = $GetNewUserEmails->SendActivePathEmail($GetNewUserID, $Token);
$PasswordEmail = $GetNewUserEmails->SendPswEmail($PlainPwd);
PS: Is not a good practice to pass the whole container to the service, but only the service it really needed.
Hope this help
I found a problem that I not sure if is a bug of the php or on my code (probably mine) so let me show you what is happening:
<?php namespace MyApp\Conciliation;
use SimpleExcel\SimpleExcel;
use ForceUTF8\Encoding;
use MyApp\Conciliation\Gol;
class Conciliation {
protected function equalizeFile($file, $providerName)
{
$type = false;
$nfile = 'public'.$file;
// TEST 1: the ideal aproach. not working (see error#1 bellow)
$provider = new $providerName();
// TEST 2: working, getting the correct response
$provider = new Gol();
// TEST 3: working, getting the correct response
$provider = new MyApp\Conciliation\Gol();
$provider->equalize($nfile);
}
Note, the $providerName = 'Gol';
error1
Class 'Gol' not found
http://inft.ly/N8Q6F4B
So, there is any way that I could keeping using variables to instantiate aliases similar as above?
Edit, Problem solved: working example
<?php namespace MyApp\Conciliation;
use SimpleExcel\SimpleExcel;
use ForceUTF8\Encoding;
class Conciliation {
protected function equalizeFile($file, $providerName)
{
$type = false;
$nfile = 'public'.$file;
$providerName = "MyApp\\Conciliation\\".$providerName;
$provider = new $providerName();
$provider->equalize($nfile);
}
http://php.net/manual/en/language.namespaces.dynamic.php
If you are calling the class dynamically, you have to use the full path to the class.
So, your call to equalizeFile should be something like:
equalizeFile("myFile", "MyApp\\Conciliation\\Gol");
I'm trying to send an email from a CakePHP shell just as you would from the Controller.
Most of the code below was adapted from this dated article on the Bakery and it's comments. The email is sending, however the line $controller->set('result', $results[$i]); throws the following notices:
Notice: Undefined property:
View::$webroot in
/home/jmccreary/www/intranet.sazerac.com/cakephp/cake/libs/view/view.php
on line 813
PHP Notice: Undefined
variable: result in
/home/jmccreary/www/intranet.sazerac.com/cakephp/app/views/elements/email/text/nea/task_reminder_it.ctp
on line 2
So I'm not getting any of the variables passed to my email view.
How can I do this, ideally following the Cake conventions?
class NotificationShell extends Shell {
var $uses = array('Employee', 'Task');
function main() {
// run if no action is passed
}
function nea_task_reminder() {
// build Task to Employee relationship
$this->Task->bindModel(array('belongsTo' => array('Employee' => array('className' => 'Employee', 'foreignKey' => 'object_id'))));
$results = $this->Task->find('all', array('conditions' => array('application_id' => 1, 'completed_by_id' => 0), 'contain' => array('Employee' => array('Contact', 'Position'))));
$count = count($results);
if ($count) {
App::import('Core', 'Controller');
App::import('Component', 'Email');
$controller =& new Controller();
$email =& new EmailComponent();
$email->startup($controller);
// send email
$email->from = Configure::read('Email.from');
$email->to = 'jmccreary#whatever.com';
$email->replyTo = 'no-reply#whatever.com';
$email->template = 'nea/task_reminder_it';
$email->sendAs = 'text';
for ($i = 0; $i < $count; ++$i) {
$email->subject = 'NEA Notification: Task Reminder for ' . $results[$i]['Employee']['Contact']['full_name'];
$controller->set('result', $results[$i]);
$email->send();
}
}
}
}
The problem is the way you're initializing the EmailComponent class. If you look at the source code, the startup() method doesn't actually have a body so it does nothing. Your controller isn't actually assigned to the EmailComponent. The problem isn't $controller->set('results', ...);. You need to use EmailComponent::initialize() instead of EmailComponent::startup().
$controller =& new Controller();
$email =& new EmailComponent(null);
$email->initialize($controller);
Sources:
Comments section of http://bakery.cakephp.org/articles/Jippi/2007/12/02/emailcomponent-in-a-cake-shell
EmailComponent::startup() Source
If you're using CakePHP 2.x, you can ditch the EmailComponent entirely and use the CakeEmail class instead.
App::uses('CakeEmail', 'Network/Email');
class NotificationShell extends Shell {
public function send() {
$email = new CakeEmail();
}
}
That entirely avoids all the thorny issues of loading components inside a shell. For email at least.
If you're using CakePHP 2.x, try to use CakeEmail instead.
CakeEmail#viewVars() provides setting variables to template.
Here is example using CakeEmail from Shell.
https://gist.github.com/tsmsogn/cee9cef2e851e7684021
Try this.
App::import('Core', 'Controller');
App::import('Component', 'Email');
$this->Controller =& new Controller();
$this->Email =& new EmailComponent(null);
$this->Email->initialize($this->Controller);
//use set function as below
$this->controller->set('result', $results[$i]);
for more reference click on below link:
http://ask.cakephp.org/questions/view/cron_shell_-_mail