Sending an email with SQL results in cakePHP - php

I need to email all my users a daily product list in cakePHP 2.
I have the following code to get all the users emails.
$users = $this->User->find('all', array('fields' => array('email')));
foreach ($users as $user) {
$this->Email->reset();
$this->Email->from = '<no-reply#test.com.au>';
$this->Email->to = $user['email'];
$this->Email->subject = "Daily Products" ;
$this->Email->sendAs = 'html';
$this->Email->send();
}
Now I understand that I could use a html template for this and parse values to it but I really need a foreach loop inside the actual view itself and send the table of products.
What would be the best practise? cakePHP code in the controller or view to get the products?
Thanks

The best practice for this would be to use a shell to send the emails out. To avoid running out of memory you should read the users and their products in chunks and not all at the same time.
Inside the foreach loop you'll need to get the data for each user and set() it as any other variable it will be available in the html template then and you can render all the products there.
Here is some (shortened) code from a shell that processes data:
public function main() {
$this->loop();
}
public function loop() {
try {
while (true) {
if (!$this->poll()) {
$this->out(__('Nothing more to process, sleeping...'));
sleep($this->sleep);
}
}
} catch (Exception $e) {
$this->out($e->getMessage());
$this->log($e->getMessage(), 'processing');
}
}
public function poll() {
$this->out('Polling...');
$result = $this->Model->getFirstUnprocessed();
if ($result === false) {
return false;
}
// do something with the result here
return true;
}
This should be enough to give you an idea. For reading your users in chunks you would need to increment the offset in your find() options. In my case I just check if there is an unprocessed record and if yes i process and and wait a moment to do the next try.

The "view" for e-mails is actually an element. It's under Views/Elements/email. There are 2 folders there html and text, both meant to hold their respective templates.
You can do your foreach in there, then make sure to set the layout in your controller with:
$this->Email->sendAs = 'html'; // Can also be 'text' or 'both' (for multipart).
$this->Email->layout = 'foo'; // Would include Views/Elements/email/html/foo.ctp
Although the Email component is deprecated since CakePHP 2.0 and you should be using the CakeEmail component instead. See the book for more details on how to use that.

Related

Sending bulk email using laravel queue

We are trying to send bulk email (100k) with PHP Laravel framework. Which way is the correct way to send bulk email with Laravel queue?
Case 1.
//controller
public function runQueue(){
dispatch(new ShootEmailJob());
}
//job
public function handle(){
$emails = EmailList::get(['email']);
foreach($emails as $email){
Mail::to($email)->send();
}
}
Case 2.
//controller
public function runQueue(){
$emailList = EmailList::get(['email']);
foreach($emailList as $emailAddress){
dispatch(new ShootEmailJob($emailAddress->email));
}
}
//job
public function handle(){
Mail::to($emailAddress)->send(new ShootMail($emailAddress));
}
Which one is the correct approach case 1 or case 2?
The first approach will first fetch all emails and then send them one by one in one "instance" of a job that is run as a background process if you queue it.
The second approach will run n "instances" of jobs, one for each email on the background process.
So performance-wise option 1 is the better approach. You could also wrap it in a try - catch block in case of exceptions so that the job does not fail if one of the emails fails, e.g.:
try {
$emails = EmailList::get(['email']);
foreach($emails as $email){
Mail::to($email)->send();
}
} catch (\Exception $e) {
// Log error
// Flag email for retry
continue;
}

Why is my controller so slow?

I'm on an app that retrieve datas (a 7k lines CSV formated string) from an external server to update my own entity. Each row is an item in a stock.
Today the job is nicely done but it's very very very slow: more than 60s (prod env) to retrieve datas, push it in a 2D array, update the BDD, and finally load a page that display the bdd content.
When only displaying the page it's about 20s (still prod).
This the profiler's timeline result while only displaying records : Symfony's profiler timeline
Anymore, i'm not able to profile the "updateAction" cause i't don't appear in the last ten request list.
2 days ago I was checking each row of the CSV file to add it only if needed, I was soft-deleting items to restore it later when back in the stock etc. but with that speed I tried many things to have normal performances.
At the begening everything was in the controler, I moved the function that add/remove in a dedicated service, then in the repository to finally get it back in my controler. To have decent results I tried to empty the database and then refill it without checking. First, using LOAD DATA LOCAL INFILE but it is not compatible with my table pattern (or I mis understood something) and now I'm simply emptying the table before filling it with the CSV (without any control). The time score I gave earlier was with this last try (which is the best one).
But enought talk
here is my controler:
public function majMatosClanAction()
{
$resMaj = $this->majClanCavernes();
if ($resMaj === NULL)
{
$this->get('session')->getFlashBag()->add('alert-danger', 'Unidentified');
return $this->redirect($this->generateUrl('loki_gbl'));
} else if ($resMaj === FALSE)
{
$this->get('session')->getFlashBag()->add('alert-warning','password update required');
return $this->redirect($this->generateUrl('loki_gbl_ST'));
} else
{
$this->get('session')->getFlashBag()->add('alert-success','success');
return $this->redirect($this->generateUrl('loki_gbl_voirMatosClan'));
}
}
here is the function that my controller call:
public function majClanCavernes()
{
$user = $this->get('security.token_storage')->getToken()->getUser();
$outils = $this->container->get('loki_gbl.outils');
if ($user !== NULL)
{
$pwd = $user->getGob()->getPwd();
$num = $user->getGob()->getNum();
if($outils->checkPwd($num, $pwd) !== TRUE) return FALSE;
$em = $this->getDoctrine()->getManager();
//This is a temporary solution
//////////////////////////////////////////////
$connection = $em->getConnection();
$platform = $connection->getDatabasePlatform();
$connection->executeUpdate($platform->getTruncateTableSQL('MatosClan', true ));
//////////////////////////////////////////////
$repository = $em->getRepository('LokiGblBundle:MatosClan');
$urlMatosClan = "http://ie.gobland.fr/IE_ClanCavernes.php?id=".$num."&passwd=".$pwd;
//encode and format the string via a service
$infosBrutes = $outils->fileGetInfosBrutes($urlMatosClan);
//$csv is a 2D array containing the datas
$csv = $outils->getDatasFromCsv($infosBrutes);
foreach($csv as $item)
{
$newItem = new MatosClan;
$newItem->setNum($item[0]);
$newItem->setType($item[1]);
[...]
$em->persist($newItem);
}
$em->flush();
return TRUE;
}
else{
return NULL;
}
}
What is wrong? 7k lines is not that big!
Could it be a lack of hardware issue?
Check out doctrine's batch processing documentation here.
You can also disable logging:
$em->getConnection()->getConfiguration()->setSQLLogger(null);

CakePHP - JsonView rendering is sub function of the controller

I'm using Cakephp with json parse extension and the RequestHandler component in order to create Web services using json.
I created a controller named Ws
In this controller I have a named userSubscribe
In order to avoid a lot of If else statements in the next methods, I thought about using a private function inside this controller that will check somes conditions and stop the script normaly BUT ALSO render the json normaly. I just want to do a DRY way !
My question is :
How could I render the json view in a sub function (called by the userSubscribe) ?
To make it clear, here is the style code that would like
public function userSubscribe() {
$this->check();
// Following code only executed if check didn't render the json view
// $data = ...
$code = 1;
$i = 2;
}
private function check() {
$input = &$this->request->data;
if ($_SERVER["CONTENT_TYPE"] != "application/json") { // For example
$result = "KO";
$this->set(compact("result"));
$this->set('_serialize', 'result');
$this->render(); // HERE, it will stop the 'normal behaviour' and render the json with _serialize
}
if (!isset($input["input"])) {
$result = "KO";
$this->set(compact("result"));
$this->set('_serialize', 'result');
$this->render(); // HERE, it will stop the 'normal behaviour' and render the json with _serialize
}
}
It's seems to be quite simple to do, but why can't I find the answer ?!
Thanks in advance for clue/advise/anything !

Using JToolbar or JToolbarHelper for CRUD in Joomla frontend for the custom component

For which cases are these classes suitable? I've been trying to use both, but none of them works.
The component skeleton was generated, and there are CRUD operations in the administrator's side. I tried using JToolbarHelper from this generated code, like this in mycomponent/view.html.php:
// Overwriting JView display method
function display($tpl = null)
{
// Include helper submenu
InvoiceHelper::addSubmenu('invoice');
// Assign data to the view
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
// Check for errors.
if (count($errors = $this->get('Errors'))){
JError::raiseError(500, implode('<br />', $errors));
return false;
};
// Set the toolbar
$this->addToolBar();
// Show sidebar
$this->sidebar = JHtmlSidebar::render();
// Display the view
parent::display($tpl);
}
protected function addToolBar()
{
JLoader::register('JToolbarHelper', JPATH_ADMINISTRATOR.'/includes/toolbar.php');
$canDo = InvoiceHelper::getActions();
JToolBarHelper::title(JText::_('Invoice Manager'), 'invoice');
if($canDo->get('core.create')){
JToolBarHelper::addNew('invoic.add', 'JTOOLBAR_NEW');
};
if($canDo->get('core.edit')){
JToolBarHelper::editList('invoic.edit', 'JTOOLBAR_EDIT');
};
if($canDo->get('core.delete')){
JToolBarHelper::deleteList('', 'invoice.delete', 'JTOOLBAR_DELETE');
};
}
But it doesn't even appear on the page.
Then I came across this tutorial http://docs.joomla.org/J3.x:Using_the_JToolBar_class_in_the_frontend and it kindof works, except I can't imagine implementing something like a list of entities with checkboxes and operations for each. And it's unclear for me how to handle form submissions using this approach, seems like it happens through JS, do I get it right?
So, please tell, what's the difference and why doesn't the first approach even make the toolbar appear?
I know this is a long time ago, but I was looking to achieve the same result and found the following to loads the toolbar on the page. Using the above code:
protected function addToolBar()
{
JLoader::register('JToolbarHelper', JPATH_ADMINISTRATOR.'/includes/toolbar.php');
$canDo = InvoiceHelper::getActions();
JToolBarHelper::title(JText::_('Invoice Manager'), 'invoice');
if($canDo->get('core.create')){
JToolBarHelper::addNew('invoic.add', 'JTOOLBAR_NEW');
}
if($canDo->get('core.edit')){
JToolBarHelper::editList('invoic.edit', 'JTOOLBAR_EDIT');
}
if($canDo->get('core.delete')){
JToolBarHelper::deleteList('', 'invoice.delete', 'JTOOLBAR_DELETE');
}
$this->toolbar = JToolbar::getInstance(); // <<<---------- ADD THIS TO METHOD!
}
Then in your view do this:
<?php echo $this->toolbar->render(); ?>
Hope this helps!!! enjoy.

How to Test cakephp parseExtension?

I setup the parseExtension for json in my cakephp 2.3.0. No errors are display. It works?
How can I test is?
In RoR ist ist very easy to test via
https://mydomain.de/view/4.json
How does it run on cakephp?
My View-Action is this.
public function view($id = null) {
if (!$this->Atla->exists($id)) {
throw new NotFoundException(__('Invalid atla'));
}
$options = array('conditions' => array('Atla.' . $this->Atla->primaryKey => $id));
$this->set('atla', $this->Atla->find('first', $options));
$this->Atla->id = $id;
$result = $this->Atla->read();
$this->response->type('json');
$this->response->body(json_encode($result));
return $this->response;
$this->set(compact('atlas'));
}
Why i always get an json-request?
If you use the _serialize key cakephp can automatically create json and xml views for you. I typically use the following to create json or xml views:
public function view() {
// data I want to display
$record1 = $this->ModelName->find('first', ...);
$this->set('record1', $record1);
$record2 = $this->ModelName->find('first', ...);
$this->set('record2', $record2);
// option 1: serialize the hard way
$this->set('_serialize', array('record1', 'record2'));
// option 2: serialize the easy way
$this->set('_serialize', array_keys($this->viewVars));
}
PS: the code after your return statement will never be executed.
you'll have create the view
app/View/Atlas/json/view.ctp
which is the view that is being used for .json requests.
Requests without .json will use the regular view file:
app/View/Atlas/view.ctp
There's more information on creating/using JSON and XML views here:
http://book.cakephp.org/2.0/en/views/json-and-xml-views.html#using-a-data-view-with-view-files
From that page the view.ctp may contain something like;
// View code - app/View/Posts/json/index.ctp
foreach ($posts as &$post) {
unset($post['Post']['generated_html']);
}
echo json_encode(compact('posts', 'comments'));
However, it really depends on what you're trying to achieve. If you will only use the 'Atlas/view' action for JSON responses, and won't be using HTML at all, sometimes you can get away with generating the response-body inside your Controller. Not very much 'in line' with MVC conventions, but it saves you from creating a view that does nothing more than echo json_encode($data); ;)
public function view($id)
{
$this->MyModel->id = $id;
$result = $this->MyModel->read();
$this->response->type('json');
$this->response->body(json_encode($result));
//Return reponse object to prevent controller from trying to render a view
return $this->response;
}
If you do want to use both 'HTML' and 'JSON', depending on the request (with/without .json extension), you should have two view files; 1 for JSON and 1 for HTML;
// This view will be used for your JSON requests
app/View/Atlas/json/view.ctp
// This view will be used for non-JSON (html) requests:
app/View/Atlas/view.ctp
In the json-view, output the data using json_encode(.....);
In the 'normal'/html view, just output normal data
In your controller, set the data as normal
public function view($id = null) {
$this->Atla->id = $id;
if (!$this->Atla->exists()) {
throw new NotFoundException(__('Invalid atla'));
}
$this->set('atla', $this->Atla->read());
}

Categories