I reconfigured my cakeEmail class to log to a specific type by rewriting the send method. I would like to use this override globally. My current single file setup uses /Lib/CustomCakeEmail.php with
App:uses('CustomCakeEmail', 'Lib');
CakePhp: Cake Email AfterSend event suggests a method to globally override using AppController but I have been unable to even trigger the debugger in
App::uses('CustomCakeEmail', 'Lib');
class AppController extends Controller {
public function getEmailInstance($config = null) {
CakeLog::write('debug', 'appcontroller triggered');
return new CustomCakeEmail($config);
}
What is the correct way to implement this global override?
CakePHP Version 2.8.4
Related
I was developing a pagefactory based framework. I had earlier used pagefactory.initements method to inintialise and move from page to page. Init method basically does the same work as saying Homepage HP = new HomePage(driver);
So it is necessary to use init method in pagefactory?
Will we loose something if we do not use it and instead use new to create a page.
If you are using a Java PageFactory with annotations yes.
The PageFactory.initElements(driver, My.class) command parses the annotations and sets up the Java Proxy classes. If you don't .initElements() none of the WebElements in your class will have locators assigned to them and they will all be null.
You can put the .initElements() in your constructor if you just want to new up a page e.g.:
public class MyPage {
public MyPage(WebDriver driver) throws Exception {
PageFactory.initElements(driver, this);
}
}
In CakePHP, how do I load a component in a shell?
To use a component in a controller, you include an array of components in a property called $components. That doesn't work for my Shell. Neither does the "on-the-fly" loading suggested in the documentation.
class MakePdfsShell extends Shell {
public $components = array('Document'); // <- this doesn't work
public function main()
{
$this->Document = $this->Components->load('Document'); // <- this doesnt work either
$this->Document->generate(); // <- this is what I want to do
}
...
I have some xml utilities that I use across some of my controllers. One of the controller launches a heavy task via the cake console, so that it can quietly run in the background via PHP CLI, while the user's request is immediately completed (once the task done, it will e-mail the results to the user).
The xml utilities are generic enough to be used in controller and shell, are too specific to the application to warrant them vendor status. The offered solution with the Lib folder does not work as in CakePhp v3 there seems to be no Lib folder.
After some quite some time spent, I managed to load my controller component to the shell task. Here is how to:
namespace App\Shell;
use Cake\Console\Shell;
use Cake\Core\App;
use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
use App\Controller\Component\XmlUtilitiesComponent; // <- resides in your app's src/Controller/Component folder
class XmlCheckShell extends Shell
{
public function initialize() {
$this->Utilities = new XmlUtilitiesComponent(new ComponentRegistry());
}
...
$this->Utilities can now be used across my entire shell class.
You simply don't.
If you think you have to load a component in shell your application architecture is bad designed and should be refactored.
Technically it is possible but it doesn't make sense and can have pretty nasty side effects. Components are not made to run outside of the scope of a request. A component is thought to run within the scope of a HTTP request and a controller - which is obviously not present in a shell.
Putting things in the right place
Why does XML manipulation stuff have to go into a component? This is simply the wrong place. This should go into a class, maybe App\Utility\XmlUtils for example and have no dependencies at all to the request nor controller.
The logic is properly decoupled then and can be used in other places that need it. Also if you get incoming XML the right place to do this manipulation (by using your utility class) would be inside the model layer, not the controller.
You want to learn about Separation of Concerns and tight coupling
Because you've gone just against both principles.
https://en.wikipedia.org/wiki/Separation_of_concerns
What is the difference between loose coupling and tight coupling in the object oriented paradigm?
Search before asking
You could have tried to search via Google or on SO you would have found one of these:
using components in Cakephp 2+ Shell
CakePHP using Email component from Shell cronjob
Using a plugin component from shell class in cakephp 2.0.2
...
Be aware that some of them might encourage bad practice. I haven't checked them all.
I assume that you have a component named YourComponent:
<?php
App::uses('Component', 'Controller');
class YourComponent extends Component {
public function testMe() {
return 'success';
}
}
- with cake 2., you can load your component like this
App::uses('ComponentCollection', 'Controller');
App::uses('YourComponent', 'Controller/Component');
class YourShell extends AppShell {
public function startup() {
$collection = new ComponentCollection();
$this->yourComponent = $collection->load('Your');
}
public function main() {
$this->yourComponent->testMe();
}
}
- with cake 3. you can load your component like this
<?php
namespace App\Shell;
use App\Controller\Component\YourComponent;
use Cake\Console\Shell;
use Cake\Controller\ComponentRegistry;
class YourShell extends Shell {
public function initialize() {
parent::initialize();
$this->yourComponent = new YourComponent(new ComponentRegistry(), []);
}
public function main() {
$pages = $this->yourComponent->testMe();
}
}
If you are trying to access a custom XyzComponent from a shell, then you probably have commonly-useful functionality there. The right place for commonly-useful functionality (that is also accessible from shells) is in /Lib/.
You can just move your old XyzComponent class from /Controller/Component/XyzComponent.php to /Lib/Xyz/Xyz.php. (You should rename your class to remove the "Component" suffix, e.g., "XyzComponent" becomes "Xyz".)
To access the new location, in your controller, remove 'Xyz' from your class::$components array. At the top of your controller file, add
App::uses('Xyz', 'Xyz'); // that's ('ClassName', 'folder_under_/Lib/')
Now you just need to instantiate the class. In your method you can do $this->Xyz = new Xyz(); Now you're using the same code, but it can also be accessed from your Shell.
//TestShell.php
class TestShell extends AppShell{
public function test(){
//to load a component in dis function
App::import('Component', 'CsvImporter');
$CsvImporter = new CsvImporterComponent();
$data = $CsvImporter->get();
}
}
//CsvImporterComponent.php
App::uses('Component', 'Controller');
class CsvImporterComponent extends Component {
function get(){
//your code
}
}
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’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 am attempting to disable ACL/ACO checks in my local development environment because its time consuming to sync up the ACO table everytime I create a new method or controller. I am having problems figuring out how to do this conditionally. I attempted the following code in AppController but it did not work:
public function beforeFilter() {
parent::beforeFilter();
// disable ACL component in local development environments
if(preg_match('/\.local/',FULL_BASE_URL)){
unset($this->components['Acl']);
unset($this->components['Auth']['authorize']);
}
}
I am running CakePHP 2.x
You can probably achieve the same this way:
Add a configuration in your app/Config/core.php
Configure::write('Auth.enabled', 0);
Having an explicit configuration is generally preferred over 'auto-detecting' your environment.
Then, inside your AppController;
public function beforeFilter()
{
if(0 === Configure::read('Auth.enabled')) {
$this->Auth->allow();
}
}
See Making actions public
Or, to disable the component(s) altogether:
public function beforeFilter()
{
if(0 === Configure::read('Auth.enabled')) {
$this->Components->disable('Acl');
$this->Components->disable('Auth');
}
}