webdevRefinery Forum: Hydrogen Overview - webdevRefinery Forum

Jump to content

Hydrogen what, now?

Hydrogen is an open-source PHP toolkit that focuses on speed, with the goal of making your webapps faster if they're built on Hydrogen then they would be if written from scratch. It was created to be portable: applications written on Hydrogen should be able to be installed on many different types of servers with different database types or cache engines without changing any code or doing any extra programming work.

This forum contains the official tutorials for Hydrogen, until Hydrogen reaches version 1.0 and they move to HydrogenPHP.com. Please feel free to comment on them if you need help understanding them, or contribute your own tutorials that might later be published on the official site!
  • 45 Pages +
  • 1
  • 2
  • 3
  • Last »
  • You cannot start a new topic
  • You cannot reply to this topic

User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 25 March 2010 - 06:35 AM (#1)

Hydrogen Overview


It's done! I think!
Hydrogen Overview
Written by Kyek
For Hydrogen Version 0.3.0


DISCLAIMER
Hydrogen is currently in Alpha, meaning that it's in active development and the API may change from version to version in ways that could break existing code. There will be no deprecation until the first major release. So if you use Hydrogen, remember to check the changelogs when you update. They will tell you what needs to be changed in your code. This guide will also be updated to stay current with the latest codebase.

This overview is not for PHP beginners!
I'm planning to write a walkthrough-style tutorial for Hydrogen, but this is not it. Make sure you know these things before attempting to read this guide:
  • What classes and objects are and how to use them
  • What -> and :: do, and the difference between them
  • Basic programming patterns like Singleton and Factory


Contents
  • What is Hydrogen?
    • Hydrogen now
    • Hydrogen in the future

  • Getting Set Up
    • Downloading Hydrogen
    • Structuring Your App
    • Including Hydrogen
      • The Config File
      • The Autoconfig File
      • The Include File

  • The View components
    • View
    • The Hydrogen Templating Engine
    • The PurePHP Engine

  • The Controller components
    • Config
    • Controller
    • Dispatcher
    • Log
    • ErrorHandler

  • The Model components
    • Database: Hydrogen's portable database system
      • Queries through the Engine
      • Queries through the Query builder

    • SQLBeans: Writing your queries for you
    • Caching your data
      • Using the cache engine
      • Using RECache

    • Model: Tying it all together into one badass package


1. What is Hydrogen?
1a. Hydrogen Now
Hydrogen is a toolkit for PHP 5.3 and up that makes your webapps run faster, and run on more platforms. To fully grasp what it is, you should be familiar with MVC-style architecture. Big words scaring the crap out of you? It's actually a very simple thing :) Glance through my Introduction to MVC and you'll understand it all.

Hydrogen has the ability to take over your Model, View, and Controller layers -- but gives you the option to not use any of these parts, and instead use other libraries or your own solutions. Pieces of the framework are loaded as they're needed, so no unnecessary RAM or CPU time is eaten by using alternative libraries in conjunction with Hydrogen.

Hydrogen's model libraries work like magic, often automatically caching the results of certain functions and views, while simultaneously providing a last-line-of-defense against webserver overload. It has a powerful and flexible query builder, multiple caching engines, support for multiple database engines, a query translation system (allowing switching between multiple database backends without rewriting queries), a much-more-efficient-than-an-ORM structure that ... well, works kinda like an ORM, and more.

Its View library is extremely robust and allows you to use the powerful Hydrogen Templating Language which offers a Django-like syntax and features like template inheritance -- but if you prefer to use plain PHP and not bother yourself with learning another language, it supports that as well.

The Hydrogen Controller library implements more of that juicy juicy magic caching, along with a Dispatcher class that allows you to trigger your controllers based on any rules you set up. You're welcome to use any type of URL structure you want without limitation or constraint. Tack on a slew of helpful packages like a highly-integrated config system, logging system, error handler, and more, and Hydrogen -- even in its alpha stages -- is a pretty full package.

1b. Hydrogen in the Future
By Hydrogen's first full release, my goal is to have adapters for all the major SQL servers -- MySQL, PostgreSQL, and MSSQL at minimum, but I'd love Oracle and DB2 as well. I'm also planning on including support for more cache and semaphore engines. SQLBeans is in for a huge overhaul, making it easier and more intuitive to use without the performance sacrifices that come with traditional ORMs. And finally, Hydrogen is in dire need of simpler documentation split into multiple pages, rather than just this thread ;-)

If you have any suggestions for core components to add to Hydrogen, please speak up :)


2. Getting Set Up
2a. Downloading Hydrogen
Hydrogen can be downloaded from HydrogenPHP.com or the downloads section on Hydrogen's GitHub Page. In the future, there will hopefully be much more documentation on the main site ;-). For now, grab the zip, and extract it to a folder called 'hydrogen'.

2b. Structuring Your App
Hydrogen doesn't require you to structure your webapp's files and folders in any certain way. There are just three things that you'll want to have somewhere:
  • A place for a config file. I recommend a 'config' folder from the root so it's easily visible.
  • A 'cache' folder with permissions set to 777 so Hydrogen can write to it.
  • A place to store Hydrogen itself. I recommend lib/hydrogen.


2c. Including Hydrogen
2c1. The Config File
Hydrogen supports three different formats for the config file: A traditional .ini file with standard ini formatting, a .php file with standard ini formatting, and a .php file that interacts with the Config library directly to set your preferences. Samples of these files can be found in hydrogen/config/sample.

If you have a 'cache' folder as mentioned above, I recommend using config_sample.ini.php. It requires no special configuration to protect from public viewing, and the structure is very simple and forgiving. If you don't have a cache folder, I recommend config_sample.php as it will prevent your webapp from having to parse INI-format for every request. Only advanced users should use config_sample.ini, as it is a plaintext file that will be visible by the public without custom webserver configuration to protect it.

Whichever you choose, make a copy of it and put it wherever you're storing your configuration. Name it whatever you like, and open it for editing. Use the following guide to choose the appropriate options in this file:
  • [cache]
    • engine: This is the caching engine to use. Currently, the only supported cache engines are Memcache and Memcached, powered by the PHP extensions of the same names. If you don't have either of these extensions, leave this blank or set it to "No". This is case-sensitive.
    • pool_name: Memcached engine ONLY. Leave blank to make a new connection to the cache with every request, or set to any name to pool the connection so all requests share the same one. Note that if you are using a version of libmemcached > 0.38, some functions, like those used in pooling, do not function correctly with the Memcached PHP extension. To avoid crashes in this case, pool_name should be left blank.

  • [database]
    • engine: The database engine to use. Currently, the only supported database engine is MysqlPDO. If you have MySQL access in PHP, you will be able to use this engine. If you have another type of SQL server, Hydrogen currently does not include support for you. But please contact me if you're interested in writing an adapter for your server!
    • host: The hostname for your SQL server. This is usually localhost unless you have shared hosting. In which case, use whatever your host tells you to use.
    • port: The port your SQL server is listening on. If you weren't told otherwise, it's 3306.
    • socket: OPTIONAL. If you know you're running MySQL from a socket, you can type the path to the socket here. Note that specifying anything for this field will cause Hydrogen to use the socket instead of the host and port.
    • database: The name of the database to use.
    • username: The username to access the database with.
    • password: The password for the given user.
    • table_prefix: OPTIONAL. Specifying a prefix like "myapp_" will cause Hydrogen to add that prefix to each table name when using the Query library to build your SQL queries. This is useful to support users on restrictive hosting packages who may only be able to create one SQL database.
    • Using multiple databases? Check out hydrogen/config/sample/config_multidb_sample.ini.php for an example of using sub-keys in the config file to name different database configurations.

  • [recache]
    • unique_name: Set this to any short, unique string of characters, such as an abbreviation of your app's name. This prevents Hydrogen from overwriting cache entries of other Hydrogen-powered webapps using the same cache system.

  • [semaphore]
    • engine: The type of engine to use for semaphore handling. If you're using Memcache for the cache engine above, set this to Cache. Otherwise, leave this blank or set it to No. This is case-sensitive.

  • [errorhandler]
    • log_errors: Set this to 1 to automatically write errors into the logging system, or 0 to allow errors to go unnoticed. It's a good idea to set this to 1, and let your log settings determine whether errors get logged or not.

  • [log]
    • engine: The logging engine to use. Currently, TextFile is the only supported engine. Leave this blank or set it to No to disable the log system from loading completely.
    • logdir: (For the TextFile engine only!) The path to the folder where logfiles should be stored. Your cache folder is recommended since its permissions are already set to 777, but this can be any folder with 777 permissions. This path can be absolute, or relative from the install directory of the webapp. Note that the engine will throw an exception if the path does not exist/isn't writable, or if you've supplied a relative path for the logdir AND the config file in hydrogen.autoconfig.php. As of v0.1.2, it no longer returns false on error. And as of v0.2.0, relative paths are no longer relative from the config file.
    • fileprefix: (For the TextFile engine only!) What to name the generated logfiles. Specifying log_ will produce files named log_currentdate.log.
    • loglevel: The level of severity to log. Hydrogen's log system closely models the widely-used log4j standard, allowing you to log anything from bits of information to debug messages to serious errors, and this value will determine what actually gets written to the logfile. 0 = No logging, 1 = Log Errors, 2 = Log Warnings & worse, 3 = Log Notices & worse, 4 = Log Info & worse, 5 = Log Debug messages & worse.

Save and move on!

2c2. The Autoconfig File
In your hydrogen folder, copy or rename hydrogen.autoconfig.sample.php to hydrogen.autoconfig.php. This file is responsible for loading your configuration file as soon as Hydrogen is included in your page, and telling the Config module where and how to cache your config file. Following that, there's a listing of configuration options for the programmer (that's you!) to set, because they deal with the function of your app itself and aren't really something that should be exposed to the end-user in the main config file.

As of Hydrogen version 0.2.0, this file is extremely well documented. So read the text in there, tweak the variables as it describes, and you're all set!


2c3. Including Hydrogen
The hard work is done! All you need to do now is require_once() hydrogen at the top of any PHP file that can be loaded directly by the public. If you're using proper MVC structure, there will probably only be one PHP file that you need to do this for :) The file to include is hydrogen.inc.php, so the top of your file should look like this, for example:
<?php

require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");

// Your code here

?>


Now, in any of your php code after that require_once() line or in any other PHP files you include() or require(), all you have to do to use one of Hydrogen's libraries is write a 'use' line like this:
<?php

require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");

// Hydrogen's "Config" library is located in hydrogen/config/Config.php,
// if you look at Hydrogen's file structure.  So to use the Config library
// in our code, just change the slashes to backslashes, and take off the .php
use hydrogen\config\Config;

// Now we can use the config library!
echo Config::getVal("database", "password");

// osht we just gave out our MySQL password!

?>


3. The View Components
3a. View
use hydrogen\view\View;

View is as easy as it gets. In the MVC structure, it's important that you keep all HTML and other output in their own files, and that's what View is for!

View allows you to declare variables that the presentation layer of your website should have access to display, as well as load the appropriate templates. Templates can be pure HTML and PHP -- the languages you already know -- but they can also be written in the very snazzy Hydrogen Templating Language which gives you the ability to do a whole range of cool stuff.

For now, let's look at how to send variables to views and how to specify which view to show. Sending a variable to a view layer is very easy: You simply pick something that a view should be allowed to show, and send it with the View::setVar() command. For example:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\view\View;

View::setVar('title', 'Coolest Site Ever');
View::setVar('welcomeTo', 'killer page!');
View::load('welcome');


Notice on the last line, we're calling View::load() to tell the View library to load the "welcome" template. Could it be easier? ...well, yes! Here's how to load a view and send all those variables in just one line of code:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\view\View;

View::load('welcome', array(
	'title' => 'Coolest Site Ever',
	'welcomeTo' => 'killer page!'
	));


And that's as complicated as it gets! Now, though, you need to know how to write an HTML page that can echo those variables out. For that, you have two choices: The 'hydrogen' template engine, or the 'purephp' template engine. You can set which one to use in your autoconfig, as well as what extension to use for those files. The extension .tpl.php is the default, but that can be whatever you'd like.

So let's explore the template options!


3b. The Hydrogen Templating Engine
The Hydrogen Templating Engine was added in Hydrogen version 0.3.0, and is a very exciting addition to the View library. It offers a range of convenient commands and shortcuts, and is so powerful that it has its own guide available here:

Hydrogen Templates for Front-End Developers

I won't spend much time covering it in this overview as it already has its own guide, but let me outline the slickest part of this engine: Template inheritance. It sounds like a scary technical feature, I know, but just imagine being able to put your entire site layout in one file just like this:

base.tpl.php
<!DOCTYPE html>
<html>
    <head>
        <title>{% block pageTitle %}{% endblock %} - My Site</title>
        {% block header.css %}
            <link rel="stylesheet" href="/css/main.css" />
        {% endblock %}
    </head>
    <body>
        <div id="header">
            Welcome to My Site!
            <div id="navigation">
                Some links and styling here...
            </div>
        <div>
        <div id="content">
            {% block content %}
            {% endblock %}
        </div>
        <div id="footer">
            Copyright 2011 Me
        </div>
    </body>
</html>


And now every other page can inject itself into that template wherever there are blocks:
welcome.tpl.php
{% extends base %}

{% block pageTitle %}Welcome{% endblock %}

{% block header.css %}
    {% parentblock %}
    <link rel="stylesheet" href="/css/welcome.css" />
{% endblock %}

{% block content %}
    Welcome to my site!
{% endblock %}


So if you want to change your entire site layout and organization... you do it with just one file ;-).

For all other documentation, I defer to the Hydrogen Templates for Front-End Developers guide :)


3b. The PurePHP Engine
Don't want the hassle of learning to use the Hydrogen Templating Engine? No problem: just write your views in straight-up PHP! Select the 'purephp' engine in your autoconfig, and away we go!

You'll recall from the View section above that we're calling our 'welcome' template like this:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\view\View;

View::setVar('title', 'Coolest Site Ever');
View::setVar('welcomeTo', 'killer page!');
View::load('welcome');


So all we need to do is go to the view folder (defined in your autoconfig) and create the file welcome.tpl.php (or whatever extension you defined in the autoconfig) and add something like this:
<html>
    <head>
        <title><?php echo $this->title; ?></title>
    </head>
    <body>
        Welcome to my <?php echo $this->welcomeTo; ?> :)
    </body>
</html>


What's all that? Well, we're pulling in a variable called "title" and a variable called "welcomeTo". We say "$this->" because this variable belongs to the 'context' in which we're executing the template.

$this-> also grants us access to a few handy functions! Inside of a view file, if you need to build a URL, these are super convenient:
<html>
    <head>
        <title><?php echo $this->title; ?></title>
        <link rel="stylesheet" type="text/css" href="<?php echo $this->viewURL('/css/style.css'); ?>" />
    </head>
    <body>
        Welcome to my <?php echo $this->welcomeTo; ?> :)
        Go back to the <a href="<?php echo $this->appURL(); ?>">start</a>.
    </body>
</html>


viewURL() produces a URL relative to the root URL of the view folder, while appURL() produces a similar URL, but relative to the root of the webapp itself. Leaving the function void of arguments returns the appropriate URL with no trailing slash. Giving an argument appends the argument to that URL, and ensures appropriate slashes when they're concatenated. Note that it's also acceptable to construct URLs like this:
<?php echo $this->viewURL(); ?>/css/style.css


You can also include views in other views. For example, if your code for the page header is all in one file, you could include that in other pages like this:
<html>
    <head>
        <title><?php echo $this->title; ?></title>
    </head>
    <body>
        <?php $this->loadView('includes/header'); ?>
        Welcome to my <?php echo $this->welcomeTo; ?> :)
    </body>
</html>


loadView('includes/header') would cause the header.php file in the "includes" folder (inside of the views folder) to be inserted into the page at that point. This is a great way to include common code in your pages without having multiple copies to maintain if you need to change it!

Note that Hydrogen's view sandbox also understands two different variable scopes. Any variables accessed with $this->varname, whether you're reading them or setting them, will be available to all views -- whether they're printed with View::load() or $this->loadView(). However, simple variables like $myVar are local to the current view. To illustrate, suppose you have a view called varview.tpl.sphp that contains just this:
<?php $this->letter = 'B'; ?>
<?php $number = 2; ?>


If you loaded the following view...
<?php $this->letter = 'A'; ?>
<?php $number = 1; ?>
Time to include varview!
<?php $this->loadView('varview'); ?>
"letter" is now <?php echo $this->letter; ?>
"number" is now <?php echo $number; ?>


...then you would get this output:
Time to include varview!
"letter" is now B
"number" is now 1


Because $this->letter uses the request-level scope, varview can change it. But $number is view-scope, meaning it's private to each specific view, so varview's change doesn't affect the variable in the main view.

And that's the View library! Nice and easy :)

4. The Controller Components
4a. Config
use hydrogen\config\Config;

Hydrogen's Config library is very straight-forward. In the Getting Set Up section, we've already looked at the config file and how it's formatted. While this has all the config options that Hydrogen requires, you can add more options or groups of options of your own! This allows your entire webapp to be configured from one file.

Config is a static library, so it can be used globally without instantiation. There are only three functions you'll want to use:

getVal($section, $key, [$subkey = false])
Returns the value for the specified config option, or NULL if the option doesn't exist.

section is the name of the [section] you want to look into.
key is the name of the config property inside of the section.
subkey if the key is a numbered array or associative array, you can specify its index here. Otherwise, don't specify this and you'll get the value you want.

getRequiredVal($section, $key, [$subkey = false])
Returns the value for the specified config option, or throws a ConfigKeyNotFoundException if the option doesn't exist. It has all the arguments of the above function :)


So if you add this section to your config file:
[general]
site_name = Coolest Blog Ever
site_url = http://www.coolestblogever.com


You can echo the site name with this line:
echo Config::getVal("general", "site_name");


To set a configuration value within the code, there's this function:
setVal($section, $key, $value)

And its arguments should be pretty obvious. So if you want to override whatever the user typed in for their database username (I have no idea why you'd want to do this), you could run this line of code:
Config::setVal("database", "username", "JimBob");


Note that this line doesn't change the file at all. It just changes the value of that key for the duration of the request.

And now you know how to use the config system!

4b. Controller
use hydrogen\controller\Controller;

Controller is one of the simplest libraries included in Hydrogen. If you're already familiar with Hydrogen's Model library, then you already know how to use it :).

Controller, like Model, is meant to be extended into a new class. You should have a new Controller for all the base pieces of functionality in your site. Here's a simple start:
<?php
// Assuming hydrogen is already included
namespace myapp\controllers;

use hydrogen\controller\Controller;

class BlogController extends Controller {

}

?>


Ta-da! We now have a controller class. The public functions you write inside of this class should each handle a page request. For our blog, we'll want one page that shows a summary of the five most recent blog posts. Since it's the controller's job to call the right models and pass them to the right view, we're going to write a function that does just that (keep in mind that I'm making up this model code and passing it to an imaginary view -- you'll need to do that work yourself):
<?php
// Assuming hydrogen is already included
namespace myapp\controllers;

use myapp\models\BlogModel;
use hydrogen\controller\Controller;

class BlogController extends Controller {

	public function summary($pageNumber=1) {
		$bm = BlogModel::getInstance();
		$postData = $bm->getSummaryPage($pageNumber);
		
		if ($postData)
			View::load('blog_summary_page', array("posts" => $postData));
		else
			View::load('no_posts');
	}
}

?>


Simple, right? And that's all that goes into a controller function. We'd need more functions in here, of course -- one for loading just a single blog post and all of its details and comments, one for posting a comment, maybe we'd want to work posting a blog entry into this controller too. But for now, let's move on.

Let's say that, in this entire page, there's no part that's different depending on the user who loads it. That it looks exactly the same for everyone. That means we can cache the entire output of this page instead of generating it over and over again every time the page is requested, right? Check out how simple this is:
<?php
// Assuming hydrogen is already included
namespace myapp\controllers;

use myapp\models\BlogModel;
use hydrogen\controller\Controller;

class BlogController extends Controller {

	public function summary__($pageNumber=1) {
		$bm = BlogModel::getInstance();
		$postData = $bm->getSummaryPage($pageNumber);
		
		if ($postData)
			View::load('blog_summary_page', array("posts" => $postData));
		else
			View::load('no_posts');
	}
}

?>


With just those two underscores after the function name, we now have access to two functions in this controller: summary(), and summaryCached(). summary() will run the function like usual, without the underscores in it. summaryCached() will check to see if this page has been saved to the cache, and if so, simply output what's in the cache. If not, Controller will run the function directly, and listen for any output to the browser for the duration of the function call. Any output it catches will be cached using the RECache algorithm (we'll get to that later!) for five minutes.

Want it to be a time other than five minutes? Just include the number of seconds after the two underscores:
public function summary__1800($pageNumber=1)


1800 seconds=30 minutes, so now it'll be cached for 30 minutes instead -- all by just calling summaryCached() instead of summary() :).

Controller supports all the Magic Caching features of Model, including cache groups. So keep your eyes open once you get to the RECache and Model sections of this guide, and remember that anything you learn there can be applied here.

And that's it! Doesn't get much simpler :)

4c. Dispatcher
use hydrogen\controller\Dispatcher;

This is one of the libraries that sets Hydrogen apart from many other frameworks. Whereas most PHP frameworks nail you down into using a very specific URL structure, Dispatcher allows you to set up your URLs however you like, calling the appropriate controllers regardless.

The format for Dispatcher is that you set up a list of rules for what types of requests to direct to which controllers, and then call the Dispatcher::dispatch() function to process the current request with those rules. There are two types of rules in Dispatcher:

Mapping Rules
Mapping rules allow the controller name and function name to be specified in the URL itself. The most popular mapping rule is the PathInfoAutoMapRule, which would allow us to construct a URL like this in order to trigger the function in the example we used in the above Controller:
http://www.mydomain.com/appfolder/index.php/blog/summary/2


In a mapping rule, you specify which element is the controller name (in the case of the PathInfoAutoMapRule, it's the first PATH_INFO element -- "blog" in this case), which is the function name ("summary"), and which elements should be sent to the controller function as arguments ("2"). Mapping rules will automatically capitalize the controller name (so "blog" becomes "Blog"), and you can specify a namespace to tack on before the controller name, and a suffix to tack on after it. If we set the namespace to "\myapp\controllers" and the suffix to "Controller", our rule will now automatically call \myapp\controllers\BlogController. Woo!

Matching Rules
Matching rules call a predefined controller and function when the pattern matches a part of the request. Like mapping rules, matching rules can be applied to the PATH_INFO, GET variables, or the URL as a whole, just for starters. Most matching rules also allow you to define parts of the match to be sent as arguments.

But enough talkity talk. Here's how you use it.
Your webapp should have only one point of entry for this to be truly effective. You can have more, of course, but most programmers will want to have one single index.php file from where all pages will be accessed. And you'll set that up like this:
<?php
require_once(__DIR__ . '/lib/hydrogen/hydrogen.inc.php');

use hydrogen\controller\Dispatcher;
use hydrogen\view\View;

// Set up the variables that you need for pretty much all views
View::setVar("title", "FishCake, Yum!");

// You only need to do this part if you're not autoloading your classes.
// I recommend autoloading your classes exactly like Hydrogen does, but if
// you're not, you need these commands to tell Hydrogen what PHP files to
// include() for what classes.  Relative paths are relative to the base URL
// set up in the autoconfig file.
Dispatcher::addControllerInclude(
    '\fishcake\controllers\HomeController',
    'lib/fishcake/controllers/HomeController.php');
Dispatcher::addControllerInclude(
    '\fishcake\controllers\CakeController',
    'lib/fishcake/controllers/CakeController.php');
Dispatcher::addControllerInclude(
    '\fishcake\controllers\ErrorController',
    'lib/fishcake/controllers/ErrorController.php');

// The HomeMatchRule matches when there is no PATH_INFO.  So basically, this
// will match when index.php is loaded directly.
Dispatcher::addHomeMatchRule('\fishcake\controllers\HomeController', "index");

// The PathInfoAutoMap rule will match any time a correctly-formatted URL
// matches an existing controller and function name.  It's very versatile, as it
// covers most pages.
Dispatcher::addPathInfoAutoMapRule('\fishcake\controllers', "Controller");

// This rule matches everything, no matter what.  Good to use for a 404 page :)
Dispatcher::addMatchAllRule('\fishcake\controllers\ErrorController', "error404");

// And run the app!
Dispatcher::dispatch();
?>


Simple as pie! There are far too many rules available to list out here, but there's one for pretty much everything you'd want :) For a listing of all rules and examples of what they do, simply open hydrogen/controller/Dispatcher.php. And enjoy URL freedom :)

4d. Log
use hydrogen\log\Log;

If you're familiar with log4j, then you already know how to use this. Basically, there are five levels of "severity" you can use when logging:

Error: Use this to log serious errors in your code that you'd want to know about even when running your site on a production server.
Warn: Use this to log near-errors, or errors that your code is fully capable of handling on its own. Stuff you'd probably want to know about, but nothing serious.
Notice: Use this when something important happens that you might want to watch for, but isn't necessarily an error.
Info: General info to let yourself know when certain things have happened in your code. Stuff that might help out during development, but definitely wouldn't want to see on a production server.
Debug: Use this level to dump variable contents or other debug-only things you use during development. All things you won't care about once your code is stable.

Log is a static library just like Config, so you don't instantiate it yourself. Just call the function named for the severity level you want to use:
Log::debug("The 'world' variable is set to: $world");
Log::info("The world object has been initialized.");
Log::notice("Coder object sent message 'Hello' to World object.");
Log::warn("World object did not respond; continuing.");
Log::error("HOLY SHIT THE WORLD EXPLODED.");


Each of those functions has two more optional arguments -- one for filename to log and one for line number to log. If you don't specify those, the logger will use the filename and line number that you're calling the log on. But if, for some reason, you want to use a different value, you do that like this:
Log::warn("Found something really fat.", "yomamma.php", 2);


As you'll remember from the config file, you can specify which levels of severity you want the Log to actually ... well, log. So there's no need to remove the Log:: lines from your code when you launch it in production -- just set your log level to "error" or "warn" and go from there.

Ta-da, now you can log :)

4e. ErrorHandler
use hydrogen\errorhandler\ErrorHandler

Hydrogen's error handler puts a stop to ugly PHP errors. If you've ever seen a PHP error message come up when you loaded one of your pages, or if one of your pages just stopped loading right in the middle because of an error and you were left with half a page, this is the solution to that problem.

ErrorHandler captures your site's output rather than letting it be sent to the user, and only when the page is 100% complete and successful does it pass that information along to the user's browser. That way, if an error is encountered, ErrorHandler can trash what it's collected so far and send a nice, pleasant error page back to the user instead.

On an error, you can send back what appears to be a stock webserver error page, a much prettier default error page, or any other page that you specify -- and that can be accompanied with any HTTP error code you like. You can tell the browser that it's a 403 or 404 error, a 500 error, or whatever suits your fancy. You can also have the site redirect on an error. So instead of showing an error page, you can send the user back to the front page of your site, or to a Rick Roll. Whatever you feel like.

ErrorHandler is stackable, too. So as soon as your code starts, you can attach a default "omg I have no idea what just happened" error page. But then later, when you're about to, say, contact the Apple iTunes server, you could attach a "Failure to connect to iTunes" error page, then clear that error when the hard part is over. If something fails after that, the "omg I have no idea what just happened" page will show.

Make sense? Here's your arsenal:
// To respond with a webserver-like "500 Internal Server Error"
ErrorHandler::attach();

// To respond with a 404 instead (all status codes can be found
// at the top of ErrorHandler.php)
ErrorHandler::attach(ErrorHandler::HTTP_NOT_FOUND);

// To respond with the default pretty error page and a
// "500 Internal Server Error" error code
ErrorHandler::attachErrorPage();

// To respond with your custom error page and a
// "403 Forbidden" error code
ErrorHandler::attachErrorPage(__DIR__ . "/errors/403.php", ErrorHandler::HTTP_FORBIDDEN);

// To respond with a simple string and a 500 error code
// (just like the above functions, you can add an error
// code argument to this too if you want)
ErrorHandler::attachErrorString("OSHT I messed up :(");

// To redirect to another site in case of error
ErrorHandler::attachRedirect("http://fuck.org");

// To detach the last added error handler.
$successful = ErrorHandler::detach();
// If this results in the only attached error handler being detached,
// ErrorHandler will pass control back to whatever error handler you
// may have had set up before this.

// To detach any and all error handlers currently attached and pass
// error handling control back to whatever error handler you may have had
// set up before this.
ErrorHandler::detachAll();

// To set the types of errors that ErrorHandler will catch
ErrorHandler::setHandledErrors(E_ALL & ~E_DEPRECATED & ~E_NOTICE & ~E_WARNING);
// Note that the above is the default setting for ErrorHandler.  You can change
// it using this method, before or after any handlers have been attached.


Overwhelmed? It's ok -- generally, you only ever need to call ErrorHandler::attachPage(); and let that be it. Here's an example of how you can "stack" error handlers, though, to get more specific error messages for certain parts of your code:
<?php

// Import Hydrogen and start using its error handler
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\errorhandler\ErrorHandler;
ErrorHandler::attachErrorPage();

// If this errors, we get our nice default page
do_stuff();

// Now we're going to do risky stuff!  If it fails, there's a nice custom message
ErrorHandler::attachErrorString("Failed during nuclear bomb launch.  Try again later.");
launch_nuclear_bombs(4);
ErrorHandler::detach();

// If this next part errors, we get the nice default error page
// again, not the nuclear bomb message
do_more_stuff();
ErrorHandler::detach();

// If this fails, we're boned.  Our last attached handler has been removed,
// so we'll get whatever PHP's configured to do in case of an error.  Unless
// we had a different error handler set up before all of this, of course.
do_even_more_stuff();

?>


New in v0.2.0!
You can now send error headers without triggering an error! Check how easy it is to send a 404 page for a page that actually exists:
<?php
// Assuming hydrogen is already included
hydrogen\errorhandler\ErrorHandler::sendHttpCodeHeader(ErrorHandler::HTTP_NOT_FOUND);
?>
<html>
<head>
    <title>Uh-oh!  Not found.</title>
</head>
<body>
    The page you're looking for isn't here.  Sucks for you.
</body>
</html>


And that's ErrorHandler!

5. The Model components
5a. Database: Hydrogen's massive portable database system
5a1. Queries through the Engine
use hydrogen\database\DatabaseEngineFactory;

Hydrogen's database system is modeled very (very) closely after PDO. Thus, if you understand how to query through PDO, you can use Hydrogen's database library already. The benefit to Hydrogen's library is that any database interface -- MySQLi, ODBC, mysql_, and anything else -- can be used in exactly the same way PDO is used, as long as there's an adapter for it.

Note that, for best results, you should NOT submit queries directly through the engine, but instead, using the Query Builder outlined below. Queries submitted through the engine are not translated to the format necessary for the currently connected SQL server, so they'll only work if you're targeting a specific type of server like MySQL. But enough warnings, let's get on with it!

Getting a database link:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\database\DatabaseEngineFactory;

$db = DatabaseEngineFactory::getEngine();
// $db is now a connected database engine object.


Simple, right? This makes a new database connection with the information in the config file. If there's more than one database config in there, you can pass in the subkey name as an argument to getEngine() -- see hydrogen/config/sample/config_multidb_sample.ini.php for an example of such a config file.

Alternatively, you can supply a server type, hostname, port, username, and yadda yadda yadda in the getCustomEngine() function. I won't go into detail here since it's so rare you need to do that, but see hydrogen/database/DatabaseEngineFactory.php for full documentation on this function.

Important: DatabaseEngineFactory, as the name suggests, uses the Factory pattern. If you're not familiar, this means that every time you call "getEngine()", you'll get the EXACT same object back. Not a copy of it, not a new one that acts the same way, the EXACT same object that's already in use. This prevents you from making multiple connections to the same server. The only way you'll get a different object back is if you supply a different hostname/database/subkey name/etc.

Once you get $db, you can interact with it much like a PDO instance -- again, even if the driver for the engine being used isn't a PDO driver. For full documentation of each function, open hydrogen/database/DatabaseEngine.php. This is one of the few files that's 100% documented :)

Example query using the $db object above:
$stmt = $db->prepare("SELECT username FROM users WHERE user_id = ?");
$stmt->execute($userId);
$result = $stmt->fetchObject();
$username = $result->username;

echo "Your name is $username";


5a2. Queries through the Query Builder
use hydrogen\database\Query;

The Query object is the recommended way to write full queries, if you're not using SQLBeans. It allows you to construct a query using the SQL you already know, without ever concatenating strings or using messy logic to determine when you need a space or an AND. In addition, the Query object will automatically format your SQL query to match whatever SQL server you're connected to.

Currently, the Query class only supports the SELECT, INSERT, UPDATE, and DELETE verbs. CREATE, ALTER, and others are planned for future releases.

Here are some samples of how to use the Query object:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\database\Query;

// Build the query
$query = new Query("SELECT");
$query->field("username");
$query->from("users");
$query->where("user_id = ?", $userId);

// From here, we can execute our query and get the results exactly as if
// $query were a DatabaseEngine or PDO instance.
$stmt = $query->prepare();
$stmt->execute();
$result = $stmt->fetchObject();
$username = $result->username;

// Let's delete something
$query = new Query("DELETE");
$query->from("users");
$query->where("username like ?", $username);
$query->limit(1);
$stmt = $query->prepare();
$stmt->execute();

// Time to make a new user.  Actually, hell, let's make three.
$query = new Query("INSERT");
$query->intoTable("users");
$query->intoField("username");
$query->intoField("email");
$query->intoField("joindate");
$query->values("(?, ?, NOW())", array($usernames[0], $emails[0]));
$query->values("(?, ?, NOW())", array($usernames[1], $emails[1]));
$query->values("(?, ?, NOW())", array($usernames[2], $emails[2]));
$stmt = $query->prepare();
$stmt->execute();

// And let's update an existing one
$query = new Query("UPDATE");
$query->table("users");
$query->set("email = ?", $newEmail);
$query->where("user_id = ?", $userId);
$query->limit(1);
$stmt = $query->prepare();
$stmt->execute();
?>


The fun part is, you don't have to build your queries "in order" anymore. For example, this works great:
$query = new Query("SELECT");
$query->limit(10);
$query->orderby("username");
$query->field("username");
$query->from("users");
$query->field("email");
$query->where("email like ?", "%gmail.com");
$stmt = $query->prepare();
$stmt->execute();


The thing that separates the Query class from other array-based builders is that everything that's possible with typing out a MySQL query by hand is possible by using the Query class. It imposes no artificial limitations.
/*
INSERT ... SELECT syntax
http://dev.mysql.com/doc/refman/5.0/en/insert-select.html

INSERT INTO table1 (field1,field3,field9)
SELECT field3,field1,field4
FROM table2;
*/
$query->new Query("INSERT");
$query->intoTable("table1");
$query->intoField("field1");
$query->intoField("field3");
$query->intoField("field9");
$sel->new Query("SELECT");
$sel->field("field3");
$sel->field("field1");
$sel->field("field4");
$sel->from("table2");
$query->select($sel);
$stmt = $query->prepare();
$stmt->execute();

/*
STRAWBERRY QUERY
http://dev.mysql.com/doc/refman/5.0/en/example-maximum-column-group-row.html

SELECT s1.article, s1.dealer, s1.price
FROM shop s1
LEFT JOIN shop s2 ON s1.article = s2.article AND s1.price < s2.price
WHERE s2.article IS NULL;
*/
$query = new Query("SELECT");
$query->from("shop", "s1");
$query->field("s1.article");
$query->field("s1.dealer");
$query->field("s1.price");
$query->join("shop", "s2", "LEFT");
$query->on("s1.article = s2.article");
$query->on("s1.price < s2.price", false, "AND");
$query->where("s2.article IS NULL");
$stmt = $query->prepare();
$stmt->execute();

/*
SELECT name, category, price
FROM products
WHERE (price > 5 AND price < 10)
    OR (name like 'diamonds' AND (price = 200 OR price > 500))
GROUP BY category
HAVING MAX(price) > 8
LIMIT 5, 10
*/
$query = new Query("SELECT");
$query->field("name");
$query->field("category");
$query->field("price");
$query->from("products");
$query->whereOpenGroup();
$query->where("price > ?", 5);
$query->where("price < ?", 10, "AND");
$query->whereCloseGroup();
$query->whereOpenGroup("OR");
$query->where("name LIKE ?", "diamonds");
$query->whereOpenGroup("AND");
$query->where("price = ?", 200);
$query->where("price > ?", 500, "OR");
// The following two lines are optional since there are no more WHERE
// expressions, but I'll include them here to eliminate confusion.
$query->whereCloseGroup();
$query->whereCloseGroup();
$query->groupby("category");
$query->having("MAX(price) > ?", 8);
$query->limit(10, 5);
$stmt = $query->prepare();
$stmt->execute();


Finally, Query also supports PDO-style :variable syntax.
$query = new Query("SELECT");
$query->field("username");
$query->from("users");
$query->where("group_name = :group");
$stmt = $query->prepare();
$stmt->execute(array("group" => "Administrators"));


Believe it or not, this class is capable of even more fanciness -- but for a complete listing of its functions, see hydrogen/database/Query.php. To see how queries are formatted or to write your own server-specific formatter, see hydrogen/database/QueryFormatter.php and hydrogen/database/formatters/StandardSQLFormatter.php.

5b. SQLBeans: Writing your queries for you
use hydrogen\sqlbeans\SQLBean;

SQLBeans is one of the most powerful parts of Hydrogen. It takes the concept of Javabeans (objects meant only for storing data in Java), and adds SQL CRUD (Create, Read, Update, Delete) functionality to make communicating with your database as easy and as hassle-free as possible. It's similar in concept to an ORM, but doesn't have the inherent inefficiencies of ORM queries.

First, an example of what SQLBeans is capable of. In this example, UserBean automatically communicates with our database's 'user' table:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
require_once(__DIR__ . "/lib/myapp/sqlbeans/UserBean.php");

use hydrogen\database\Query;

$query = new Query("SELECT");
$query->where("username = ?", $username);
$users = UserBean::select($query);

if ($users) {
    // $users is an array of users.  We're only concerned with the first (and probably only) one
    $user = $users[0];

    echo "Hello there, " . $user->username . "! You currently have " . $user->score . " points."
    echo "I'm going to give you 10 more!";

    $user->score = $user->score + 10;
    $user->update();

    echo "You now have 10 more points in our database!  Awesome!";

    $user->delete();

    echo "lol, we just deleted you from the database.  Too bad.";

    $fred = new UserBean();
    $fred->username = "Freddy";
    $fred->score = 0;
    $fred->insert();

    echo "But we just made a new dude named Freddy with a default score of " . $fred->score . "!";
    echo "Maybe he'll have better luck.";
}
else
    echo "Sorry, that user doesn't exist";

?>


So let's get into what makes the magic happen, here. Like Hydrogen's Model class, in order to use SQLBeans, you must create a new class of your own and extend Hydrogen's SQLBean library. So create a new php file (name it whatever you like) and do that. It should look something like this:
<?php
// Namespace is optional, but you should get into the habit of using it
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UserBean extends SQLBean {

}
?>


You don't need to include any logic code in that file, but we WILL need to define some variables so that SQLBeans knows all about the database table this matches up to. Here's our file with those variables added. See my comments to understand what each variable says:
<?php
// Namespace is optional, but you should get into the habit of using it
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UserBean extends SQLBean {
	/**
	 * The name of the table this bean refers to.  Do not
	 * include the prefix you're using, if any
	 */
	protected static $tableNoPrefix = 'users';

	/**
	 * A unique alias for this table.  This can be anything,
	 * as long as none of your other beans have the same alias.
	 */
	protected static $tableAlias = 'usr';

	/**
	 * The name of this table's primary key field
	 */
	protected static $primaryKey = 'id';

	/**
	 * Usually, your primary key will be set to auto-increment,
	 * or increase by 1 automatically.  Set this to true if that's
	 * the case for this table, or false if you have to specify your
	 * own ID every time you insert.
	 */
	protected static $primaryKeyIsAutoIncrement = true;

	/**
	 * An array of every field in the table.
	 */
	protected static $fields = array(
		'id',
		'username',
		'score'
		);
}
?>


Easy enough, right? Just look into your database and answer the questions. Now you're done! require_once() that file into your code, and you'll be able to use your bean just as I did in my example. One thing to note from my example is how the Query object is used with SQLBeans to narrow your results.
// This will print a list of every single user in the database
$users = UserBean::select();
foreach ($users as $user)
	echo $user->username . "<br />";

// This will get an array of the first 10 users in alphabetical order
$query = new Query("SELECT");
$query->orderby("username", "ASC");
$query->limit(10);
$users = UserBean::select($query);

// This will get all users whose names start with F
$query = new Query("SELECT");
$query->where("username like ?", "F%");
$users = UserBean::select($query);

SQLBeans will use the exact same query object you provide to form its own database query, so don't do anything crazy like adding tables or sending an "INSERT" query or anything like that. Just use where, group by, order by, limit, and all those extras to get exactly the results you want.

So while that's convenient and all, this isn't anything you couldn't easily do with the query object alone. The power of SQLBeans lies in bean mapping -- tying beans to other beans.

Let's say, for example, that we added a "usergroups" table to our database, with three simple columns. 'id' for our primary key, 'name' for the name of the group, and 'description' to say what it's for. Let's make a UsergroupBean object for that:
<?php
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UsergroupBean extends SQLBean {
	protected static $tableNoPrefix = 'usergroups'
	protected static $tableAlias = 'ugrp';
	protected static $primaryKey = 'id';
	protected static $primaryKeyIsAutoIncrement = true;
	protected static $fields = array(
		'id',
		'name',
		'description'
		);
}
?>

In our 'users' table, we'll have to add a field to show which group each user belongs to. So our UserBean becomes this:
<?php
// Namespace is optional, but you should get into the habit of using it
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UserBean extends SQLBean {
	protected static $tableNoPrefix = 'users';
	protected static $tableAlias = 'usr';
	protected static $primaryKey = 'id';
	protected static $primaryKeyIsAutoIncrement = true;
	protected static $fields = array(
		'id',
		'username',
		'score',
		'group_id'
		);
}
?>

Now, the mapping! Every group_id value in the user table will match exactly one id in the usergroups table. Here's how we describe that link within our UserBean:
<?php
// Namespace is optional, but you should get into the habit of using it
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UserBean extends SQLBean {
	protected static $tableNoPrefix = 'users';
	protected static $tableAlias = 'usr';
	protected static $primaryKey = 'id';
	protected static $primaryKeyIsAutoIncrement = true;
	protected static $fields = array(
		'id',
		'username',
		'score',
		'group_id'
		);

	// We add $beanMap to say that this bean is linked to other beans
	protected static $beanMap = array(
		// For each "link", we provide a name.  We'll name this one "group"
		'group' => array(
			// The type of JOIN to use.  LEFT, INNER, OUTER, RIGHT -- all valid here
			'joinType' => 'LEFT',

			// The full name of the bean to join with.  If you're not using
			// namespaces, this would just be "UsergroupBean"
			'joinBean' => 'myapp\sqlbeans\UsergroupBean',

			// The name of the field in this table to link with the primary key
			// of the joined table.  The usergroup table's primary key is 'id',
			// which matches up to the group_id field in this table, so we use
			// group_id here.
			'foreignKey' => 'group_id'
			)
		);
}
?>

And that's it! If you're confused, take your time reading through it -- as long as you're familiar with how JOINs work in SQL, this should be no sweat :)

Once that's done, here's how powerful SQLBeans becomes:
$query = new Query("SELECT");
$query->limit(2); 
$users = UserBean::select($query);

foreach ($users as $user) {
    $group = $user->getMapping("group");
    // $group is now a UsergroupBean object containing the group data
    // for this user.
    echo "Hello " . $user->username . "!<br />";
    echo "You're in group " . $group->name . "!<br />;
    echo "Some information about that group: " . $group->description . "<br />";
}

The select for the user automatically pulled the information for the group :). And it doesn't stop there -- UserBean could have had more than one mapping, the objects it's mapped to could have had THEIR own mappings, THOSE mappings could have mappings, and SQLBeans would have gotten all that information and organized it with just one SQL query.

But it's not just that we have access to the group info now. Let's say both of those users are in the same group -- meaning, their group_id fields are the same. SQLBeans sees that and shares a group object between them. So you can do this:
$group1 = $users[0]->getMapping("group");
$group2 = $users[1]->getMapping("group");

// group1 and group2 are both the exact same UsergroupBean object
// since our two users have the same group_id.  So we change one,
// and the other changes too.
$group1->description = "Members of this group like cheese.";

echo $group2->description;
// Outputs "Members of this group like cheese."

$group2->update();
// Saved that description change to the database

But what if we need to work with a user's group name a lot? It's pretty messy to do that getMapping() call in our code, so SQLBeans provides a way to include variables not in that bean, and override variables that are. Let's add two functions to the UserBean.php file:
<?php
// Namespace is optional, but you should get into the habit of using it
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UserBean extends SQLBean {
	protected static $tableNoPrefix = 'users';
	protected static $tableAlias = 'usr';
	protected static $primaryKey = 'id';
	protected static $primaryKeyIsAutoIncrement = true;
	protected static $fields = array(
		'id',
		'username',
		'score',
		'group_id'
		);
	protected static $beanMap = array(
		'group' => array(
			'joinType' => 'LEFT',
			'joinBean' => 'myapp\sqlbeans\UsergroupBean',
			'foreignKey' => 'group_id'
			)
		);

	protected function get_groupname() {
		$group = $this->getMapped('group');
		return $group !== false ? $group->name : false;
	}

	protected function set_groupname($val, $func=false) {
		$group = $this->getMapped('group');
		return $group !== false ? $group->set('name', $val, $func) : false;
	}
}
?>

Now you can do this in your code:
echo "User " . $users[0]->username . " is in group " . $users[0]->groupname;
$users[0]->groupname = "Banned";
$users[0]->update();
echo "But now his group name has changed in the database!";

Note that you don't even call the function we just wrote above -- simply accessing the "groupname" variable will cause SQLBeans to check to see if there's a get_groupname() method it should be running. And when we set the group to Banned, before SQLBeans looks to see if it actually has a variable named groupname (which the UserBean doesn't), it looks to see if it has a function called set_groupname (which it does :)).

You can even write get_ and set_ functions to override variables the bean already has. This is most useful with dates in SQL, because you can convert dates to one format when they're "gotten", and convert them back into the format your SQL table needs when they're set.

SQLBeans has a hundred more features than what I've covered here -- like joining beans with a custom "ON" clause instead of using a foreign key, telling them not to do any mapping when you call select(), giving them a custom mapping to follow during select(), using a different database engine than what's set up in the config file, swapping out entire mapped beans (to change which group a user is in, for example), error handling, setting variables equal to SQL functions, serializing and unserializing them without losing their database connection, and on and on and on. If you're interested in one of these things, see hydrogen/sqlbeans/SQLBean.php or ask here :)

The source code for AppDB is also a great resource, as it uses (literally) every feature of SQLBeans at the time of this writing. See lib/appdb/sqlbeans for the beans themselves, and lib/appdb/model for the files that use the beans.

5c. Caching your data
5c1. Using the cache engine
use hydrogen\cache\CacheEngineFactory;

Hydrogen's cache system is similar in structure to its database system. You get an engine from the factory, and no matter how many times in your code you get that engine, it's always the exact same object. You communicate with all cache engines in the exact same way, no matter which the config file is set to. You can get an engine and execute cache commands even when people aren't using a cache, and your code will still function as expected.

Also like the database system, I do not recommend using it directly. You can, and this guide will show you how, but it's a much better idea to use RECache as described in the next section.

The cache system is meant to store bits of information in RAM -- most often SQL query results, as pulling those from a cache is much faster than having an SQL server process the query. When you store something, you assign it a "key", or an identify string, which you can use to get the information back later. You also specify an expiration time, so you can say your cached data is allowed to stay cached for 5 minutes. After that time, the cache engine should delete it so that you're forced to cache updated data.

The CacheEngine system is modeled closely after PHP's Memcache library. Here's a very simple walkthrough of how to use it:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\cache\CacheEngineFactory;

// First we get the cache engine.  The actual engine returned depends on
// what's in the config file, but we don't need to know what engine is
// being used to use it.
$cache = CacheEngineFactory::getEngine();

// Try to get our page 1 blog posts from the cache.
$posts = $cache->get("blog_page_1");

// Was it in there?
if (!$posts) {
    // Our posts weren't in there.  Let's get them from MySQL.
    $posts = a_function_to_get_our_posts_from_mysql();
    // Now that we have the data, cache it for next time
    $cache->set("blog_page_1", $posts, 300);  // 300 seconds = 5 minute cache
}

// Now we use $posts just like we would normally
display_blog_page($posts);

?>


99% of the time, get and set are all you'll need to use if you're communicating directly with the cache engine. There are many more methods available, however. hydrogen/cache/CacheEngine.php has a listing of all methods, and full documentation for each :).

Using the CacheEngine directly leaves a lot to be desired, though. Keep reading to see RECache's features, and you'll understand why!

5c2. Using RECache
use hydrogen\recache\RECacheManager;

Welcome to RECache, the very first Hydrogen library, and the entire reason I wrote Hydrogen in the first place :). RECache stands for Ridiculously Efficient Cache, and the name isn't an empty promise :).

Features
  • Enforces key expiration times (sometimes cache expiration is too glitchy to count on)
  • Allows key "grouping", so huge groups of keys can all be manually expired at the same time.
  • Allows keys to belong to multiple groups
  • Uses a unique semaphore system to ensure that only one visitor can update the cache at a time, without slowing down any other users or forcing them to wait.
  • Detects when your server is getting more requests than it can handle, and cancels certain requests to keep the server from crashing.
  • Serves an entire page from the cache if it exists, or caches an entire page if it doesn't, all with one function call.
  • Allows you to execute code if the frequency of a certain action rises above a certain level
  • Allows you to cache a big object if and only if it's requested at a high frequency


The first problem with traditional caching
Let's look at the code example from above:
$posts = $cache->get("blog_page_1");
if (!$posts) {
    $posts = a_function_to_get_our_posts_from_mysql();
    $cache->set("blog_page_1", $posts, 300);
}
display_blog_page($posts);

That works for low-traffic sites, but imagine what would happen if 20 people came to the site all at the same time. MySQL queries are fast, but not instant -- sometimes taking a second or more. 20 people will come in, and the cache won't have the blog page stored for any of them. So all at the same time, 20 people will execute the exact same MySQL query, causing the query to take longer than usual. In that time, more people will visit the site, not get the cached version of the page, and start the mysql query. If you have a high-traffic site, this can topple your entire server. If you have a moderate-traffic site, it still has the potential to noticibly slow the site down for your visitors.

The RECache algorithm solves that by making sure only ONE person can run the MySQL query and update the cache. All other visitors are served data that's been expired for less than a few seconds while that happens. No slowdown, and eliminates practically all load from the MySQL server.

The second problem with traditional caching
You have a blog. Your blog has 20 pages. Each one is set to go into the cache for 10 minutes once it's requested. You make a new blog post, and now the last post on each page gets moved back a page. Only not, because the pages have all been cached at different times. For 10 minutes after every blog post, you have posts duplicated between page 1 and 2, page 2 and 3, page 3 and 4, etc. You know how many real blogs have that problem? None.

With RECache, that's not a problem. Put all those cached pages in the "blogpages" group. When you make a new post, tell RECache to expire all keys in that group. Whether the group has 10 keys or 10 thousand keys in it, the expiration takes place instantly.

Get started with Caching and Grouping
Let's start easy, getting the blog page data.
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\recache\RECacheManager;

// Get the RECache object
$recache = RECacheManager::getInstance();

// What blog page was requested?
$reqPage = $_GET["page"] ?: 1;

// See if RECache has it
$pageData = $recache->get("page_$reqPage");
if (!$pageData) {
    // RECache doesn't have it?  Get it and cache it.
    $pageData = get_blog_page($reqPage);
    $recache->set(
        "page_$reqPage", // Name of the key to set
        $pageData,       // Value to store in this key
        600,             // Seconds until key expires
        "pages"          // Group name or array of group names for this key
        );
}

// Now the data is ready for you to use
display_blog_page($pageData);
?>


But now, using recache, we can force all cached pages to expire at the same time by saying:
$recache->clearGroup("pages");

And magically, all of those pages will be updated the next time they're requested :)

The same thing, using the RECache Algorithm
Even though we just did that with the RECache library, my "RECache Algorithm" -- the thing that makes sure your SQL server doesn't get overloaded -- wasn't used. Here's how to do the exact same thing we just did, using that algorithm. Hint: It's mad crazy easier.
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\recache\RECacheManager;

// Get the RECache object
$recache = RECacheManager::getInstance();

// What blog page was requested?
$reqPage = $_GET["page"] ?: 1;

// Get the data.
$pageData = $recache->recache_get(
    "page_$reqPage",  // The name of the key to get
    600,              // Expiration time if the data needs to be cached
    "pages",          // Group name or array of group names for this key
    "get_blog_page",  // Name of the function to call for updated data if necessary
    array($reqPage)   // Array of arguments to pass to that function
    );

// Now the data is ready for you to use
display_blog_page($pageData);
?>

One function call, and that's all it takes. No if-statements, no deciding when to update the data... RECache takes care of it all, and it makes sure that only one person gets to update the cache at a time. No server overloads. You can expire the "pages" group exactly how we did above, too.

Caching an entire page
Sometimes you'll want to cache a full page of output -- doing this for RSS feeds is really common. All it takes is one function call once you have the $recache object.
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\recache\RECacheManager;

// Get the RECache object
$recache = RECacheManager::getInstance();

// Print the page and quit if it's cached, or let
// the page load and THEN cache it if it's not.
$recache->pageCache(
    600,         // Number of seconds until expiration
    "RSS_page",  // Key name
    "feeds"      // Group or array of groups to put this key into
    );

// All the code to print the page normally goes here
?>

There you have it. One function call to handle either printing the entire page from the cache, or putting an entire page into the cache -- depending on whether it's already there or not. IMPORTANT: That's the non-recache-algorithm version of pageCache, which allows for frequency checking (we'll cover that soon). If you want to use the recache algorithm, just change $recache->pageCache to $recache->recache_pageCache :)

Checking Frequency
Even though we're not caching any important data with this, a memory cache is still the best way to track the frequency of a certain action across all of your visitors. In this example, we're going to track how often the page is loaded. If traffic gets really high, we're going to use a totally made-up function to E-mail the site admin.
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\recache\RECacheManager;

// Get the RECache object
$recache = RECacheManager::getInstance();

// Have we gotten more than 30 requests in the past 60 seconds?
// ("pageloads" in the line below is just the key name we're using to track this)
if ($recache->checkIfFrequent(30, 60, "pageloads"))
    email_admin("HOLY SHIT THE SITE IS EXPLODING!");

// Continue loading the page here :)
?>


Caching frequently requested data
Sometimes a user's request generates a ton of data that you could cache, but you're reluctant to cache all that data if no one's likely to use it. The most common example of this is search results. You want to cache real, common searches, but you don't want to cache people's typos -- things that will never be searched again. Caching search results only if the search is common is a good way to go.
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\recache\RECacheManager;

// Get the RECache object
$recache = RECacheManager::getInstance();

// What are we searching for?
$searchTerm = $_GET['search'];

// Get the page data
$pageData = $recache->get("search_$searchTerm");

if (!$pageData) {
    // The search isn't cached.  Get the results, 
    // and cache it only if it's common
    $pageData = run_search($searchTerm);
    $recache->setIfFrequent(
        3,                     // Number of requests that have to happen
        60,                    // ...within this many seconds.
        "search_$searchTerm",  // The key to save the data under
        $pageData,             // The data to be saved
        3600,                  // Number of seconds until expiration
        "pages"                // Group name or array of group names to use
        );
}

// Now use $pageData as you normally would
display_search_results($pageData);
?>


Other Stuff
This was the tip of the RECache iceberg. There are many other features not covered here -- like increment/decrement, stats tracking, getting the REAL hit/miss count, clearing individual keys, clearing all keys cache-wide, using the RECache Algorithm more directly with recache_get_wrapper, and much much more. For a listing of all RECache functions with some (very) basic documentation, see hydrogen/recache/RECacheManager.php.

5d. Model: Tying it all together into one badass package
use hydrogen\model\Model;

All these caching and database libraries are fun tools, but they're all very separate and a pain to use individually. The Model library is a ridiculously simple library to make using all of this stuff very very easy :). The idea is that you'll have a "Model" object for each of the main data parts of your webapp. For example, in a blog, you'd have one to handle users, one to handle blog posts, and one to handle blog comments -- just to start :). And their job would be to get and return data in exactly the format you need it. Let's start by building a UserModel that will get all of our User data for us.

Model, like SQLBean, is used by writing your own class that "extends" it. So let's do that now:
<?php
// Namespaces are optional, but recommended
namespace myapp\models;

use hydrogen\model\Model;

class UserModel extends Model {

}

?>

Simple enough :) Note that I didn't include the hydrogen.inc.php file there, because it should already be require_once()'ed by whatever PHP file that will be including our new UserModel.php file.

Our model class needs only one thing to do its job: a "protected static" variable called $modelID. $modelID should be a unique string that doesn't match any of your other models. Since this is our UserModel, I'll just call it "um".
<?php
// Namespaces are optional, but recommended
namespace myapp\models;

use hydrogen\model\Model;

class UserModel extends Model {
    protected static $modelID = "um";  // That was easy :)

}

?>


Now we're set, and we can put functions inside of this to get our data! So I'm going to include Query from Hydrogen and the UserBean that we wrote with SQLBeans earlier, and write up a small function to get all a user's information by his ID number:
<?php
// Namespaces are optional, but recommended
namespace myapp\models;

use hydrogen\model\Model;
use hydrogen\database\Query;

// I'm going to assume the PHP file for this bean was already included at some point
use myapp\sqlbeans\UserBean;

class UserModel extends Model {
    protected static $modelID = "um";  // That was easy :)

    /**
     * Takes a user ID integer and gives back a fully populated
     * UserBean object, with the attached UsergroupBean.
     *
     * @param userId number The ID of the user to get
     * @return mixed All the information about the requested user,
     *     or false if the user does not exist.
     */
    public function getUser($userId) {
        $query = new Query("SELECT");
        $query->where("id = ?", $userId);
        $userArray = UserBean::select($query);
        if (!$userArray)
            return false;
        return $userArray[0];
    }
}

?>

If you've been awake during the "Query" and "SQLBeans" sections, this should make sense :) In our function, we make a new SELECT query, narrow it down to one specific id, and hand it off to the UserBean, which gives us an array. If the array is empty, that means there was no user with that ID. Otherwise, our user is going to be the very first (and only) thing in that array, so we return it.

Right now you're probably asking yourself "So what good is this? We're not doing anything different." And if you are, that's very observant of you! Right now, in our main PHP code, we can do this:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
require_once("/path/to/UserModel.php");
use myapp\models\UserModel;

// Models are singletons, so instead of saying "new UserModel", we say this:
$userModel = UserModel::getInstance();

// Now we can call the function inside of it
$firstUser = $userModel->getUser(1);

?>

Still no big deal, right? We're doing some fancy object-oriented stuff, but still nothing that couldn't be achieved without this crazy model class. UNTIL we change our UserModel code from this:
    public function getUser($userId) {

to this:
    public function getUser__($userId) {

And now, instead of calling "getUser", we can call "getUserCached" -- and the output of that function will be cached using the RECache algorithm for five minutes. Check out the code that uses it:
// Get the Model
$userModel = UserModel::getInstance();

// Get the cached version of the user
$user = $userModel->getUserCached($userId);

// Get the user and bypass the cache
$user = $userModel->getUser($userId);

That's it. No extra code, no dealing with the cache yourself, you don't even add those underscores anywhere in the main code when we're calling getUser(). Add those underscores in the UserModel, and it will automatically handle a "Cached" version of the function. Period. Done.

What's that you say? You want to cache it for 600 seconds (10 minutes) instead of 300 seconds (5 minutes)? Ok, change this:
    public function getUser__($userId) {

to this:
    public function getUser__600($userId) {

And you're done. See, 300 seconds is the default -- so we didn't have to do anything special to use it. If you want anything else, you just type the number after the two underscores. Now, every time we call getUserCached() with a new userId, it will cache the result of the function and give it to straight from the cache every time you call the function in the future, all using the advanced RECache algorithm so it's crazy fast.

What about groups, you say? You want to be able to manually expire your cached user information by putting them all in a recache group? Just change this:
    public function getUser__600($userId) {

To this:
    public function getUser__600_users($userId) {

Now all the keys that this function caches will be put in the "users" group. Anywhere in your code, you can manually expire the "users" group using RECache (see the RECache guide above) and your webapp will start getting all new values from MySQL.

Do you need the result of that function to belong to more than one group? Just keep tacking group names on like this:
    public function getUser__600_users_admins_people($userId) {


It's really that easy :) You can get the benefits of RECache without ever touching that library at all. Heck, you can get the benefits of RECache by adding as little as two extra characters to functions that you want to be cached. It just couldn't be easier!

Just to be clear, though, no matter what expiration time or group names you put in that function name, you still call it without all of that in there. If your getUser function looks like what I just posted, this is still what you use:
$firstUser = $userModel->getUserCached(1);

Unless you don't want the cached version for some reason, in which case you do this:
$firstUser = $userModel->getUser(1);


So when you decide to change how things are cached, you only change it in one place :)


And that, my friends, is it.
Just remember that Hydrogen is in Alpha, and there WILL be bugs -- both in my Hydrogen code, and in my examples in this guide. I am EXTREMELY thankful to anyone who helps me find them. An extra special shoutout to magik who found some of the most important bugs right away!

This guide will be updated every time I make a new full release, but if you want to stay on top of development, visit Hydrogen on GitHub at: http://github.com/TomFrost/Hydrogen
10


User is offline Smarag 

  • Group: Members
  • Posts: 584
  • Joined: 08-March 10
  • Expertise:HTML,CSS,PHP

Posted 25 March 2010 - 07:52 AM (#2)

LMAO. I just thought "Finally. He made a tutorial." and then just this xD
0


User is online dida 

  • Group: Members
  • Posts: 1982
  • Joined: 10-March 10
  • Expertise:HTML,CSS,PHP,Java,Javascript,Python,Ruby on Rails,Node.js,SQL,Graphics,Flash,MongoDB,CouchDB,Cassandra

Posted 25 March 2010 - 08:35 AM (#3)

View PostSmarag, on 25 March 2010 - 07:52 AM, said:

LMAO. I just thought "Finally. He made a tutorial." and then just this xD



LOOL XDXDXD
0


User is offline Mack 

  • http://mackgoodstein.com/
  • Group: Members
  • Posts: 2072
  • Joined: 08-March 10
  • Expertise:HTML,CSS,PHP,Javascript

Posted 25 March 2010 - 04:02 PM (#4)

View PostSmarag, on 25 March 2010 - 07:52 AM, said:

LMAO. I just thought "Finally. He made a tutorial." and then just this xD


Well he has one on PDO too :P
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 27 March 2010 - 08:56 AM (#5)

rofl xD Well now you can at least start a Hydrogen project ;-)
0


User is offline Smarag 

  • Group: Members
  • Posts: 584
  • Joined: 08-March 10
  • Expertise:HTML,CSS,PHP

Posted 27 March 2010 - 10:13 AM (#6)

xP

Quote

What -> and :: do, and the difference between then


Typo!
Don't forget about the ninjas!

;)
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 27 March 2010 - 10:19 AM (#7)

View PostSmarag, on 27 March 2010 - 10:13 AM, said:

Typo!

Woops! Thanks! :D
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 27 March 2010 - 07:44 PM (#8)

Holy shit. So I just finished all of part 3, and I'm just NOW realizing that I misspelled 'detach' in the ErrorHandler library. In both function names that use that word.

This must be how the guy who wrote "http_referer" into the rfc spec for http felt :(
0


User is offline Rob 

  • Group: Members
  • Posts: 207
  • Joined: 08-March 10
  • Expertise:HTML,CSS,PHP,Javascript,SQL,Graphics

Posted 27 March 2010 - 09:05 PM (#9)

View PostKyek, on 27 March 2010 - 07:44 PM, said:

Holy shit. So I just finished all of part 3, and I'm just NOW realizing that I misspelled 'detach' in the ErrorHandler library. In both function names that use that word.

This must be how the guy who wrote "http_referer" into the rfc spec for http felt :(


Well the library is "alpha", so this is just one more instance that needs correction prior to a beta release. At least it is minor and affects very little code at this point. Do you plan to fix it the right way or the MicroSloth way, e.g. correcting the function name and adding a wrapper with the incorrect spelling marking it as deprecated for all eternity? Posted Image
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 27 March 2010 - 09:58 PM (#10)

View PostRob, on 27 March 2010 - 09:05 PM, said:

Well the library is "alpha", so this is just one more instance that needs correction prior to a beta release. At least it is minor and affects very little code at this point. Do you plan to fix it the right way or the MicroSloth way, e.g. correcting the function name and adding a wrapper with the incorrect spelling marking it as deprecated for all eternity? Posted Image

Hahaha-- no deprecation until 1.0. Until then, it's just changelogs. So once Alpha 2 is out, my blunder will be but a single line in a text file ;-). I'm just having severe identity issues. I may have to turn in my grammar nazi badge.
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 28 March 2010 - 10:15 PM (#11)

SO CLOSE TO BEING DONE I CAN TASTE IT. AND IT'S TASTY.

Has anyone even attempted to read this yet? xD
0


User is offline hydralisk 

  • Group: Administrators
  • Posts: 496
  • Joined: 02-March 10
  • Expertise:HTML,CSS,Javascript,Flash

Posted 29 March 2010 - 12:35 AM (#12)

View PostKyek, on 28 March 2010 - 10:15 PM, said:

SO CLOSE TO BEING DONE I CAN TASTE IT. AND IT'S TASTY.

Has anyone even attempted to read this yet? xD

I'd read it, but I think I have plans for the next 2.5 years. I'll let ya know after that :P
1


User is offline Starblaster100 

  • Group: Members
  • Posts: 211
  • Joined: 08-March 10
  • LocationEngland
  • Expertise:HTML,CSS,PHP,Javascript,SQL

Posted 29 March 2010 - 11:30 AM (#13)

Wow, incredible.
I've just read the whole thing. Really nicely written, and you really do justice to Hydrogen. I'll definitely be checking this out soon!
0


User is offline Mack 

  • http://mackgoodstein.com/
  • Group: Members
  • Posts: 2072
  • Joined: 08-March 10
  • Expertise:HTML,CSS,PHP,Javascript

Posted 29 March 2010 - 02:12 PM (#14)

View PostKyek, on 28 March 2010 - 10:15 PM, said:

SO CLOSE TO BEING DONE I CAN TASTE IT. AND IT'S TASTY.

Has anyone even attempted to read this yet? xD


I read it, but am having trouble getting it all working. I'll probably have my site use this soon, but right now there is no php calls from AJAX, which will change soon.
0


User is offline IcyTexx 

  • Group: Members
  • Posts: 351
  • Joined: 08-March 10
  • LocationCroatia

Posted 29 March 2010 - 03:01 PM (#15)

I've glanced it and my eyes bleed.
They'll pop out when I try to read it. :P

I can just say: frikkin awesome.
0


User is offline magik 

  • magikly delicious
  • Group: Members
  • Posts: 1095
  • Joined: 08-March 10
  • Expertise:HTML,PHP,Javascript,SQL

Posted 29 March 2010 - 03:09 PM (#16)

just started reading through this, only got through error handlers so far right now ( am also testing out the code as I read )


Quote

3b. Log

use hydrogen/log/Log;


those forward slashes should be backslashes in your use statement



Also, I couldn't get the error handler's to work ( or some of them at least ), so I opened up ErrorHandler.php and searched for the functions.... you called them by wrong name in your example code!

ErrorHandler::attachPage   ==> ErrorHandler::attachErrorPage
ErrorHandler::attachString ==> ErrorHandler::attachErrorString



that's as far as I've gotten so far, I'll try to read through the rest a bit later

edit:
also, not sure if this is how it is intended to be, but when ErrorHandler handles the error for the page, I don't get any errors logged to my default PHP error log...

Is there any way to have the errors goto the defualt PHP error log when the ErrorHandler handles an error? It'd be useful IMO to be able to figure out what caused the user to trigger the error page

Or is there something I am missing to enable this? I currently have the ini file set to:
log_error = 1
loglevel = 5
logdir = cache
engine = TextFile

I do not notice any log files getting created in the cache directory ( it did cache a config.quickload.php )

edit2:
I was triggering a E_NOTICE level error, and if I don't register the ErrorHandler to handle E_NOTICE, the error still goes to my default log, but as soon as I register E_NOTICE, no notice errors get to the default log.... maybe there is a way you can throw exceptions higher ( if they won't break execution ), maybe even just for E_NOTICE/E_WARNING/E_DEPRECATED ?

edit3:
hrm... this may be a log problem, I'm not noticing any Log::debug messages going anywhere.... I'm gonna re-read that section

edit4:
yes, it was an error w/ a config setting in my ini file: I had logdir = cache, thinking that the default setting would mean it would use the cache dir, but re-reading that section, it seems this config might be a bit buggy - I just changed it to the absolute path of where my cache is... I think I'm actually gonna change that to goto my /var/log dir

edit5:
I still think it may be a good idea to throw errors higher - if the ErrorHandler is not set/setup correctly, then the errors will goto the default php error log, but if it is registered - then no error info is put into any logs... I still think it would be nice to be able to know what caused a user error in the end w/o having to add more debug/log statements in the code... maybe I'm doing this wrong though? What are your thoughts Kyek? Do you rely on your own debug/error logging? Do you have ErrorHandlers in every php file? That doesn't make sense - only end user display php files would have this I think...

I just don't like having all these different locations/logs to search through for any given error. I prefer to have my log open with a tail -f while I debug so I can see the errors happening live....

edit6:
Last edit hahaha... (hopefully)... but one question - does the ErrorHandler use output buffering? Will it break anything if I use output buffering? What happens if I enable the ErrorHandler after output has already been sent to the user.
.... magik's most awesomely rainbowed signature =þ


the wdR forums, on crack rocks, said:

Your signature may contain:
  • Any number images
  • Images of any size
  • Any number of URLs
  • Any number of lines

dangerous...
0


User is offline Renegade 

  • 418 I'm a teapot
  • Group: Members
  • Posts: 748
  • Joined: 08-March 10
  • Expertise:HTML,CSS,PHP,Javascript,Node.js,SQL,Graphics

Posted 29 March 2010 - 03:20 PM (#17)

Nice! Ill give a thorough read through this later because I really want to incorporate this library!
http://adriancooney.ieGithubTwitterDribbbleForrst
We all die. The goal isn't to live forever. The goal is to create something that will.

Array(16).join({}-{}) + " Batman!";
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 29 March 2010 - 05:42 PM (#18)

View Postmagik, on 29 March 2010 - 03:09 PM, said:

those forward slashes should be backslashes in your use statement

Damn, good catch! Edit: Fixed :D


Quote

Also, I couldn't get the error handler's to work ( or some of them at least ), so I opened up ErrorHandler.php and searched for the functions.... you called them by wrong name in your example code!

ErrorHandler::attachPage   ==> ErrorHandler::attachErrorPage
ErrorHandler::attachString ==> ErrorHandler::attachErrorString

Oh fuck xD Thanks man! Edit: Fixed :D

Quote

edit:
also, not sure if this is how it is intended to be, but when ErrorHandler handles the error for the page, I don't get any errors logged to my default PHP error log...

Correct sir :) PHP only logs errors when they're unhandled. Since ErrorHandler is, as the name implies, handling the error (xD) it doesn't hit PHP's error log.

Quote

Is there any way to have the errors goto the defualt PHP error log when the ErrorHandler handles an error? It'd be useful IMO to be able to figure out what caused the user to trigger the error page

That's a good question.. I think you can call a function to output directly to the PHP error log, so I may make than an option. For now, though, Hydrogen's Log library can log errors.

Quote

Or is there something I am missing to enable this? I currently have the ini file set to:
log_error = 1
loglevel = 5
logdir = cache
engine = TextFile

I do not notice any log files getting created in the cache directory ( it did cache a config.quickload.php )

For logdir, give it an absolute path. It assumes a lot of shit it shouldn't be assuming if you give it a relative path like that :) Edit: Oh shit, I just realized you figured that out xD Nice!

Quote

edit5:
I still think it may be a good idea to throw errors higher - if the ErrorHandler is not set/setup correctly, then the errors will goto the default php error log, but if it is registered - then no error info is put into any logs... I still think it would be nice to be able to know what caused a user error in the end w/o having to add more debug/log statements in the code... maybe I'm doing this wrong though? What are your thoughts Kyek? Do you rely on your own debug/error logging? Do you have ErrorHandlers in every php file? That doesn't make sense - only end user display php files would have this I think...

I just don't like having all these different locations/logs to search through for any given error. I prefer to have my log open with a tail -f while I debug so I can see the errors happening live....

In all my PHP apps, there is only one PHP file that the public can load. Everything happens through index.php -- it's the dispatcher part of my controller module. So I attach a basic default error handler at the top of index.php after all my includes, and I'm done. If I want more specific errors later (or if it's an AJAX call and I want errors to return something like { "successful": "0" } on error instead of a page), then I use ErrorHandler's stacking ability.

ErrorHandler should be logging any errors you run into through the Log library, though, as long as you have that config option turned on. You shouldn't have to write your own Log::error methods and whatnot. Is that not working?

If you're attaching an error handler during development, though, I don't normally do that. For development, leave the error handler off and turn PHP's HTML errors on. Then you get to see exactly what's wrong as it happens. The only time I use ErrorHandler during development is when I'm working on AJAX calls, because it's much easier to watch a log than it is to try to dig down through with firebug every single freaking time to see if I can find an error message hidden somewhere ;-)

Quote

edit6:
Last edit hahaha... (hopefully)... but one question - does the ErrorHandler use output buffering? Will it break anything if I use output buffering? What happens if I enable the ErrorHandler after output has already been sent to the user.

It uses output buffering, but output buffering can be stacked-- so feel free to use it yourself :) You definitely definitely want to attach an error handler first thing, though, because if you output something and THEN turn the error handler on, the error handler can't cancel what you've already output. It can't set the error header, it can't show an error page because there's already data there. So attaching a handler should definitely be one of the first things you do in your code, definitely before any output.
0


User is offline magik 

  • magikly delicious
  • Group: Members
  • Posts: 1095
  • Joined: 08-March 10
  • Expertise:HTML,PHP,Javascript,SQL

Posted 29 March 2010 - 06:12 PM (#19)

View PostKyek, on 29 March 2010 - 05:42 PM, said:

ErrorHandler should be logging any errors you run into through the Log library, though, as long as you have that config option turned on. You shouldn't have to write your own Log::error methods and whatnot. Is that not working?

I guess it is actually working like that ;)

I think I may have thought it wasn't writing into there, but at the time I dont think I was handling E_NOTICE messages, so that output got thrown to the default php error log.

But I just tried it again and it seemed to be working as you have explained above. I get any errors that the ErrorHandler caught put into the hydrogen log file. I think when I had tested it, it was an E_NOTICE error and since I wasn't handling those, it went to the default php error log


But here's something else I ran into, after detaching the ErrorHandler, I don't get my normal program output, just a skeleton HTML output w/ no content. I am not doing much in terms of processing, just checking out these functions, so my code is very simple:
require_once('/XXX/hydrogen.inc.php');
use hydrogen\config\Config;
use hydrogen\log\Log;
use hydrogen\errorhandler\ErrorHandler;

ErrorHandler::setHandledErrors(E_ALL | E_STRICT); // & ~E_DEPRECATED & ~E_NOTICE & ~E_WARNING);
ErrorHandler::attachErrorString("OSHT I messed up :(");

$successful = ErrorHandler::detatch();
echo $successful;

Log::debug("The 'world' variable is set to: $world");
echo 'a';


Doing this, causes me to get the blank HTML skeleton output:
<html> 
	<head> 
		<title></title> 
	</head> 
	<body> 
		<h1></h1> 
	</body> 
</html>


Which I imagine is how the ErrorString page probably looks - but w/o any error string...

If I move the ErrorHandler::detatch() function to after the Log::debug line ( $world does not exist - should cause an E_NOTICE error ), then it correctly outputs the error page...

It's almost as if the ErrorHandler doesn't remove error handling on the detatch()

Or is there some sort of default error handler that gets attached?

calling detatchAll(), I get my normal output of just 'a'...

calling detatch() twice doesn't help - I still get the HTML skeleton

is there something I'm doing wrong?
.... magik's most awesomely rainbowed signature =þ


the wdR forums, on crack rocks, said:

Your signature may contain:
  • Any number images
  • Images of any size
  • Any number of URLs
  • Any number of lines

dangerous...
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 29 March 2010 - 06:21 PM (#20)

Innnnnteresting. Let me run your simulation on my machine and see what's up :)

Edit: Ok, the error handler definitely isn't detaching in that script... but I haven't gotten the skeleton output.

Edit 2: Oh balls, there's an error in the NoEngine semaphore engine, too. Sorry, people without Memcache! I'll post Alpha 2 as soon as I can.
0


Share this topic:


  • 45 Pages +
  • 1
  • 2
  • 3
  • Last »
  • You cannot start a new topic
  • You cannot reply to this topic

4 User(s) are reading this topic
0 members, 4 guests, 0 anonymous users


Enter your sign in name and password


Sign in options
  Or sign in with these services