Is it possible to log emails sent using CakeEmail using the new (2.1+) Events system?
I'm hoping I can do something like this, only what I have doesn't do anything yet:
// config/bootstrap.php
App::uses('CakeEmailRecord', 'Lib/Event');
App::uses('CakeEventManager', 'Event');
CakeEventManager::instance()->attach(new CakeEmailRecord());
// /app/Lib/Event/CakeEmailRecord.php
App::uses('CakeEventListener', 'Event');
class CakeEmailRecord implements CakeEventListener {
public function implementedEvents() {
return array(
'Network.CakeEmail.afterSend' => 'recordSend',
);
}
public function recordSend($event) {
$this->log("triggered an event");
}
}
I'm specifically asking about the events system here. I want to know if that feature is something I can & should use to solve this. The question tagged as a duplicate does not deal with the cake events system
Related
I am using CakePHP(3.6) Events for sending email on a certain action. And want to use queue for differed action.
I have integrated Queuesadilla plugin for this purpose and using MysqlEngine.
Mostly I have followed this blog https://someguyjeremy.com/2017/07/queued-events-in-cakephp.html .
I have started the worker by executing this command.
$ bin/cake queuesadilla
Event has been queued and cleared after a certain time (suppose delay is 10 sec). But the method which is supposed to be called after dispatch , is not being called.
But it says
Success. Acknowledging job on queue
.
I have a separate listener.Here it is.
namespace App\Event;
use Cake\Event\EventListenerInterface;
use Cake\Log\Log;
use App\Queue\QueueManager;
class TestListener implements EventListenerInterface
{
public function implementedEvents()
{
Log::write("debug","inside listener");
return [
'Controller.Reports.afterRemove' => 'afterRemove',
];
}
public function afterRemove($event)
{
Log::write('debug', "Test listener has been fired");
}
}
I don't understand why afterRemove() method was not called ?
And I want to send Email from this method but it looks like it's not being called !
And this is how I have enqueued the job
$test = [
1=>"Test message"
];
$event = new Event('Controller.Reports.afterRemove', null,$test);
QueueManager::queue($event, [
'delay' => 10
]);
Here how I have attached my listener with global
use App\Event\TestListener;
use Josegonzalez\CakeQueuesadilla\Queue\Queue;
Configure::load('queuesadilla', 'default', false);
Queue::setConfig(Configure::consume('Queuesadilla'));
$testListener =new TestListener();
EventManager::instance()->on($testListener);
Am I missing something ? Help will be appreciated. Thanks!
I have a CakePHP application hosted on AWS Elastic Beanstalk. Because of the multiple EC2 instances I will use in the future I want to store my PHP sessions in a database. AWS provides a very nice library for storing PHP sessions in their DynamoDB database. See http://goo.gl/URoi3s
Now I putted the AWS SDK in my vendors folder and created an access wrapper for it (a plugin):
<?php
Configure::load('aws');
require_once VENDORS . 'autoload.php';
use Aws\Common\Aws;
class AwsComponent extends Component
{
private $_aws;
public function __construct()
{
$this->_aws = Aws::factory(array(
'key' => Configure::read('Aws.key'),
'secret' => Configure::read('Aws.secret'),
'region' => Configure::read('Aws.region')
));
}
public function getClient($service)
{
return $this->_aws->get($service);
}
}
The wrapper is working well, I already implemented some S3 stuff. Now for the session handler i added the following code to my AppController.php:
public $components = array('Aws.Aws');
public function beforeFilter()
{
$this->_setSessionStorage();
}
private function _setSessionStorage()
{
$client = $this->Aws->getClient('dynamodb');
$client->registerSessionHandler(array(
'table_name' => 'sessions'
));
}
The AWS's internal registerSessionHandler() is executed (tested it) but the session is not beeing stored into the DynamoDB table. Of course I created the table before and if I add the call to the AWS library directly to my webroot/index.php before dispatcher is loaded everything works fine.
I think the problem is that my code is executed after CakePHP calls session_start(). So what is the best way to implement that? http://goo.gl/kUFUIR doesn't help me, I don't want to rewrite the AWS library for beeing compatible with the CakePHP interface.
So what is the best way to implement that? http://goo.gl/kUFUIR
doesn't help me, I don't want to rewrite the AWS library for beeing
compatible with the CakePHP interface.
This is in fact the best way. And this does not mean to reinvent the wheel, abstraction in OOP means that you make things available in a generic interface that can be replaced with something else. You wrap a foreign API or code in an API compatible to your system, in this case a CakePHP application.
Wrap the vendor lib in a AwsSession adapter that implements the CakeSessionHandlerInterface. This way it's API compatible with other session adapters in the case you change it and it might be even solve your core problem, because CakeSession will take care of the initialization.
Your component is initialized after the session in CakePHP, when the controller is already instantiated and then is initializing all its components. So this happens at a pretty late time. Your alternative is to stop CakePHP from initializing the session, I never had a need to do so, so no idea without looking it up myself. Dig in CakeSession. Even if you manage to do so, other components like the default Auth adapter depends on being able to work with Sessions, so you have to take care of the issue that your component has to be loaded before Auth as well. Pretty fragile system with lots of possbile points of failure. Seriously, go for the Session adapter, guess its a lot less painful to get it working this way.
By a quick look at the DynamoDB Session documentation this seems to be pretty easy. Extend the regular session handler and overload only the init and garbage collection of it to add the Aws API calls there, no guarantee this is right but seems to be easy.
What I end up with in CakePHP 3.
src/Network/Session/DynamoDbSession.php
<?php
namespace App\Network\Session;
use Aws\DynamoDb\DynamoDbClient;
use Cake\Core\Configure;
class DynamoDbSession implements \SessionHandlerInterface
{
private $handler;
/**
* DynamoDbSession constructor.
*/
public function __construct()
{
$client = new DynamoDbClient(Configure::read('DynamoDbCredentials'));
$this->handler = $client->registerSessionHandler(array(
'table_name' => Configure::read('DynamoDbCredentials.session_table')
));
}
public function close()
{
return $this->handler->close();
}
public function destroy($session_id)
{
return $this->handler->destroy($session_id);
}
public function gc($maxlifetime)
{
return $this->handler->gc($maxlifetime);
}
public function open($save_path, $session_id)
{
return $this->handler->open($save_path, $session_id);
}
public function read($session_id)
{
return $this->handler->read($session_id);
}
public function write($session_id, $session_data)
{
return $this->handler->write($session_id, $session_data);
}
}
Activate it in config/app.php file:
'Session' => [
'defaults' => 'php',
'handler' => [
'engine' => 'DynamoDbSession'
],
'timeout' => (30 * 24 * 60)
]
I have to work with an Oracle database using the old database driver (ora_logon ) which is not supported by cakephp. I cant use the oci driver instead.
Right now I do the follow:
Every method of every model connects to the database and retrieve data
class SomeClass extends Model {
public function getA(){
if ($conn=ora_logon("username","password"){
//make the query
// retrieve data
//put data in array and return the array
}
}
public function getB(){
if ($conn=ora_logon("username","password"){
//make the query
// retrieve data
//put data in array and return the array
}
}
}
I know that it is not the best way go.
How could I leave cakephp manage opening and closing of the connection to the database and have models only retrieve data? I'm not interested in any database abstraction layer.
I would think you could just make your own OracleBehavior. Each model could use this behavior, and in it, you can overwrite or extend the Model's find() behavior to build a traditional oracle query and run it (I don't know much about Oracle).
Then, in your Behavior's beforeFind() you can open your connection, and in your Behavior's afterFind(), you can close your database connection.
That way, every time before a query is run, it automatically opens the connection, and every time after a find it closes it. You can do the same with beforeSave() and afterSave() and beforeDelete() and afterDelete(). (You'll likely want to create a single connect() method and disconnect() method in the Behavior, so you don't have duplicate code in each beforeX() method.
Do you really need to extend a Cake Model class?
class SomeClass extends Model {
private $conn;
public function constructor() {
parent::constructor();
$conn = ora_logon("username","password");
if(!$conn)
throw new Exception();
}
public function getA() {
//Some code
}
}
SomeController:
App::uses('SomeClass','Model');
public function action() {
$data = array();
$error = null;
try{
$myDb = new SomeClass();
$data = $myDb->getA();
} catch($e) {
$error = 'Cannot connect to database';
}
$this->set(compact('data', 'error'));
}
I’m trying to create a custom datasource for Amazon Web Services in CakePHP. My approach is as follows:
Base AwsDataSource that creates signatures, makes the actual HTTP requests etc
Various datasources for each AWS product (i.e. S3, SQS etc) that extend this class and specifies the endpoint to use
Models for things like S3Bucket, SqsQueue, SqsMessage and so on
My base datasource class looks like this (simplified):
<?php
class AwsDataSource extends DataSource {
public $config = array(
'key' => '',
'secret' => '',
'region' => ''
);
public $endpoint;
public function signRequest($parameters) {
// generates signature
}
public function makeRequest($parameters = array(), $method = 'get') {
// generates signature and makes HTTP request to AWS servers
}
}
And a sample model looks like this:
<?php
class SqsQueue extends AwsAppModel {
public $name = 'SqsQueue';
public $useTable = false;
}
My problem comes trying to then use these models/datasources in my CakePHP app.
I’ve implemented methods named create(), read(), update() and delete() in my AWS datasource as per the CakePHP cookbook, but they don‘t seem to be getting called. I know this because I’ve put die() statements in my datasource with a message, and execution is never stopped.
I’ve exhausted the cookbook, so if any one could show me how to get my models to call the CRUD methods in my datasource classes then I’d be most grateful.
My bad. Turns out my approach was flawed.
The datasource is specified in database config and is specified as AwsDataSource. Therefore, S3DataSource or SqsDataSource is never used, even though that’s where I’ve defined my CRUD methods, hence my application never exiting (because the CRUD methods aren’t defined in AwsDataSource, the actual datasource being called).
Looks like it’s back to the drawing board.
I found that I was unable to return collections from my JAX-WS Web Service.
I appreciate that the Java Collections API may not be supported by all clients, so I switched to return an array, but I can't seem to do this either.
I've set up my web service as follows:
#WebService
public class MyClass {
public ReturnClass[] getArrayOfStuff() {
// extremely complex business logic... or not
return new ReturnClass[] {new ReturnClass(), new ReturnClass()};
}
}
And the ReturnClass is just a POJO. I created another method that returns a single instance, and that works. It just seems to be a problem when I use collections/arrays.
When I deploy the service, I get the following exception when I use it:
javax.xml.bind.MarshalException - with linked exception:
[javax.xml.bind.JAXBException: [LReturnClass; is not known to this context]
Do I need to annotate the ReturnClass class somehow to make JAX-WS aware of it?
Or have I done something else wrong?
I am unsure of wheter this is the correct way to do it, but in one case where I wanted to return a collection I wrapped the collection inside another class:
#WebService
public class MyClass {
public CollectionOfStuff getArrayOfStuff() {
return new CollectionOfStuff(new ReturnClass(), new ReturnClass());
}
}
And then:
public class CollectionOfStuff {
// Stuff here
private List<ReturnClass> = new ArrayList<ReturnClass>();
public CollectionOfStuff(ReturnClass... args) {
// ...
}
}
Disclaimer: I don't have the actual code in front of me, so I guess my example lacks some annotations or the like, but that's the gist of it.