So I have been playing with Haxe for a little while now. I’ve decided I’m ready to build a hobby project using this new language. It will be a PHP browser game with Flash elements to utilize AMF remoting.
Skip to Implementation
The first thing I did (after the unit testing framework failed to work with PHP) was to decide I didn’t want to hardcode my HTML in code. Having worked on many PHP projects in the past, I have become a fan of using Smarty to abstract my views.
So in order to learn how to include Smarty in my project, I begin by looking up examples for using external libraries. It shows how to use the extern keyword on classes to make references that the Haxe Compiler can’t interpret.
Using this, I was able to create a Smarty.hx file that implemented all the functionality I needed.
//empty package because they are not natively supported by the PHP target
package ;
//Using NativeArray, since that is what Smarty is expecting
import php.NativeArray;
extern class Smarty
{
public var template_dir:String;
public var compile_dir:String;
public var config_dir:String;
public var plugins_dir :NativeArray;
public var debugging:Bool;
public var error_reporting :Int;
public var compile_check:Bool;
public var force_compile :Bool;
public function assign(tpl_var:Dynamic, ?value:Dynamic):Void;
public function append(tpl_var:Dynamic, ?value:Dynamic, ?merge:Bool):Void;
public function clear_assign(tpl_var:String):Void;
public function clear_all_assign():Void;
public function register_block(block:String, block_impl:String, ?cacheable:Bool, ?cache_attrs:Dynamic):Void;
public function unregister_block(block:String):Void;
public function display(resource_name:String, ?cache_id:String, ?compile_id:String):Void;
//Returns Boolean or String containing parsed template
public function fetch(resource_name:String, ?cache_id:String, ?compile_id:String, ?display:Bool):Dynamic;
}
Using this class will trigger a runtime error of not being able to locate the class file. Makes sense since we haven’t installed it yet. So next was to figure out how to how to include this file.
The first option was just to require the Smarty file using “untyped __call__('require_once', smarty_location);” in the main file. Although this may be alright for this scenario, I wasn’t happy with it because I do not require this library when I will start with remoting.
EDIT: It’s also possible to include this code in a static __inline__ function in the external class, which eliminates this issue.
So I began digging into what the compiled PHP looked like. One of the first relevant bits I came across, in lib/php/Boot.class.php, was _hx_register_type that would allow the path to be declared for the class file. This could have worked, by implementing a mapping between external libraries and their types.
However, I started then noticing the structure of the generated PHP’s for the base package. They are simply in the form of (class name).class.php. So I simply copied the contents of Smarty’s libs directory into the generated lib directory of my project, and confirmed it worked.
This worked because:
- My
extern class was in the base package. (to avoid the compiler’s work around for packages in PHP)
- The Smarty class file was called Smarty.class.php, which is the format expected by Haxe’s auto_load function.
Now that I’ve confirmed this is working, I then went ahead to create it as part of my build.
Implementation
So first I created a Haxe/PHP project in FlashDevelop. I deleted the generated Bin folder and pointed the output to a subfolder oh my htdocs, changing the custom run command at the same time.
Next I created a lib directory in the root of my project directory and copied the Smarty libs there.
I then created all the default folders for Smarty under the root of my project.
- templates
- templates_c
- configs
- cache
I created a simple html page and saved it as index.tpl under the the templates folder.
I created my main class to test the template.
static function main()
{
var template = new Smarty();
template.display('index.tpl');
}
Next I have to make sure these files get copied to during the build. Since I’m using Vista, I decided to make use of RoboCopy for this step. I created a batch file called doCopy.bat and placed in the the project’s root directory, and set it as the the pre build command.
@echo off
REM USE: in prebuild use
REM $(ProjectDir)\doCopy.bat "$(ProjectDir)" "$(OutputDir)\$(OutputName)"
REM copy the full directory to the output directory, removing generated and deleted files
robocopy %1 %2 /MIR /XD *src /XF *.hxproj *.bat
REM robocopy return and exit code between 0 and 8 for success
REM FlashDevelop will only continue for exit code 0
EXIT /B 0
Testing, I ran into the follow error
uncaught exception: unlink(templates_c\%%3F^3F5^3F5AD92D%%MainTemplate.tpl.php) [function.unlink]: No such file or directory (errno: 2) in C:\dev\httpdocs\FirstSite\lib\internals\core.write_file.php at line #46unlink(templates_c\%%3F^3F5^3F5AD92D%%MainTemplate.tpl.php) [function.unlink]: No such file or directory
Called from net.tylermac.PHPIndex::runApp
Called from net.tylermac.PHPIndex::main
Line 44 (not 46) of lib/internals/core.write_file.php contains @unlink($params['filename']);. Of course, the @ in PHP is suppose to suppress errors, however, it doesn’t if there is a error handler set. Haxe sets one up for us, so this line throws an error if the filename does not exists. (It doesn’t on first run). So to correct it, I simply modified as follows
if (file_exists($params['filename']))
{
@unlink($params['filename']);
}
This causes a small performance hit rather then trying to silently ignore the error, but I find it acceptable.
Running the code again, and suddenly I have the html displaying for me.