# On the fly generation of zip files

2014-06-12

## Introduction

Let's consider the following situation: You have built a file repository system or a photo album in CakePHP and you would like the user to download all the files or photos in a specific folder in one convenient zip file. In principle, you have several options how to proceed:

• Pre-generate the zip file and provide a link to that file
• Generate the zip file on request and give the link to the user
• Generate the zip file on the fly

Here, I will discuss why the latter option is most likely the best and how to implement this in CakePHP.

Without any doubt, the first two options require both computational time and disk space. If you pre-generate the zip file, this means that for each file or photo you have added, some time is spent adding the item to the zip file. Consequently, uploading of new content will take more time. Moreover, you will need almost 1.5x the normal amount of storage space, as you need to store both the raw images as well as the zip file.

Only generating the zip file when it is being requested by a user would alleviate this somewhat, as you only need storage space when the user places a request. You could for instance use a temporary file system for this. The problem with this approach is that the user is required to wait an additional amount of time for the zip file to be created first before it can be offered for download.

Generating a zip file on the fly has in this sense many benefits. First of all, because the zip file is generated on the fly, the download starts immediately and the user does not have to wait. Secondly, because the file is immediately sent to the user, it does not need to be saved on the disk. Most likely, the zip compression algorithm will be fast enough that no significant reduction in download speed is noted, although this depends of course on the technical specifications of your system. Indeed, the only major drawback of this method is that the processor has to work harder during the download and multiple simultaneous downloads could give very high load averages.

## Implementation

We assume for the sake of simplicity that you have a particular method in your controller which gives an array of the locations of all the files which need to be sent to the user. In the example below, we have a Folder Model with a hasMany association to a File class. We have chosen these names purely for this example, for a real application I would not recommend using a class named 'File' as there already exists such a class in the CakePHP library!!


public function zip($id = null) {$this->layout = 'empty';
$this->Folder->recursive = 1;$this->set('folder', $this->Folder->read(null,$id));
}



Note that we have set an empty file for the layout. (i.e. no layout)

Our view file looks like the following:


<?php

header('Content-disposition: attachment; filename="'.$folder['Folder']['name'].'.zip"'); header('Content-Transfer-Encoding: binary'); mb_http_output('pass'); ob_clean(); flush();$files = '';
foreach($folder['File'] as$file) {
$files .= '"'.$folder['path'].'" ';
}

$fp = popen('zip -r - '.$files, 'r');

$bufsize = 8192;$buff = '';

while( !feof($fp) ) {$buff = fread($fp,$bufsize);
ob_flush();
echo $buff; } pclose($fp);

?>



Let us go through the code. The first three lines inform the browser that we are going to send a zip file with binary encoding. Moreover, we can already give the file a name which we have conveniently extracted from our Folder object. The mb_http_output('pass'); line ensures us that PHP is not going to mess with the encoding. ob_clean() and flush() flush the output and write buffers.

Next, we convert the array of file locations to a string. We use this string to tell the zip program (make sure you have it installed on your operating system) which files to compress. The output of the zip program is not a zip file, but a stream, which is conveniently piped to PHP using the popen function and referenced by a resource handler.

Now comes the interesting part, via this resource handler a chunk of data is read (as set in $bufsize, which is 8kb) to the read buffer, the read buffer is then flushed and the output is echoed. In other words, in segments of 8kb the download is being 'fed'. This is a continuous loop until an end-of-file (EOF) is encountered. After that, the pipe is closed. This way, we only need a very small amount of memory because we fill up a chunk of 8kb, parse it to the browser and then release the memory. If we would let the zip program stream its output completely to the memory and then echo this, we would most likely encounter a memory allocation error. If you have questions or comments, feel free to drop a line! Like what you read? Share this page with your friends and colleagues. ### Comments #### Drop a line Question: What is the answer to Six + Seven? Please answer with a whole number, i.e. 2, 3, 5, 8,... ### Tags ### CakePHP ### Related posts 11906 0 06-06-2014 6883 0 07-06-2014 • History ## Request History 4 previous requests available ==== • Session ## Session • 0(null) ==== • Request ## Request #### Cake Params • plugin(null) • controllerposts • actionview • named(empty) • pass(array) • 016 • 1On the fly generation of zip files #### Post data No post data. #### Query string No querystring data. #### Cookie To view Cookies, add CookieComponent to Controller #### Current Route • keys(array) • 0controller • 1action • options(array) • defaultRoute(true) • defaults(array) • plugin(null) • template/:controller/:action/* ==== • Sql Log ## Sql Logs #### default ##### Total Time: 1 ms Total Queries: 14 queries Query Affected Num. rows Took (ms) Actions SELECT COUNT(*) AS count FROM ivofilot_nl.posts AS Post WHERE Post.id = 16 1 1 0 SELECT Post.active, Post.id FROM ivofilot_nl.posts AS Post WHERE Post.id = 16 LIMIT 1 1 1 0 SELECT Comment.email, Comment.comment, Comment.id, Comment.post_id, Comment.created FROM ivofilot_nl.comments AS Comment WHERE Comment.post_id = (16) 0 0 0 SELECT Tag.id, Tag.name, Tag.icon, Tag.color, PostsTag.post_id, PostsTag.tag_id FROM ivofilot_nl.tags AS Tag JOIN ivofilot_nl.posts_tags AS PostsTag ON (PostsTag.post_id = 16 AND PostsTag.tag_id = Tag.id) 1 1 0 UPDATE posts SET watched=watched+1 WHERE id='16' 1 1 0 SELECT Comment.id, Comment.email, Comment.comment, Comment.post_id, Comment.parent_id, Comment.lft, Comment.rght, Comment.active, Comment.code, Comment.deleted, Comment.created, Comment.modified, Post.id, Post.title, Post.content, Post.watched, Post.active, Post.created, Post.modified, ParentComment.id, ParentComment.email, ParentComment.comment, ParentComment.post_id, ParentComment.parent_id, ParentComment.lft, ParentComment.rght, ParentComment.active, ParentComment.code, ParentComment.deleted, ParentComment.created, ParentComment.modified FROM ivofilot_nl.comments AS Comment LEFT JOIN ivofilot_nl.posts AS Post ON (Comment.post_id = Post.id) LEFT JOIN ivofilot_nl.comments AS ParentComment ON (Comment.parent_id = ParentComment.id) WHERE Comment.post_id = 16 AND Comment.active = '1' 0 0 1 maybe slow SELECT Tag.id, Tag.name, Tag.icon, Tag.color FROM ivofilot_nl.tags AS Tag inner JOIN ivofilot_nl.posts_tags AS PostsTag ON (Tag.id = PostsTag.tag_id) inner JOIN ivofilot_nl.posts AS Post ON (PostsTag.post_id = Post.id) WHERE Post.id = 16 1 1 0 SELECT Post.id, Post.title, Post.content, Post.watched, Post.active, Post.created, Post.modified, PostsTag.post_id, PostsTag.tag_id FROM ivofilot_nl.posts AS Post JOIN ivofilot_nl.posts_tags AS PostsTag ON (PostsTag.tag_id = 6 AND PostsTag.post_id = Post.id) 4 4 0 SELECT COUNT(*) AS count FROM ivofilot_nl.comments AS Comment LEFT JOIN ivofilot_nl.posts AS Post ON (Comment.post_id = Post.id) LEFT JOIN ivofilot_nl.comments AS ParentComment ON (Comment.parent_id = ParentComment.id) WHERE Comment.post_id = 11 1 1 0 SELECT COUNT(*) AS count FROM ivofilot_nl.comments AS Comment LEFT JOIN ivofilot_nl.posts AS Post ON (Comment.post_id = Post.id) LEFT JOIN ivofilot_nl.comments AS ParentComment ON (Comment.parent_id = ParentComment.id) WHERE Comment.post_id = 14 1 1 0 SELECT COUNT(*) AS count FROM ivofilot_nl.comments AS Comment LEFT JOIN ivofilot_nl.posts AS Post ON (Comment.post_id = Post.id) LEFT JOIN ivofilot_nl.comments AS ParentComment ON (Comment.parent_id = ParentComment.id) WHERE Comment.post_id = 15 1 1 0 SELECT COUNT(*) AS count FROM ivofilot_nl.comments AS Comment LEFT JOIN ivofilot_nl.posts AS Post ON (Comment.post_id = Post.id) LEFT JOIN ivofilot_nl.comments AS ParentComment ON (Comment.parent_id = ParentComment.id) WHERE Comment.post_id = 16 1 1 0 SELECT Post.id, Post.title, Post.content, Post.watched, Post.active, Post.created, Post.modified FROM ivofilot_nl.posts AS Post WHERE Post.id = 16 LIMIT 1 1 1 0 SELECT COUNT(*) AS count FROM ivofilot_nl.comments AS Comment LEFT JOIN ivofilot_nl.posts AS Post ON (Comment.post_id = Post.id) LEFT JOIN ivofilot_nl.comments AS ParentComment ON (Comment.parent_id = ParentComment.id) WHERE Comment.post_id = 16 1 1 0 #### Query Explain: Click an "Explain" link above, to see the query explanation. ==== • Timer ## Memory Peak Memory Use 4.75 MB Message Memory use Component initialization 992 KB Controller action start 1.02 MB Controller render start 1.67 MB View render complete 2.09 MB ## Timers Total Request Time: 276 (ms) Message Time in ms Graph Core Processing (Derived from$_SERVER["REQUEST_TIME"]) 63.87
Event: Controller.initialize 0.18
Event: Controller.startup 3.74
Controller action 106.77
Event: Controller.beforeRender 30.15
» Processing toolbar data 30.10
Rendering View 33.92
» Event: View.beforeRender 0.02
» Rendering APP/View/Posts/view.ctp 31.63
» » Rendering APP/View/Elements/code_highlighting.ctp 3.39
» » Rendering APP/View/Elements/post.commentform.ctp 18.13
» » » Rendering APP/View/Elements/post.comment.captcha.ctp 1.70
» » Rendering APP/View/Elements/post.relatedpost.ctp 0.25
» Event: View.afterRender 0.01
» Event: View.beforeLayout 0.01
» Rendering APP/View/Layouts/default.ctp 1.29
» » Rendering APP/View/Elements/navbar.ctp 0.27
» » Rendering APP/View/Elements/footer.ctp 0.22
» » » Rendering APP/View/Elements/biography.ctp 0.07
Event: View.afterLayout 0.00
====
• Log

## Logs

There were no log entries made this request

====
• Variables

## View Variables

• meta_keywordsCakePHP, Blog, Post, Ivo Filot
• meta_descriptionOn the fly generation of zip files
• title_for_layoutOn the fly generation of zip files
• tags(array)
• 0(array)
• Tag(array)
• id6
• nameCakePHP
• iconpictonic-prog-cakephp
• colorprimary
• Post(array)
• 0(array)
• id11
• titleBeautiful flash messages in CakePHP
• content## Introduction Using CakePHP's Session component we can display a flash message after a redirect(). It provides an elegant way of informing a user what happened after the page refresh. For example, when the user saves a post, you can inform the user that the post has either been saved correctly, or that is has failed to do so. To make the user more aware of this message we show in this tutorial how to add CSS styling to the flash message and to provide a much more elegant way of displaying these flash messages, we show how to let the message fade in and out. We are going to use a combination of CSS and jQuery. We will make use of the animate.css package as provided by Daniel Eden. For some nice icons, we will use the font-awesome library a well. ### Demo ![Flash dropdown in CakePHP](http://www.ivofilot.nl/images/2/beatiful_flash_messages.jpg) Curious for the final result? [Click here to see a demo.](http://www.ivofilot.nl/demo/demo_1) ## Requirements For this tutorial, we will make use of the following tools: * [CakePHP](http://cakephp.org/) * [Bootstrap](http://getbootstrap.com/) * [Font-awesome](http://fortawesome.github.io/Font-Awesome/) * [jQuery](http://jquery.com/) * [animate.css from Daniel Eden](http://daneden.github.io/animate.css/) ##Code ### Controller In your Controller, normally you would only use the first argument of the setFlash function of the Session component. We expand on the default behaviour by adding a second argument which refers to an Element we are going to use to display the Flash message. <pre> <code class="php"> $this->Session->setFlash('Item successfully saved', 'flash_success); </code> </pre> ### Template Create the Element file */app/View/Element/flash_success.ctp* and give it the content as shown below. Note that we have used here for the styling the Bootstrap framework, but you could in principle set your own styling. <pre> <code class="html"> <div class="alert alert-success alert-hover" id="flash_success"> <button class="close">x</button> <i class="fa fa-check"></i> <?php echo$message; ?> </div> <script type="text/javascript"> $('#flash_success').addClass('animated fadeInDown');$( ".close" ).click(function() { jQuery('#flash_success').removeClass('fadeInDown'); jQuery('#flash_success').addClass('fadeOutUp'); }); </script> </code> </pre> Let's go through the code. The first four lines generate the message. Lines 6-12 is where the magic happens. When the document is loaded, jQuery automatically adds two CSS styles to the div block in line 1. Note that the #flash_success identifier refers to the id attribute of the div. Then, a function is added which triggers when the user clicks on the button with the 'close' class. The function then removes the fadeInDown class and adds the fadeOutUp class. The only thing left to do is to define then the fadeInDown and fadeOutUp classes and the animated class that goes along with it. These are redefined in animate.css as provided by Daniel Eden, but we will only copy those classes from there which we are actually using. Put the following in *app/webroot/css/animate.css*: <pre> <code class="css"> .animated { -webkit-animation-duration: 1s; animation-duration: 1s; -webkit-animation-fill-mode: both; animation-fill-mode: both; } @-webkit-keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .fadeInDown { -webkit-animation-name: fadeInDown; animation-name: fadeInDown; } @-webkit-keyframes fadeOutUp { 0% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-20px); transform: translateY(-20px); } } @keyframes fadeOutUp { 0% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px); } } .fadeOutUp { -webkit-animation-name: fadeOutUp; animation-name: fadeOutUp; } </code> </pre> Don't forget to link to font-awesome for the icons and to the jQuery library in your layout file (*app/View/Layout/Default.ctp*): <pre> <code class="php"> echo $this->Html->css('animate.css'); echo$this->Html->css('font-awesome.min'); echo $this->Html->script('jquery.min.js'); </code> </pre> ## References and further reading * [CakePHP](http://cakephp.org/) * [Bootstrap](http://getbootstrap.com/) * [Font-awesome](http://fortawesome.github.io/Font-Awesome/) * [jQuery](http://jquery.com/) * [animate.css from Daniel Eden](http://daneden.github.io/animate.css/) • watched11906 • active(true) • created2014-06-06 13:54:11 • modified2015-06-30 17:40:49 • 1(array) • id14 • titleToggling a database flag field in CakePHP • contentSometimes we have to make a data model that involves various flags to indicate different statuses for an object. For example, a Post can have attributes like *active* or *comments_allowed* which can have either the value 0 (off) or 1 (on). Often we wish to toggle these values; how do this in an elegant fashion? (scroll to the bottom for the one-liner) We could grab the value: <pre> <code class="php">$post = $this->Post->read(null,$id); if($post['Post']['active'] == 1) {$post['Post']['active'] = 0; } else { $post['Post']['active'] = 1; }$this->Post->save($post); </code> </pre> More elegant would be to use a ternary operator like so: <pre> <code class="php">$post = $this->Post->read(null,$id); $post['Post']['active'] =$post['Post']['active'] == 1 ? 0 : 1; $this->Post->save($post); </code> </pre> But in principle, we do not even need to grab the old value from the database to toggle the value: <pre> <code class="php"> $post =$this->Post->query("UPDATE posts SET active= IF(active=1, 0, 1) WHERE id='$id'"); </code> </pre> We can even get rid of the if statement: <pre> <code class="php">$post = $this->Post->query("UPDATE posts SET active= 1 - active WHERE id='$id'"); </code> </pre> And there you go, a simple one-liner.
• watched6883
• active(true)
• created2014-06-07 02:40:34
• modified2015-06-30 17:37:49
• 2(array)
• id15
• title404 errors when using CakePHP plugin due to MultiViews module
• watched4588
• active(true)
• created2014-06-09 09:29:39
• modified2015-06-30 17:39:04
• 3(array)
• id16
• titleOn the fly generation of zip files
• content## Introduction Let's consider the following situation: You have built a file repository system or a photo album in CakePHP and you would like the user to download all the files or photos in a specific folder in one convenient zip file. In principle, you have several options how to proceed: * Pre-generate the zip file and provide a link to that file * Generate the zip file on request and give the link to the user * Generate the zip file on the fly Here, I will discuss why the latter option is most likely the best and how to implement this in CakePHP. Without any doubt, the first two options require both computational time and disk space. If you pre-generate the zip file, this means that for each file or photo you have added, some time is spent adding the item to the zip file. Consequently, uploading of new content will take more time. Moreover, you will need almost 1.5x the normal amount of storage space, as you need to store both the raw images as well as the zip file. Only generating the zip file when it is being requested by a user would alleviate this somewhat, as you only need storage space when the user places a request. You could for instance use a temporary file system for this. The problem with this approach is that the user is required to wait an additional amount of time for the zip file to be created first before it can be offered for download. Generating a zip file on the fly has in this sense many benefits. First of all, because the zip file is generated on the fly, the download starts immediately and the user does not have to wait. Secondly, because the file is immediately sent to the user, it does not need to be saved on the disk. Most likely, the zip compression algorithm will be fast enough that no significant reduction in download speed is noted, although this depends of course on the technical specifications of your system. Indeed, the only major drawback of this method is that the processor has to work harder during the download and multiple simultaneous downloads could give very high load averages. ## Implementation We assume for the sake of simplicity that you have a particular method in your controller which gives an array of the locations of all the files which need to be sent to the user. In the example below, we have a Folder Model with a hasMany association to a File class. **We have chosen these names purely for this example, for a real application I would not recommend using a class named 'File' as there already exists such a class in the CakePHP library!!** <pre> <code lang="php"> public function zip($id = null) {$this->layout = 'empty'; $this->Folder->recursive = 1;$this->set('folder', $this->Folder->read(null,$id)); } </code> </pre> Note that we have set an empty file for the layout. (i.e. no layout) Our view file looks like the following: <pre> <code lang="php"> <?php header('Content-Type: application/zip'); header('Content-disposition: attachment; filename="'.$folder['Folder']['name'].'.zip"'); header('Content-Transfer-Encoding: binary'); mb_http_output('pass'); ob_clean(); flush();$files = ''; foreach($folder['File'] as$file) { $files .= '"'.$folder['path'].'" '; } $fp = popen('zip -r - '.$files, 'r'); $bufsize = 8192;$buff = ''; while( !feof($fp) ) {$buff = fread($fp,$bufsize); ob_flush(); echo $buff; } pclose($fp); ?> </code> </pre> Let us go through the code. The first three lines inform the browser that we are going to send a zip file with binary encoding. Moreover, we can already give the file a name which we have conveniently extracted from our Folder object. The *mb\_http\_output('pass');* line ensures us that PHP is not going to mess with the encoding. *ob\_clean()* and *flush()* flush the output and write buffers. Next, we convert the array of file locations to a string. We use this string to tell the zip program (make sure you have it installed on your operating system) which files to compress. The output of the zip program is not a zip file, but a stream, which is conveniently piped to PHP using the *popen* function and referenced by a resource handler. Now comes the interesting part, via this resource handler a chunk of data is read (as set in $bufsize, which is 8kb) to the read buffer, the read buffer is then flushed and the output is echoed. In other words, in segments of 8kb the download is being 'fed'. This is a continuous loop until an end-of-file (EOF) is encountered. After that, the pipe is closed. This way, we only need a very small amount of memory because we fill up a chunk of 8kb, parse it to the browser and then release the memory. If we would let the zip program stream its output completely to the memory and then echo this, we would most likely encounter a memory allocation error. • watched11981 • active(true) • created2014-06-12 13:54:11 • modified2015-06-30 17:35:44 • relatedposts(array) • 0(array) • id11 • titleBeautiful flash messages in CakePHP • content## Introduction Using CakePHP's Session component we can display a flash message after a redirect(). It provides an elegant way of informing a user what happened after the page refresh. For example, when the user saves a post, you can inform the user that the post has either been saved correctly, or that is has failed to do so. To make the user more aware of this message we show in this tutorial how to add CSS styling to the flash message and to provide a much more elegant way of displaying these flash messages, we show how to let the message fade in and out. We are going to use a combination of CSS and jQuery. We will make use of the animate.css package as provided by Daniel Eden. For some nice icons, we will use the font-awesome library a well. ### Demo ![Flash dropdown in CakePHP](http://www.ivofilot.nl/images/2/beatiful_flash_messages.jpg) Curious for the final result? [Click here to see a demo.](http://www.ivofilot.nl/demo/demo_1) ## Requirements For this tutorial, we will make use of the following tools: * [CakePHP](http://cakephp.org/) * [Bootstrap](http://getbootstrap.com/) * [Font-awesome](http://fortawesome.github.io/Font-Awesome/) * [jQuery](http://jquery.com/) * [animate.css from Daniel Eden](http://daneden.github.io/animate.css/) ##Code ### Controller In your Controller, normally you would only use the first argument of the setFlash function of the Session component. We expand on the default behaviour by adding a second argument which refers to an Element we are going to use to display the Flash message. <pre> <code class="php">$this->Session->setFlash('Item successfully saved', 'flash_success); </code> </pre> ### Template Create the Element file */app/View/Element/flash_success.ctp* and give it the content as shown below. Note that we have used here for the styling the Bootstrap framework, but you could in principle set your own styling. <pre> <code class="html"> <div class="alert alert-success alert-hover" id="flash_success"> <button class="close">x</button> <i class="fa fa-check"></i> <?php echo $message; ?> </div> <script type="text/javascript">$('#flash_success').addClass('animated fadeInDown'); $( ".close" ).click(function() { jQuery('#flash_success').removeClass('fadeInDown'); jQuery('#flash_success').addClass('fadeOutUp'); }); </script> </code> </pre> Let's go through the code. The first four lines generate the message. Lines 6-12 is where the magic happens. When the document is loaded, jQuery automatically adds two CSS styles to the div block in line 1. Note that the #flash_success identifier refers to the id attribute of the div. Then, a function is added which triggers when the user clicks on the button with the 'close' class. The function then removes the fadeInDown class and adds the fadeOutUp class. The only thing left to do is to define then the fadeInDown and fadeOutUp classes and the animated class that goes along with it. These are redefined in animate.css as provided by Daniel Eden, but we will only copy those classes from there which we are actually using. Put the following in *app/webroot/css/animate.css*: <pre> <code class="css"> .animated { -webkit-animation-duration: 1s; animation-duration: 1s; -webkit-animation-fill-mode: both; animation-fill-mode: both; } @-webkit-keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .fadeInDown { -webkit-animation-name: fadeInDown; animation-name: fadeInDown; } @-webkit-keyframes fadeOutUp { 0% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-20px); transform: translateY(-20px); } } @keyframes fadeOutUp { 0% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px); } } .fadeOutUp { -webkit-animation-name: fadeOutUp; animation-name: fadeOutUp; } </code> </pre> Don't forget to link to font-awesome for the icons and to the jQuery library in your layout file (*app/View/Layout/Default.ctp*): <pre> <code class="php"> echo$this->Html->css('animate.css'); echo $this->Html->css('font-awesome.min'); echo$this->Html->script('jquery.min.js'); </code> </pre> ## References and further reading * [CakePHP](http://cakephp.org/) * [Bootstrap](http://getbootstrap.com/) * [Font-awesome](http://fortawesome.github.io/Font-Awesome/) * [jQuery](http://jquery.com/) * [animate.css from Daniel Eden](http://daneden.github.io/animate.css/)
• watched11906
• active(true)
• created2014-06-06 13:54:11
• modified2015-06-30 17:40:49
• 1(array)
• id14
• titleToggling a database flag field in CakePHP
• contentSometimes we have to make a data model that involves various flags to indicate different statuses for an object. For example, a Post can have attributes like *active* or *comments_allowed* which can have either the value 0 (off) or 1 (on). Often we wish to toggle these values; how do this in an elegant fashion? (scroll to the bottom for the one-liner) We could grab the value: <pre> <code class="php"> $post =$this->Post->read(null, $id); if($post['Post']['active'] == 1) { $post['Post']['active'] = 0; } else {$post['Post']['active'] = 1; } $this->Post->save($post); </code> </pre> More elegant would be to use a ternary operator like so: <pre> <code class="php"> $post =$this->Post->read(null, $id);$post['Post']['active'] = $post['Post']['active'] == 1 ? 0 : 1;$this->Post->save($post); </code> </pre> But in principle, we do not even need to grab the old value from the database to toggle the value: <pre> <code class="php">$post = $this->Post->query("UPDATE posts SET active= IF(active=1, 0, 1) WHERE id='$id'"); </code> </pre> We can even get rid of the if statement: <pre> <code class="php"> $post =$this->Post->query("UPDATE posts SET active= 1 - active WHERE id='$id'"); </code> </pre> And there you go, a simple one-liner. • watched6883 • active(true) • created2014-06-07 02:40:34 • modified2015-06-30 17:37:49 • comments0 • 2(array) • id15 • title404 errors when using CakePHP plugin due to MultiViews module • contentThis short post is for everyone using CakePHP plugins and getting 404 errors when they try to access a Controller in their plugin. I spend 1 hour puzzling to find out what could possible be wrong with my plugin. Especially considering that the plugin was already running perfectly on several production servers. It turned out it had something to do with my Apache settings. What was happening? I created a plugin, for the sake of this post let's call it 'MyPlugin' and I wanted to access the controller 'MyController'. Normally, you would point your browser to http://localhost/my_app/my_plugin/my_controller, but instead of getting the expected page, I got a 404 error. Interestingly, the CakePHP logs did not show anything, meaning that the framework was not even being accessed. Eventually, I read the Apache logs and found this particular message: <pre> <code class="text"> [Mon Jun 09 09:19:06 2014] [error] [client 127.0.0.1] Negotiation: discovered file(s) matching request: /var/www/myapp (None could be negotiated)., referer: http://localhost/myapp/ </code> </pre> After some Googling I found out that it hat something to do with the MultiView module of Apache. Let me quote the effect of this module: >If the server receives a request for /some/dir/foo, if /some/dir has MultiViews enabled, and > /some/dir/foo does not exist, then the server reads the directory looking for files named > foo.*, and effectively fakes up a type map which names all those files, assigning them the > same media types and content-encodings it would have if the client had asked for one of > them by name. It then chooses the best match to the client's requirements. Apache was thus finding some physical file on a per-directory basis and then using Content Negotiation to try and figure out which file it should serve up based on the request headers. And, since I didn't have any file types configured for content negotiation, Apache didn't know how to respond and just returned a 404. In the end, I just disabled the MultiViews module in the Apache settings: <pre> <code lang="apache"> Options Indexes FollowSymLinks -MultiViews </code> </pre> and everything worked accordingly. This clearly shows that using the default settings of Apache without really knowing what they do it a very bad thing. • watched4588 • active(true) • created2014-06-09 09:29:39 • modified2015-06-30 17:39:04 • comments1 • post(array) • Post(array) • id16 • titleOn the fly generation of zip files • content## Introduction Let's consider the following situation: You have built a file repository system or a photo album in CakePHP and you would like the user to download all the files or photos in a specific folder in one convenient zip file. In principle, you have several options how to proceed: * Pre-generate the zip file and provide a link to that file * Generate the zip file on request and give the link to the user * Generate the zip file on the fly Here, I will discuss why the latter option is most likely the best and how to implement this in CakePHP. Without any doubt, the first two options require both computational time and disk space. If you pre-generate the zip file, this means that for each file or photo you have added, some time is spent adding the item to the zip file. Consequently, uploading of new content will take more time. Moreover, you will need almost 1.5x the normal amount of storage space, as you need to store both the raw images as well as the zip file. Only generating the zip file when it is being requested by a user would alleviate this somewhat, as you only need storage space when the user places a request. You could for instance use a temporary file system for this. The problem with this approach is that the user is required to wait an additional amount of time for the zip file to be created first before it can be offered for download. Generating a zip file on the fly has in this sense many benefits. First of all, because the zip file is generated on the fly, the download starts immediately and the user does not have to wait. Secondly, because the file is immediately sent to the user, it does not need to be saved on the disk. Most likely, the zip compression algorithm will be fast enough that no significant reduction in download speed is noted, although this depends of course on the technical specifications of your system. Indeed, the only major drawback of this method is that the processor has to work harder during the download and multiple simultaneous downloads could give very high load averages. ## Implementation We assume for the sake of simplicity that you have a particular method in your controller which gives an array of the locations of all the files which need to be sent to the user. In the example below, we have a Folder Model with a hasMany association to a File class. **We have chosen these names purely for this example, for a real application I would not recommend using a class named 'File' as there already exists such a class in the CakePHP library!!** <pre> <code lang="php"> public function zip($id = null) { $this->layout = 'empty';$this->Folder->recursive = 1; $this->set('folder',$this->Folder->read(null, $id)); } </code> </pre> Note that we have set an empty file for the layout. (i.e. no layout) Our view file looks like the following: <pre> <code lang="php"> <?php header('Content-Type: application/zip'); header('Content-disposition: attachment; filename="'.$folder['Folder']['name'].'.zip"'); header('Content-Transfer-Encoding: binary'); mb_http_output('pass'); ob_clean(); flush(); $files = ''; foreach($folder['File'] as $file) {$files .= '"'.$folder['path'].'" '; }$fp = popen('zip -r - '.$files, 'r');$bufsize = 8192; $buff = ''; while( !feof($fp) ) { $buff = fread($fp, $bufsize); ob_flush(); echo$buff; } pclose($fp); ?> </code> </pre> Let us go through the code. The first three lines inform the browser that we are going to send a zip file with binary encoding. Moreover, we can already give the file a name which we have conveniently extracted from our Folder object. The *mb\_http\_output('pass');* line ensures us that PHP is not going to mess with the encoding. *ob\_clean()* and *flush()* flush the output and write buffers. Next, we convert the array of file locations to a string. We use this string to tell the zip program (make sure you have it installed on your operating system) which files to compress. The output of the zip program is not a zip file, but a stream, which is conveniently piped to PHP using the *popen* function and referenced by a resource handler. Now comes the interesting part, via this resource handler a chunk of data is read (as set in$bufsize, which is 8kb) to the read buffer, the read buffer is then flushed and the output is echoed. In other words, in segments of 8kb the download is being 'fed'. This is a continuous loop until an end-of-file (EOF) is encountered. After that, the pipe is closed. This way, we only need a very small amount of memory because we fill up a chunk of 8kb, parse it to the browser and then release the memory. If we would let the zip program stream its output completely to the memory and then echo this, we would most likely encounter a memory allocation error.
• watched11981
• active(true)
• created2014-06-12 13:54:11
• modified2015-06-30 17:35:44
• $request->data(empty) •$this->validationErrors(array)
• Post(empty)
• Comment(empty)
• Tag(empty)
• PostsTag(empty)
• ParentComment(empty)
• 0Auth
• 1Navbar
• 3Flag
• 4Markdown
• 5Number
• 6SimpleGraph
• 7DebugTimer
• 8Toolbar
• 9Html
• 10Form
• 11Session
• 12HtmlToolbar
====
• Environment

## App Constants

Constant Value
CONFIG /customers/e/2/e/ivofilot.nl/httpd.www/app/Config/

## CakePHP Constants

Constant Value
APP /customers/e/2/e/ivofilot.nl/httpd.www/app/
APP_DIR app
APPLIBS /customers/e/2/e/ivofilot.nl/httpd.www/app/Lib/
CACHE /customers/e/2/e/ivofilot.nl/httpd.www/app/tmp/cache/
CAKE /customers/e/2/e/ivofilot.nl/httpd.www/lib/Cake/
CAKE_CORE_INCLUDE_PATH /customers/e/2/e/ivofilot.nl/httpd.www/lib
CORE_PATH /customers/e/2/e/ivofilot.nl/httpd.www/lib/
CAKE_VERSION 2.10.13
CSS /customers/e/2/e/ivofilot.nl/httpd.www/app/webroot/css/
CSS_URL css/
DS /
FULL_BASE_URL https://ivofilot.nl
IMAGES /customers/e/2/e/ivofilot.nl/httpd.www/app/webroot/img/
IMAGES_URL img/
JS /customers/e/2/e/ivofilot.nl/httpd.www/app/webroot/js/
JS_URL js/
LOGS /customers/e/2/e/ivofilot.nl/httpd.www/app/tmp/logs/
ROOT /customers/e/2/e/ivofilot.nl/httpd.www
TESTS /customers/e/2/e/ivofilot.nl/httpd.www/app/Test/
TMP /customers/e/2/e/ivofilot.nl/httpd.www/app/tmp/
VENDORS /customers/e/2/e/ivofilot.nl/httpd.www/vendors/
WEBROOT_DIR webroot
WWW_ROOT /customers/e/2/e/ivofilot.nl/httpd.www/app/webroot/

## PHP Environment

Environment Variable Value
Php Version 7.4.14
Onecom Domain Name ivofilot.nl
Onecom Domain Root /customers/e/2/e/ivofilot.nl/
Onecom Memorylimit 1073741824
Onecom Cpu Shares 1024
Onecom Exec latest
Onecom Dir Layout Ver 0
Content Length 0
Http Connection close
Script Name /app/webroot/index.php
Request Uri /posts/view/16/On+the+fly+generation+of+zip+files
Query String
Request Method GET
Server Protocol HTTP/1.1
Gateway Interface CGI/1.1
Redirect Url /app/webroot/posts/view/16/On+the+fly+generation+of+zip+files
Remote Port 41440
Script Filename /customers/e/2/e/ivofilot.nl/httpd.www/app/webroot/index.php
Context Document Root /var/www
Context Prefix
Request Scheme https
Server Port 80
Server Name ivofilot.nl
Server Software Apache
Server Signature
Path /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Http X Varnish 971606230
Http Accept Encoding gzip
Http Host ivofilot.nl
Http X Onecom Host ivofilot.nl
Http X Forwarded Proto https
Http X Onecom Forwarded Proto https
Http X Forwarded For 184.72.102.217
Http Accept Language en-US,en;q=0.5
Http Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Http User Agent CCBot/2.0 (https://commoncrawl.org/faq/)
Env Vcv Env Addons Id one.com
Env Vcv Token Url https://wpapi.one.com/api/v1.0/plugins/visualcomposer/activate
Onecom One Photo Url https://onephoto.one.com/domain_discover
Onecom Webshop Host webshop2.cst.webpod8-cph3.one.com
Https on
Onecom Tmpdir /customers/e/2/e/ivofilot.nl//tmp
Domain Name ivofilot.nl
Onecom Document Root /customers/e/2/e/ivofilot.nl/httpd.www
Document Root /customers/e/2/e/ivofilot.nl/httpd.www
Redirect Status 200
Redirect Env Vcv Env Addons Id one.com
Redirect Env Vcv Token Url https://wpapi.one.com/api/v1.0/plugins/visualcomposer/activate
Redirect Onecom One Photo Url https://onephoto.one.com/domain_discover
Redirect Onecom Wp Addons Api https://wpapi.one.com
Redirect Onecom Webshop Host webshop2.cst.webpod8-cph3.one.com
Redirect Https on
Redirect Onecom Cpu Shares 1024
Redirect Onecom Memorylimit 1073741824
Redirect Onecom Exec latest
Redirect Onecom Dir Layout Ver 0
Redirect Onecom Tmpdir /customers/e/2/e/ivofilot.nl//tmp
Redirect Onecom Domain Root /customers/e/2/e/ivofilot.nl/
Redirect Onecom Domain Name ivofilot.nl
Redirect Domain Name ivofilot.nl
Redirect Onecom Document Root /customers/e/2/e/ivofilot.nl/httpd.www
Redirect Document Root /customers/e/2/e/ivofilot.nl/httpd.www
Redirect Redirect Status 200
Redirect Redirect Env Vcv Env Addons Id one.com
Redirect Redirect Env Vcv Token Url https://wpapi.one.com/api/v1.0/plugins/visualcomposer/activate
Redirect Redirect Onecom One Photo Url https://onephoto.one.com/domain_discover
Redirect Redirect Onecom Wp Addons Api https://wpapi.one.com
Redirect Redirect Onecom Webshop Host webshop2.cst.webpod8-cph3.one.com
Redirect Redirect Https on
Redirect Redirect Onecom Cpu Shares 1024
Redirect Redirect Onecom Memorylimit 1073741824
Redirect Redirect Onecom Exec latest
Redirect Redirect Onecom Dir Layout Ver 0
Redirect Redirect Onecom Tmpdir /customers/e/2/e/ivofilot.nl//tmp
Redirect Redirect Onecom Domain Root /customers/e/2/e/ivofilot.nl/
Redirect Redirect Onecom Domain Name ivofilot.nl
Redirect Redirect Domain Name ivofilot.nl
Redirect Redirect Onecom Document Root /customers/e/2/e/ivofilot.nl/httpd.www
Redirect Redirect Document Root /customers/e/2/e/ivofilot.nl/httpd.www
Fcgi Role RESPONDER
Php Self /app/webroot/index.php
Request Time Float 1611319498.8131
Request Time 1611319498
====
• Include

## Included Files

#### Include Paths

• 0/customers/e/2/e/ivofilot.nl/httpd.www/lib
• 2/usr/share/php
• 3-> /customers/e/2/e/ivofilot.nl/httpd.www/lib/Cake/

#### Included Files

• core(array)
• Behavior(array)
• 0CORE/Model/Behavior/TreeBehavior.php
• Cache(array)
• 0CORE/Cache/Cache.php
• 1CORE/Cache/Engine/FileEngine.php
• 2CORE/Cache/CacheEngine.php
• Component(array)
• 0CORE/Controller/Component/SessionComponent.php
• 1CORE/Controller/Component/AuthComponent.php
• 2CORE/Controller/Component/Auth/FormAuthenticate.php
• 3CORE/Controller/Component/Auth/BaseAuthenticate.php
• Config(array)
• 0CORE/Config/routes.php
• 1CORE/Config/config.php
• Controller(array)
• 0CORE/Controller/Controller.php
• 1CORE/Controller/ComponentCollection.php
• 2CORE/Controller/Component.php
• Datasource(array)
• 0CORE/Model/Datasource/CakeSession.php
• 1CORE/Model/Datasource/Database/Mysql.php
• 2CORE/Model/Datasource/DboSource.php
• 3CORE/Model/Datasource/DataSource.php
• Error(array)
• 0CORE/Error/exceptions.php
• 1CORE/Error/ErrorHandler.php
• I18n(array)
• 0CORE/I18n/I18n.php
• 1CORE/I18n/L10n.php
• Log(array)
• 0CORE/Log/CakeLog.php
• 1CORE/Log/LogEngineCollection.php
• 2CORE/Log/Engine/FileLog.php
• 3CORE/Log/Engine/BaseLog.php
• Model(array)
• 0CORE/Model/Model.php
• 1CORE/Model/BehaviorCollection.php
• 2CORE/Model/ConnectionManager.php
• 3CORE/Model/ModelBehavior.php
• Network(array)
• 0CORE/Network/CakeRequest.php
• 1CORE/Network/CakeResponse.php
• Other(array)
• 0CORE/bootstrap.php
• 1CORE/basics.php
• 2CORE/Core/App.php
• 3CORE/Core/Configure.php
• 4CORE/Core/CakePlugin.php
• 5CORE/Event/CakeEventListener.php
• 6CORE/Event/CakeEvent.php
• 7CORE/Event/CakeEventManager.php
• 8CORE/Core/CakeObject.php
• Routing(array)
• 0CORE/Routing/Dispatcher.php
• 1CORE/Routing/Filter/AssetDispatcher.php
• 2CORE/Routing/DispatcherFilter.php
• 3CORE/Routing/Filter/CacheDispatcher.php
• 4CORE/Routing/Router.php
• 5CORE/Routing/Route/CakeRoute.php
• 6CORE/Routing/Route/PluginShortRoute.php
• Utility(array)
• 0CORE/Utility/Hash.php
• 1CORE/Utility/Inflector.php
• 2CORE/Utility/ObjectCollection.php
• 3CORE/Utility/Debugger.php
• 4CORE/Utility/CakeText.php
• 5CORE/Utility/ClassRegistry.php
• 6CORE/Utility/Set.php
• View(array)
• 0CORE/View/HelperCollection.php
• app(array)
• Config(array)
• 0APP/Config/core.php
• 1APP/Config/bootstrap.php
• 2APP/Config/routes.php
• 3APP/Config/database.php
• Controller(array)
• 0APP/Controller/PostsController.php
• 1APP/Controller/AppController.php
• Model(array)
• 0APP/Model/Post.php
• 1APP/Model/AppModel.php
• 2APP/Model/Comment.php
• 3APP/Model/Tag.php
• Other(array)
• 0APP/webroot/index.php
• plugins(array)
• DebugKit(array)
• Component(array)
• 0DebugKit/Controller/Component/ToolbarComponent.php
• Plugin(array)
• 0DebugKit/Lib/DebugMemory.php
• 1DebugKit/Lib/Panel/HistoryPanel.php
• 2DebugKit/Lib/DebugPanel.php
• 3DebugKit/Lib/Panel/SessionPanel.php
• 4DebugKit/Lib/Panel/RequestPanel.php
• 5DebugKit/Lib/Panel/SqlLogPanel.php
• 6DebugKit/Lib/Panel/TimerPanel.php
• 7DebugKit/Lib/Panel/LogPanel.php
• 8DebugKit/Lib/Log/Engine/DebugKitLog.php
• 9DebugKit/Lib/Panel/VariablesPanel.php
• 10DebugKit/Lib/Panel/EnvironmentPanel.php
• 11DebugKit/Lib/Panel/IncludePanel.php
• 12DebugKit/Lib/DebugTimer.php
• Markdown(array)
• Config(array)
• 0Markdown/Config/bootstrap.php
• Plugin(array)
• 0Markdown/Lib/Error/MarkdownExceptions.php
====