Archive for the 'PHP' Category

How to make your own Zend Framework Resource Plugin

Saturday, July 4th, 2009

A recent release of Zend Framework intruduced Zend_Application: an organized way to bootstrap the framework without the need of an ugly boostrap.php file.
It comes with plugins support too, so let's see how to create one.

With Zend_Application you can not only setup all the components required (Zend_Table, Zend_View, Zend_Navigation, ecc) but you can also setup your own custom plugin, called Resource.

In the Zend lingo a Resource is a plugin loaded by the Zend_Application and configured through the application.ini file or in the Bootstrap.php class.

A Resource can be used to setup a standard Zend component, routes, controller plugins or your custom component.

Lets see how.

Use the Bootstrap class

When you create an empty Zend Framework project with the zf create project command, you'll find a Bootstrap.php file in your application folder.

In this file there is a Bootstrap class that you can use to define your own resources by simply creating a method with the _init prefix.

For example:

PHP:
  1. class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
  2. {
  3.     protected function _initDoctype()
  4.     {
  5.         $this->bootstrap('view');
  6.         $view = $this->getResource('view');
  7.         $view->doctype('XHTML1_STRICT');
  8.     }
  9. }

This setup a resource called Doctype.
This resource simply set the doctype property in the view (which is a resource too).

With the $this->bootstrap('view'); line, the Doctype resource requires the initialization of another resource: the View. In this way you can define dependencies in your resources.

You can even override the definition of a standard resource with your own, for example:

PHP:
  1. protected function _initView()   
  2. {
  3.      $view = new Zend_View();
  4.      return $view;
  5. }

In this way Zend_Application will use the view resource defined in your _initView method instead of the standard one.

With this method you can customize your Zend Framework with ease, but when you need to configure a standard component, like the db connection, there is a better approach: using the application.ini.

Use the application.ini file

Another gift of the zf create project command is the application/configs/application.ini file.

It contains all the configurations options of your application.
You can for example use it to configure your db:

INI:
  1. resources.db.adapter = "pdo_mysql"
  2. resources.db.params.host = "localhost"
  3. resources.db.params.username = "root"
  4. resources.db.params.password = ""
  5. resources.db.params.dbname = "test"
  6. resources.db.isDefaultTableAdapter = true

You could use either the Bootrap or the application.ini method for this task, however I personally prefer this method when configuring parameters that changes across the servers (development, testing, production) like the include paths or the db connection options.

A better approach: Create a custom resource class

To improve reusability, you can create a custom resource class, that can be easily distributed and configured through your application.ini.

Its simple: in your library create a MyResource folder and a Custom class:

PHP:
  1. //In library/MyResource/Custom.php:
  2. class MyResource_Custom extends Zend_Application_Resource_ResourceAbstract
  3. {
  4.     public function init() {
  5.         $this->getBootstrap()->bootstrap('view');
  6.         $view = $this->getBootstrap()->getResource('view');
  7.         $view->doctype('XHTML1_STRICT');
  8.         return $view;
  9.     }
  10. }

In the init method, you can initialize your resource, and return it.
The object returned will go in the resources repository, and it will be accessible with the $bootstrap->getResource('custom') call in the other resources. (hint: is case insensitive)

To use your newly created resource, you can simply put that in your application.ini:

INI:
  1. ;Add the libray/MyResource folder to the plugin search path:
  2. pluginPaths.MyResource = "MyResource"
  3. ;enable your Custom resource:
  4. resources.custom = true

That's all.

A resource can take configuration options too, for example:

INI:
  1. ;Add the libray/MyResource folder to the plugin search path:
  2. pluginPaths.MyResource = "MyResource"
  3. ;enable your Custom resource while setting the "doctype" option:
  4. resources.custom.doctype = "XHTML1_STRICT"

Zend_Application will automatically initialize a resource when you set a configuration option in it.
If our resource doesn't expect a parameter, you can simply write resources.*resourcename* = true, like in the previous example.

You can access the options from within the resource through the $this->options variable. You can also define a setter method for the option:

PHP:
  1. //In library/MyResource/Custom.php:
  2. class MyResource_Custom extends Zend_Application_Resource_ResourceAbstract
  3. {
  4.     var $doctype = 'XHTML1_STRICT';
  5.     //Setter method for the doctype option:
  6.     public function setDoctype($value) {
  7.         $this->doctype = $value;
  8.     }
  9.  
  10.     public function init() {
  11.         $this->getBootstrap()->bootstrap('view');
  12.         $view = $this->getBootstrap()->getResource('view');
  13.         $view->doctype($this->doctype);
  14.         return $view;
  15.     }

You are now ready to build your distributable plugin for Zend Framework, for other examples see the Zend/Application/Resource folder in the framework distribution or my MadaConsole resource.

MadaConsole – A debug console for Zend Framework

Wednesday, July 1st, 2009

Update: See my guide on How to make your own Zend Framework Resource Plugin

Building Facebook applications with Zend Framework, I've needed a debug console that can shows messages even in a Facebook canvas page, through an ajax request or a redirect.

For this purpose I've build a custom plugin for ZF that fits the need: it will sit under your pages and shows all your dumped object, messages and queries.

MadaConsole debug console

MadaConsole debug console

Usage is really simple: copy in your library and add the plugin in your Boostrap or application.ini.
For a detailed guide see below.

Features

  • Trace custom messages easily: debug("A debug message");
  • Dump variables easily in your code with the pass-through function:
    Before: array_count_values($arr); After: array_count_values(debug($arr));
  • Shows the queries executed by your Zend_Table
  • Shows debug messages even through redirects or ajax requests

Install

Installing the plugin is simple:

  1. Download the archive: MadaConsole
  2. Unzip the library folder of the archive in the library folder of your Zend Framework application

Then you can configure it with two methods: through your application.ini or in the Bootsrap class.

Method1: application.ini

Add those lines after [development : production]:

pluginPaths.MadaConsole = "MadaConsole"
resources.console = true

Method 2: Bootstrap class

Add this method:

PHP:
  1. public function _initConsole() {
  2.      return MadaConsole_Console::initialize($this);
  3. }

Usage

Now in your code you can do:

PHP:
  1. debug("A debug message");
  2. debug($myArray);
  3.  
  4. $data = debug($myModel->findAll());
  5. //It will recognize Zend_Db_* objects

With the pass-through functionality you can simply add the debug() function in your existing statements with ease: $var = debug(my_func($foo));

To see the debug output when you do an ajax request, you can open a page of your choice in another browser window: the debug console will show all the messages site-wide.

Downloads

MadaConsole plugin
MadaConsole example Zend Framework project

How the debug console looks like

How the debug console looks like

Build a Facebook application with Zend Framework

Sunday, May 10th, 2009

I've just finished a Facebook game, so here a quick tutorial on how to use Zend Framework to build a Facebook Application.

Facebook basics

You can integrate your application with facebook in two way: with an IFRAME (using XFBML) or with FBML.
(If you are already familiar with how a Facebook application work you can skip this step)

You can embed your content in a Facebook page through FBML or use an IFRAME

You can embed your content in a Facebook page through FBML or use an IFRAME

Build your website using FBML tags

Facebook will be a proxy between your server and the user's browser: it will load a page form your own server like a normal browser, parse it, "enhance" it with his components and then embed it in a Facebook page. You will use standard HTML tags plus FBML tags.

Pros:

  • You can use almost standard HTML, but with some other tags like: <fb:comments ... />, <fb:wall ... />
  • Feature rich: with a simple tag integrate wall, comments, tabs, dashboard and many others components (Complete list of facebook tags)
  • The url in the browser follow the navigation
  • Faster when you need data from facebook database (groups, friends, etc.)

Cons:

  • Slower page load times: restart the facebook chat at every request
  • Can't use standard JavaScript, only FBJS that's almost equals but your existing scripts and libraries won't work
  • Its losing ground: Facebook is trying to take IFRAME/XFBML on par

Embed using an IFRAME

Use an iframe: just build a standard web site and let FB embeds it in an iframe

Pros:

  • Better ajax support: you can use standard javascript, html and css
  • Easy to debug, its just a standard web site
  • Faster page load times

Cons:

  • Less features than with FBML (but XFBML is rapidly improving)
  • Slower when you need data from facebook database (eg: user's friends, groups, etc.)
  • The url in the browser doesn't follow the navigation
  • A bit more complex

We'll start with FBML since its easier to use and to find help about on the forums.
However take a look at XFBML in the future, since the Facebook team has really improved it recently and they are trying to push them on par.

See facebook wiki for further details on the differences between FMBL and IFRAME.

Integration

First of all, setup your FBML app and write down your api key and secret key.

Just follow the onscreen instructions or the wiki, but care attention to the Canvas Page URL:
it is something like: http://apps.facebook.com/*APPLICATION_NAME* and it is the url through the user can reach your app.

While the Canvas Callback URL (for example: http://www.yourserver.com/fbapp) its the address of your own web server, where you'll deploy your application.
With a dynamic dns service like dyndns you can create a domain name that map to the public address of your development machine, so that you can host your app on your dev machine while allowing Facebook to reach it.

Facebook will map every canvas url: http://apps.facebook.com/*APPLICATION_NAME*/foo to your callback url: http://www.yourserver.com/fbapp/foo

After you have done with the setup, you can use the official PHP client to interact with Facebook.

First, set up an empty Zend Framework project: you can use the Zend Studio for Eclipse wizard or the new Zend_Tool command line.

Use Zend Studio wizard to create an empty Zend Framewrok project

Use Zend Studio wizard to create an empty Zend Framewrok project

Then, download the client and copy the files in the php folder of the archive to the library/facebook folder of you Zend Framework project.

Now you can start using the client.

The Controller

To start, write in your controllers/IndexController.php:

PHP:
  1. require_once 'Zend/Controller/Action.php';
  2.  
  3. $err = error_reporting(E_ERROR);
  4. require_once 'facebook/facebook.php';
  5.  
  6. class IndexController extends Zend_Controller_Action {
  7.   public $apiKey = "22c...s05";
  8.   public $apiSecret = "df6...fb5";
  9.  
  10.   function indexAction()   {
  11.     $facebook = new Facebook($this->apiKey, $this->apiSecret);
  12.     $facebook->require_login();
  13.   }
  14. }

The line: $err = error_reporting(E_ERROR); is needed to hide some strange php notices from the facebook.php file.
In the line 12 we instantiate our client with the api key and secret taken during the application set up.
With the require_login method you can require the user to "add" your application.
Notice that many api features are available only if the user has added the application.

Then in your Zend actions you can call every standard method of the api with the
$facebook->api_client variable, for example:

PHP:
  1. $facebook->api_client->notifications_send(...); //Send a notify to a user
  2. $facebook->api_client->friends_getAppUsers(); //Get the friends of the current user that use your app
  3. //etc...

See the complete list of methods on the wiki and try them with the API console tool.

The View

Now that you know how to interact with Facebook, you can show a simple page containing the "Comments box" component.

In your layouts/main.phtml file write:

HTML:
  1. <?php echo $this->headScript() ?>
  2. <?php echo $this->headStyle() ?>
  3. <?php echo $this->layout()->nav ?>
  4.  
  5. <fb:title><?php echo $this->placeholder('title') ?></fb:title>
  6.  
  7. <?php echo $this->layout()->content ?>

As you can see we don't have to put the standard html opening tags in your layout (<html><head>...) since Facebook will load the page from your server and embed it in his own HTML canvas page.
We had also used our first FBML tag: <fb:title> to set the page title in the browser titlebar (we can't use the ZF headTitle() method here).

Now create a view script for your index action of the index controller views/scripts/index/index.phtml:

HTML:
  1. <?php $this->placeholder('title')->set('My First Facebook App!'); ?>
  2.  
  3. Hello <fb:profile-pic uid='<?php echo $this->fbUserId ?>' linked="true"/>!<br /> <br />
  4.  
  5. Something to say?
  6. <fb:comments xid="myCommentsBox" canpost="true" candelete="false" returnurl="http://apps.facebook.com/*APPLICATION_NAME*">
  7.   <fb:title>My first comment Box</fb:title>
  8. </fb:comments>

This will place a comment box in your page. Notice that the returnurl property need to point to your canvas url and not your callback url. (substitute *APPLICATION_NAME* with your canvas url)

To experiment with the various <fb:* > tags use the FBML console.

Now you can go to your canvas url to see the result:

Your first Facebook app with Zend Framework

Your first Facebook app with Zend Framework

In the HTML source of the canvas page you can see the original FBML used to generate the page and sometimes some errors/warnings.

Javascript and CSS

You can embed JavaScript in your FBML like in HTML, but only a special flavour of JavaScript: FBJS.
It is almost like the standard one, but there are some differences and your existing scripts and libraries won't work.

If you want to embed an external .js file, you'll have to specify the absolute path. Not only: Facebook will keep a copy of your script, even if you modify it, so you'll have to put a timestamp in the url to force a cache refresh.

For example, if you want to embed a script in your index.phtml:

HTML:
  1. <?php $this->headScript()->appendFile('http://www.yourserver.com/fbapp/scripts/js.js?v=' . filemtime('scripts/js.js')) ?>

In this way Facebook will load the script from the url: http://www.yourserver.com/fbapp/scripts/js.js?v=1231707071.
When you'll edit the file, the number will change and FB will reload it.

The same apply to CSS:

HTML:
  1. <?php $this->headStyle()->appendFile('http://www.yourserver.com/fbapp/styles/fbstyle.css?v=' . filemtime('scripts/js.js')) ?>

CSS in FB is almost standard, but use absolute urls.
Also be sure to write clean CSS, otherwise the Facebook parser will sometimes hang without and explanation.

Make things easier

We can use a base controller class.
In library/FacebookController.php write:

PHP:
  1. $err = error_reporting(E_ERROR);
  2. require_once 'facebook/facebook.php';
  3.  
  4. class FacebookController extends Zend_Controller_Action {
  5.   public $simulateFb = false;
  6.   public $useTestApplication = true;
  7.  
  8.   public $apiKey = "28c...b05";
  9.   public $apiSecret = "df6...fb5";
  10.   public $canvasUrl = "http://apps.facebook.com/myfbapp";
  11.  
  12.   public $testApiKey = '28c...b05';
  13.   public $testApiSecret = 'df6...3fb5';
  14.   public $testCanvasUrl = "http://apps.facebook.com/myfbapptest";
  15.  
  16.   public $fbUserId = "1234567";
  17.    
  18.   /**
  19.    * Facebook api
  20.    * @var Facebook
  21.    */
  22.   protected $facebook;
  23.    
  24.     public function init() {   
  25.         if($this->simulateFb) {
  26.             Zend_Session::start();
  27.             parent::init()
  28.         }
  29.         else {
  30.             if($this->useTestApplication) {
  31.                 $this->apiKey = $this->testApiKey;
  32.                 $this->apiSecret = $this->testApiSecret;
  33.                 $this->canvasUrl = $this->testCanvasUrl;
  34.             }
  35.  
  36.             $this->facebook = new Facebook($this->apiKey, $this->apiSecret);
  37.             $session_key = md5($this->facebook->api_client->session_key);
  38.             if(!Zend_Session::isStarted())  {
  39.                 Zend_Session::setId($session_key);
  40.                 Zend_Session::start();
  41.             }
  42.             parent::init()
  43.         }
  44.     }
  45.    
  46.     protected function requireLogin() {
  47.         if(!$this->simulateFb) {
  48.             $this->fbUserId = $this->facebook->require_login();
  49.         }
  50.     }
  51.    
  52.     protected function _redirect($url, array $options = array()) {
  53.         if(!$this->simulateFb) {
  54.            $this->facebook->redirect($this->canvasUrl . $url);
  55.         }
  56.         else {
  57.            parent::_redirect($url, $options);
  58.         }
  59.     }
  60. }
  61.  
  62. header( 'Content-Type: text/html; charset=UTF-8' );

In this way, our IndexController can be rewritten in this simplier way:

PHP:
  1. require_once 'Zend/Controller/Action.php';
  2.  
  3. class IndexController extends FacebookController  {
  4.  
  5.   function indexAction()   {
  6.     $this->requireLogin();
  7.     $this->view->fbUserId = $this->fbUserId;
  8.   }
  9. }

We had made IndexController inherit from FacebookController, and used its method requireLogin, without requiring to set up the Facebook client.

Not only:
with the $simulateFb variable set to true you can preview your site outside the Facebook canvas page without requiring the login (but you'll not see the FBML components).

With $useTestApplication you can use a different pair of api key and secret for the development, while keeping your main application safe. (you need to set up another app to obtain the keys)

Also the FacebookController will allow the standard Zend Framework _redirect method to work and will bind Zend session to the one of Facebook.

Finally in the last line we set up the charset encoding to be sure it will be the same of the Facebook canvas page.

Thats all:
now download the tutorial sources and start building your own Facebook app.

My two cents on Static Typing

Wednesday, May 16th, 2007

While I was wondering if using haXe or Java with my server, I've stumbled upon some discussion on Static Typing (Java) vs. Dynamic Typing (Ruby, Php).
In a really interesting blog post (read the comments too) I found this touching phrase:

"Static typing is Communist Bureaucracy".

In short: since we (should) have pervasive testing, Static Typing is only more testing. However, a comment is enlighting:

I'm finding this logic hard to follow. Testing is good, coverage is good. Static typing is, among other things, a way to have the compiler test your code for type issues. Since we like testing, this makes static typing good, right?

But Static Typing not only prevent you to do mistakes, like typos, it allows your IDE to know what you are doing:

Dynamic Static Typing speeds up development because there is physically less to type since ctrl+space and ctrl+2 write the declarations for you.

Its seems obvious to me that Dynamic Typing is worth the catch only for a quick start or for learning.
Since almost all of the dynamically typed languages (like Actionscript, Perl, Php) have migrated to some kind of Static Typing (eg: Type Inference), there should be a reason.

Periodically, a new Dynamically Typed language spawn, attracting junior/tired programmers with the mirage of the simplicity and the less-to-type-ing, riding the hype until his young audience grows and start doing large projects hoping not to call a function with the wrong arguments.

Only then they realize the big truth: Static Typing is just more informations in the source code.
And more information means less wondering-what-object-was or what-was-the-method-name-and-parameters or who-instantiate-this-object, not to mention static analysis.

And hey, they have invented Javadoc-like documentation and Annotations just to add some more information, why remove one of the most important?

A better print_r: dBug

Friday, March 2nd, 2007

dBug is a php library that print a nice-formatted dump of a variable:

PHP:
  1. include_once("dBug.php");
  2.  
  3. $variable = array(
  4.     "first"=>"1",
  5.     "second",
  6.     "third"=>array(
  7.         "inner third 1",
  8.         "inner third 2"=>"yeah"),
  9.     "fourth");
  10.  
  11. new dBug($variable);

Will be formatted like this:
dBug

Variable types supported are: Arrays, Objects, Recordsets and XML Resources.

Simple CakeAMFPhp How To

Wednesday, November 8th, 2006

It's been a while since I last wrote, sorry! I've been busy with work, thesis and some other suffs (which I'll be posting about in the next days).
One of those is AMFPhp: a method to connect Flash with Php.
Why AMF instead of JSON or XML? Basically because AMF is more mature than JSON, offer a ready-made and really complete set of classes, many utilities like the connection debugger component, a great Php implementation, and, above all, CakeAMFPhp. (AMF is also a binary protocol on top of HTTP, so performance should be better than XML)
CakeAMFPhp is the integration of AMFPhp and Cake, a Rails-inspired php framework. With Cake I've quickly build a prototype with the Bake command-line utility. Then I've started implementing the methods for the AMF calls. Thanks to the great database abstraction, every method is only few lines long.

Since CakeAMFPhp is a beta, I've found some problems, for which I've found a quick fix that I'll explain shortly.

To start a CakeAMFPhp project, simply download the required libraries as explained in this tutorial. (If you are new to Cake you should also see this)
Then in your controller, for example GalleriesController, put this:

PHP:
  1. vendor("cakeamfphp/amf-core/util/MethodTable");
  2. vendor('cakeamfphp/amf-core/adapters/lib/Arrayft');
  3. vendor('cakeamfphp/amf-core/adapters/lib/Arrayf');
  4.  
  5. class GalleriesController extends AppController
  6. {
  7. ...
  8. function GalleriesController()
  9. {
  10.   //AMF:
  11.   $this->methodTable = MethodTable::create(__FILE__);
  12.   parent::__construct();
  13. }

In the line 11 we call the automatic method table constructor of AMFPhp: it read the javadoc-like comments in the file and do all the magic:

PHP:
  1. /**
  2. * @desc Return the list of public galleries
  3. * @access remote
  4. * @pagesize 25
  5. */
  6. function getGalleries($offset = 0, $limit = 25) {
  7.   $data = $this->Gallery->findAll(null, null, 'created DESC', $limit, $offset, 0);
  8.   return $this->_arrayft($data, 'Gallery');
  9. }
  10.  
  11. function getGalleries_count() {
  12.   return $this->Gallery->findCount();
  13. }

The @access remote tag specify that the getGalleries method is callable from flash:

Actionscript:
  1. var ser = new Service("http://localhost/cake_gateway.php", null, 'GalleriesController', null , null);
  2. var pc:PendingCall = ser.getGalleries();
  3. pc.responder = new RelayResponder(this, "handleResult", "handleError");

The getGalleries_count() method is needed for the pageable recordset:

Actionscript:
  1. function handleResult (re:ResultEvent) {
  2.   var rs = RecordSet(re.result);
  3.   rs.addEventListener('modelChanged', this); //Listen for updateItems
  4.   this.totalItems = rs.getLength();
  5.  
  6.   for(var i:Number=startFrom; i <l; i++) {
  7.     item = rs.getItemAt(i);
  8.     if(i <rs.getNumberAvailable()) { //It is already fetched
  9.     ... //Process a row
  10.   }
  11. }

getGalleries return only the first 25 row from the database. When the data is loaded in flash, rs.getLength() method return the value from getGalleries_count(). getItemAt return an item or a future, we check if the item is loaded in the line 8. If the item isn't from the ones loaded, it is automatically requested from the server when we call getItemAt. When it arrives, the modelChanged method is called:

Actionscript:
  1. function modelChanged (info:Object) {
  2.   var rs = this.rs;
  3.   var item:Object;
  4.   if(info.eventName == "updateItems") {
  5.     for(var i:Number=info.firstItem; i <= info.lastItem; i++) {
  6.       item = rs.getItemAt(i);
  7.       target.addItem(item);
  8.     }
  9.   }
  10. }

in the info object there are the rows received, you can configure Flash to get only the item required or the page containing it.
That's all, simply add the @access remote tag, a *methodname*_count method and it works.
You can find more information on pageable recordsets here.

The _arrayft is required for the issue I've talk about before: CakeAMFPhp 0.6 does not support sending recorset to flash, so this method is required to transform the data returned by the Cake database abstraction layer into a AMF-compatible recordset:

PHP:
  1. function _arrayft($data, $table) {
  2.   $r = array();
  3.   if(!empty($data[$table])) {
  4.     //Multi table query:
  5.     if(is_array($data[$table][0])) {
  6.       //Nested table:
  7.       foreach($data[$table] as $t) {
  8.         $r[] = $t;
  9.       }
  10.     }
  11.     else {
  12.     //Sigle record:
  13.       foreach($data[$table] as $t) {
  14.         $r[] = $t;
  15.       }
  16.     }
  17.   }
  18.   else {
  19.     foreach($data as $t) {
  20.       array_push($r, $t[$table]);
  21.     }
  22.   }
  23.   return new Arrayf($r, array_keys($r[0])  );
  24. }

For performance reason, it is suggested to use a 0 'recursive' value in the find(..) call.

Some useful tips on Flash, caching and Php

Wednesday, September 13th, 2006

Making the test for some strange flash cache behaviour I've learned some useful tips:

You can be sure a movie will be loaded appending a query string to the file name:

Actionscript:
  1. loadMovie("clip.swf?123", target);

 

You can force the user to reload a movie when you release a new version:

Actionscript:
  1. clipname= "cache"+VERSION+"-clip.swf";
  2. loadMovie(clipname, target); //Loaded
  3. //After the first load the movie is cached:
  4. loadMovie(clipname, target2); //Cached

And use a .htaccess file (ModRewrite required):

Apache:
  1. RewriteEngine on
  2. RewriteCond %{REQUEST_URI} cache([0-9]+)-(.*)\.swf(.*)$
  3. RewriteRule ^cache([0-9]+)-(.*)\.swf(.*)$ $2\.swf [L]

Only when you change the VERSION variable (pass it with flashVars) the movie will be reload, otherwise it will be loaded from the browser cache.
 

You can make cacheable a file downloaded through php:

PHP:
  1. header('Last-Modified: '.date( "D, j M Y G:i:s \G\M\T", filemtime($filename) ));

Be sure to set the correct content type:

PHP:
  1. $filename= "clip.swf";
  2. $fp = @fopen($filename,"r");
  3. if($fp){
  4.     header('Last-Modified: '.date( "D, j M Y G:i:s \G\M\T", filemtime($filename) ));
  5.     header('Content-Length: '.filesize($filename));
  6.     header('Connection: close');
  7.     header('Content-Type: application/x-shockwave-flash');
  8.  
  9.     while (!feof($fp)){
  10.         echo(fgets($fp, 4096));
  11.         ob_flush();
  12.     }
  13.     fclose($fp);
  14. }
  15. else
  16. {
  17.     header("HTTP/1.0 404 Not Found");
  18. }

Without Last-Modified or ETag headers flash doesn't cache the files.