Source for file phery.php
Documentation is available at phery.php
* PHP + jQuery + AJAX = phery
* Copyright (C) 2011 gahgneh
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* @link https://github.com/gahgneh/phery
* @license http://opensource.org/licenses/gpl-3.0.html GNU Public License
* The functions registered
private $functions =
array();
* The callbacks registered
private $callbacks =
array();
* The callback data to be passed to callbacks and responses
* Static instance for singleton
* @staticvar phery $instance
private static $instance =
null;
* Will call the functions defined in this variable even
* if it wasn't sent by AJAX, use it wisely. (good for SEO though)
* Hold the answers for answer_for function
private $answers =
array();
* Construct the new phery instance
$this->callbacks =
array(
'no_stripslashes' =>
false,
'unobstructive' =>
array(),
* Set callbacks for pre and post filters.
* Callbacks are useful for example, if you have 2 or more AJAX functions, and you need to perform
* the same data manipulation, like removing an 'id' from the $_POST['args'], or to check for potential
* CSRF or SQL injection attempts on all the functions, clean data or perform START TRANSACTION for database, etc
* @param array $callbacks
* // Set a function to be called BEFORE
* // processing the request, if it's an
* // AJAX to be processed request, can be
* // an array of callbacks
* 'pre' => array|function,
* // Set a function to be called AFTER
* // processing the request, if it's an AJAX
* // processed request, can be an array of
* 'post' => array|function
* The callback function should be
* // $additional_args is passed using the callback_data() function, in this case, a pre callback
* function pre_callback($ajax_data, $internal_data){
* $_POST['args']['id'] = $additional_args['id'];
* // post callback would be to save the data perhaps? Just to keep the code D.R.Y.
* function post_callback($ajax_data, $internal_data){
* $this->database->save();
* Returning false on the callback will make the process() phase to RETURN, but won't exit.
* You may manually exit on the post callback if desired
* Any data that should be modified will be inside $_POST['args'] (can be accessed freely on 'pre',
* will be passed to the AJAX function)
if (isset
($callbacks['pre']))
foreach ($callbacks['pre'] as $func)
$this->callbacks['pre'][] =
$func;
if ($this->config['exceptions'] ===
true) throw
new phery_exception("The provided pre callback function isn't callable");
$this->callbacks['pre'][] =
$callbacks['pre'];
if ($this->config['exceptions'] ===
true) throw
new phery_exception("The provided pre callback function isn't callable");
if (isset
($callbacks['post']))
if (is_array($callbacks['post']) &&
!is_callable($callbacks['post']))
foreach ($callbacks['post'] as $func)
$this->callbacks['post'][] =
$func;
if ($this->config['exceptions'] ===
true) throw
new phery_exception("The provided post callback function isn't callable");
$this->callbacks['post'][] =
$callbacks['post'];
if ($this->config['exceptions'] ===
true) throw
new phery_exception("The provided post callback function isn't callable");
* Set any data to pass to the callbacks
* @param mixed $args,... Parameters, can be anything
* Encode PHP code to put inside data-args, usually for updating the data there
* @param mixed $data Any data that can be converted using json_encode
* @return string Return json_encode'd and htmlentities'd string
static function args($data, $encoding =
'UTF-8')
* Check if the current call is an ajax call
return (bool)
(isset
($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strcasecmp($_SERVER['HTTP_X_REQUESTED_WITH'], 'XMLHttpRequest') ===
0 &&
strtoupper($_SERVER['REQUEST_METHOD']) ===
'POST');
private function strip_slashes_recursive($variable)
foreach ($variable as $i =>
$value)
$variable[$i] =
$this->strip_slashes_recursive($value);
* Return the data associatated with a processed unobstructive POST call
* @param string $alias The name of the alias for the process function
* @param mixed $default Any data that should be returned if there's no answer, defaults to null
* @return mixed Return $default if no data available, defaults to NULL
if (isset
($this->answers[$alias]) &&
!empty($this->answers[$alias]))
return $this->answers[$alias];
private function _process($unobstructive, $last_call)
if ( ! isset
($_POST['remote'])) {
if ($this->config['exceptions'])
$this->data['requested'] =
$_GET['_'];
$remote =
$_POST['remote'];
if (isset
($_POST['submit_id']))
$this->data['submit_id'] =
'#'.
"{
$_POST['submit_id']}";
$this->data['remote'] =
$remote;
if ($unobstructive ===
true)
if ($this->config['no_stripslashes'] ===
false)
$args =
$this->strip_slashes_recursive($_POST);
if (isset
($_POST['args']))
if ($this->config['no_stripslashes'] ===
false)
$args =
$this->strip_slashes_recursive($_POST['args']);
if ($last_call ===
true) unset
($_POST['args']);
foreach ($this->callbacks['pre'] as $func)
if (isset
($this->functions[$remote]))
foreach ($this->callbacks['post'] as $func)
$_POST['remote'] =
$remote;
if ($unobstructive ===
false)
header("Cache-Control: no-cache, must-revalidate");
header('Content-Type: application/json');
$this->answers[$remote] =
$response;
if ($this->config['exceptions'] &&
$last_call ===
true)
if ($unobstructive ===
false)
if ($this->config['exit_allowed'] ===
true)
if ($last_call ||
$response !==
null) exit;
* Process the AJAX requests if any
* @param bool $last_call Set this to false if any other further calls to process() will happen, otherwise it will exit
function process($last_call =
true)
$this->_process(false, $last_call);
elseif (strtoupper($_SERVER['REQUEST_METHOD']) ==
'POST' &&
isset
($_POST['remote']) &&
// Regular processing, unobstrutive post, pass the $_POST variable to the function anyway
$this->_process(true, $last_call);
* Config the current instance of phery
* @param array $config Associative array containing the following options
* // Defaults to true, stop further script execution
* 'exit_allowed' => true/false,
* // Don't apply stripslashes on the args
* 'no_stripslashes' => true/false,
* // Throw exceptions on errors
* 'exceptions' => true/false,
* // Set the functions that will be called even if is a
* // POST but not an AJAX call
* 'unobstructive' => array('function-alias-1','function-alias-2')
function config(array $config)
if (isset
($config['exit_allowed']))
$this->config['exit_allowed'] = (bool)
$config['exit_allowed'];
if (isset
($config['no_stripslashes']))
$this->config['no_stripslashes'] = (bool)
$config['no_stripslashes'];
if (isset
($config['exceptions']))
$this->config['exceptions'] = (bool)
$config['exceptions'];
if (isset
($config['unobstructive']) &&
is_array($config['unobstructive']))
* Generates just one instance. Useful to use in many included files. Chainable
* @param array $config Associative config array
static function instance(array $config =
null)
if (!(self::$instance instanceof
phery))
self::$instance =
new phery($config);
* Sets the functions to respond to the ajax call.
* For security reasons, these functions should not be available for direct POST/GET requests.
* These will be set only for AJAX requests as it will only be called in case of an ajax request,
* The answer/process function, must necessarily have the following structure:
* function func($ajax_data, $callback_data){
* $r = new phery_response; // or phery_response::factory();
* // Sometimes the $callback_data will have an item called 'submit_id',
* // is the ID of the calling DOM element.
* // if (isset($callback_data['submit_id'])) { }
* $r->jquery('#id')->animate(...);
* @param array $functions An array of functions to register to the instance.
function set(array $functions)
if (strtoupper($_SERVER['REQUEST_METHOD']) !=
'POST' &&
!isset
($_POST['remote'])) return $this;
if (isset
($functions) &&
is_array($functions))
foreach ($functions as $name =>
$func)
if (isset
($this->functions[$name]))
if ($this->config['exceptions']) throw
new phery_exception('The function "'.
$name.
'" already exists and was rewritten');
$this->functions[$name] =
$func;
if ($this->config['exceptions'])
throw
new phery_exception('Provided function "'.
$name.
'" isnt a valid function or method');
* Create a new instance of phery that can be chained, without the need of assigning it to a variable
* @param array $config Associative config array
static function factory(array $config =
null)
return new phery($config);
* Helper function that generates an ajax link, defaults to "A" tag
* @param string $title The content of the link
* @param string $function The PHP function assigned name on phery::set()
* @param array $attributes Extra attributes that can be passed to the link, like class, style, etc
* // Display confirmation on click
* 'confirm' => 'Are you sure?',
* // The tag for the item, defaults to a
* // Define another URI for the AJAX call, this defines the HREF of A
* 'href' => '/path/to/url',
* // Extra arguments to pass to the AJAX function, will be stored
* // in the args attribute as a JSON notation
* 'args' => array(1, "a"),
* // Set the "href" attriute for non-anchor (a) AJAX tags (like buttons or spans).
* // Works for A links too, it won't function without javascript
* 'target' => '/default/ajax/controller',
* // Define the data-type for the communication
* // Set the encoding of the data, defaults to UTF-8
* @param phery $phery Pass the current instance of phery, so it can check if the
* functions are defined, and throw exceptions
* @return string The mounted HTML tag
static function link_to($title, $function, array $attributes =
array(), phery $phery =
null)
if ($phery->config['exceptions']) throw
new phery_exception('The "function" argument must be provided to "link_to"');
if (!isset
($phery->functions[$function]))
if ($phery->config['exceptions'])
throw
new phery_exception('The function "'.
$function.
'" provided in "link_to" hasnt been set');
if (isset
($attributes['tag']))
$tag =
$attributes['tag'];
unset
($attributes['tag']);
if (isset
($attributes['target']))
$attributes['data-target'] =
$attributes['target'];
unset
($attributes['target']);
if (isset
($attributes['args']))
$attributes['data-args'] =
json_encode($attributes['args']);
unset
($attributes['args']);
if (isset
($attributes['confirm']))
$attributes['data-confirm'] =
$attributes['confirm'];
unset
($attributes['confirm']);
if (isset
($attributes['encoding']))
$encoding =
$attributes['encoding'];
unset
($attributes['encoding']);
$attributes['data-remote'] =
$function;
foreach ($attributes as $attribute =>
$value)
$ret[] =
"{
$attribute}=\"
".
htmlentities($value, ENT_COMPAT, $encoding, false).
"\"";
$ret[] =
">{$title}</{$tag}>";
* Create a <form> tag with ajax enabled. Must be closed manually with </form>
* @param string $action where to go, can be empty
* @param string $function Registered function name
* @param array $attributes
* 'confirm' => 'Are you sure?',
* // Type of call, defaults to JSON (to use phery_response)
* // 'all' submits all elements on the form, even
* // if empty or not checked, disabled also submit disabled elements
* 'submit' => array('all' => true, 'disabled' => true)
* // Set the encoding of the data, defaults to UTF-8
* @param phery $phery Pass the current instance of phery, so it can check if the functions are defined, and throw exceptions
* @return string The mounted <form> HTML tag
static function form_for($action, $function, array $attributes =
array(), phery $phery =
null)
if ($phery->config['exceptions']) throw
new phery_exception('The "function" argument must be provided to "form_for"');
if (!isset
($phery->functions[$function]))
if ($phery->config['exceptions'])
throw
new phery_exception('The function "'.
$function.
'" provided in "form_for" hasnt been set');
if (isset
($attributes['args']))
$attributes['data-args'] =
json_encode($attributes['args']);
unset
($attributes['args']);
if (isset
($attributes['confirm']))
$attributes['data-confirm'] =
$attributes['confirm'];
unset
($attributes['confirm']);
if (isset
($attributes['submit']))
$attributes['data-submit'] =
json_encode($attributes['submit']);
unset
($attributes['submit']);
if (isset
($attributes['encoding']))
$encoding =
$attributes['encoding'];
unset
($attributes['encoding']);
$ret[] =
'<form method="POST" action="'.
$action.
'" data-remote="'.
$function.
'"';
foreach ($attributes as $attribute =>
$value)
$ret[] =
"{
$attribute}=\"
".
htmlentities($value, ENT_COMPAT, $encoding, false).
"\"";
$ret[] =
'><input type="hidden" name="remote" value="'.
$function.
'"/>';
* Create a <select> element with ajax enabled on "change" event.
* @param string $function Registered function name
* @param array $items Options for the select, 'value' => 'text' representation
* @param array $attributes
* 'confirm' => 'Are you sure?',
* // Type of call, defaults to JSON (to use phery_response)
* // The URL where it should call
* 'target' => '/path/to/php',
* // Extra arguments to pass to the AJAX function, will be stored
* // in the args attribute as a JSON notation
* 'args' => array(1, "a"),
* // Set the encoding of the data, defaults to UTF-8
* // The current selected value, or array(1,2) for multiple
* @param phery $phery Pass the current instance of phery, so it can check if the functions are defined, and throw exceptions
* @return string The mounted <select> with <option>s inside
static function select_for($function, array $items, array $attributes =
array(), phery $phery =
null)
if ($phery->config['exceptions']) throw
new phery_exception('The "function" argument must be provided to "select_for"');
if (!isset
($phery->functions[$function]))
if ($phery->config['exceptions'])
throw
new phery_exception('The function "'.
$function.
'" provided in "select_for" hasnt been set');
if (isset
($attributes['args']))
$attributes['data-args'] =
json_encode($attributes['args']);
unset
($attributes['args']);
if (isset
($attributes['confirm']))
$attributes['data-confirm'] =
$attributes['confirm'];
unset
($attributes['confirm']);
if (isset
($attributes['encoding']))
$encoding =
$attributes['encoding'];
unset
($attributes['encoding']);
if (isset
($attributes['selected']))
$selected =
$attributes['selected'];
$selected =
array($attributes['selected']);
unset
($attributes['selected']);
if (isset
($attributes['multiple']))
$attributes['multiple'] =
'multiple';
$ret[] =
'<select data-remote="'.
$function.
'"';
foreach ($attributes as $attribute =>
$value)
$ret[] =
"{
$attribute}=\"
".
htmlentities($value, ENT_COMPAT, $encoding, false).
"\"";
foreach ($items as $value =>
$text)
$_value =
'value="'.
htmlentities($value, ENT_COMPAT, $encoding, false).
'"';
$_value .=
' selected="selected"';
$ret[] =
"<option {$_value}>{$text}</option>\n";
public function __set($name, $value)
$this->data[$name] =
$value;
public function __get($name)
if (isset
($this->data[$name]))
return $this->data[$name];
* Utility function taken from MYSQL
if (isset
($arg) &&
!empty($arg)) return $arg;
* Standard response for the json parser
* @subpackage phery_response
* @method phery_response detach() detach() Detach a DOM element retaining the events attached to it
* @method phery_response prependTo() pretendTo($target) Prepend DOM element to target
* @method phery_response appendTo() appendTo($target) Append DOM element to target
* @method phery_response replaceWith() replaceWith($newContent) The content to insert. May be an HTML string, DOM element, or jQuery object.
* @method phery_response css() css($propertyName, $value) propertyName: A CSS property name. value: A value to set for the property.
* @method phery_response toggle() toggle($speed) Toggle an object visible or hidden, can be animated with 'fast','slow','normal'
* @method phery_response hide() hide($speed) Hide an object, can be animated with 'fast','slow','normal'
* @method phery_response show() show($speed) Show an object, can be animated with 'fast','slow','normal'
* @method phery_response toggleClass() toggleClass($className) Add/Remove a class from an element
* @method phery_response data() data($name, $data) Add data to element
* @method phery_response addClass() addClass($className) Add a class from an element
* @method phery_response removeClass() removeClass($className) Remove a class from an element
* @method phery_response animate() animate($prop, $dur, $easing, $cb) Animate an element
* @method phery_response trigger() trigger($eventName, [$args]) Trigger an event
* @method phery_response fadeIn() fadeIn($prop, $dur, $easing, $cb) Animate an element
* @method phery_response filter() filter($selector) Filter elements
* @method phery_response fadeTo() fadeTo($dur, $opacity) Animate an element
* @method phery_response fadeOut() fadeOut($prop, $dur, $easing, $cb) Animate an element
* @method phery_response slideUp() slideUp($dur, $cb) Hide with slide up animation
* @method phery_response slideDown() slideDown($dur, $cb) Show with slide down animation
* @method phery_response slideToggle() slideToggle($dur, $cb) Toggle show/hide the element, using slide animation
* @method phery_response unbind() unbind($name) Unbind an event from an element
* @method phery_response stop() stop() Stop animation on elements
* @method phery_response live() live($name) Bind a live event to the selected elements
* @method phery_response die() die($name) Unbind an event from an element set by live()
* @method phery_response val() val($content) Set the value of an element
* @method phery_response removeData() removeData($element, $name) Remove element data added with data()
* @method phery_response removeAttr() removeAttr($name) Remove an attribute from an element
* @method phery_response scrollTop() scrollTop($val) Set the scroll from the top
* @method phery_response scrollLeft() scrollLeft($val) Set the scroll from the left
* @method phery_response height() height($val) Set the height from the left
* @method phery_response width() width($val) Set the width from the left
* @method phery_response slice() slice($start, $end) Reduce the set of matched elements to a subset specified by a range of indices.
* @method phery_response not() not($val) Remove elements from the set of matched elements.
* @method phery_response eq() eq($selector) Reduce the set of matched elements to the one at the specified index.
* Last jQuery selector defined
* Array containing answer data
private $arguments =
array();
* @param string $selector Create the object already selecting the DOM element
* Create a new phery_response instance for chaining, for one liners
* return phery_response::factory('a#link')->attr('href', '#')->alert('done');
* @param string $selector
static function factory($selector =
null)
* Merge another response to this one.
* Selectors with the same name will be added in order, for example:
* $response->jquery('a.links')->remove(); //from $response
* // there will be no more "a.links", so the addClass() will fail silently
* $response2->jquery('a.links')->addClass('red');
* return $response->merge($response2);
* @param phery_response $phery Another phery_response object
function merge(phery_response $phery)
* Sets the selector, so you can chain many calls to it
* @param string $selector Sets the current selector for subsequent chaining
* ->css(array('top' => '10px', 'left' => '90px'));
* Shortcut/alias for jquery($selector)
* @param string $selector Sets the current selector for subsequent chaining
return $this->jquery($selector);
* @param string $msg Message to be displayed
* Remove the current jQuery selector
* @param string $selector Set a selector
function remove($selector =
null)
* Add a command to the response
* @param int $cmd Integer for command, see phery.js for more info
* @param array $args Array to pass to the response
* @param string $selector Insert the jquery selector
function cmd($cmd, array $args, $selector =
null)
$selector =
phery::coalesce($selector, $this->last_selector);
if ($selector ===
null ||
!is_string($selector))
if (!isset
($this->data[$selector])) $this->data[$selector] =
array();
$this->data[$selector][] =
* Set the attribute of a jQuery selector
* $phery_response->attr('href', 'http://url.com', 'a#link-' . $args['id']);
* @param string $attr HTML attribute of the item
* @param string $selector [optional] Provide the jQuery selector directly
function attr($attr, $data, $selector =
null)
* Call a javascript function.
* Warning: calling this function will reset the selector jQuery selector previously stated
* @param string $func_name Function name
* @param mixed $args,... Any additional arguments to pass to the function
* Clear the selected attribute.
* Alias for attr('attrname', '')
* @param string $attr Name of the attribute to clear, such as 'innerHTML', 'style', 'href', etc
* @param string $selector [optional] Provide the jQuery selector directly
function clear($attr, $selector =
null)
return $this->attr($attr, '', $selector);
* Set the HTML content of an element.
* Automatically typecasted to string, so classes that
* respond to __toString() will be converted automatically
* @param string $selector [optional] Provide the jQuery selector directly
function html($content, $selector =
null)
* Set the text of an element.
* Automatically typecasted to string, so classes that
* respond to __toString() will be converted automatically
* @param string $selector [optional] Provide the jQuery selector directly
function text($content, $selector =
null)
* Compile a script and call it on-the-fly.
* There is a closure on the executed function, so
* to reach out global variables, you need to use window.variable
* Warning: calling this function will reset the selector jQuery selector previously stated
* @param string|array$script Script content. If provided an array, it will be joined with ;\n
* phery_response::factory()
* ->script(array("if (confirm('Are you really sure?')) $('*').remove()"));
$script =
join(";\n", $script);
* @param string $url Complete url with http:// (according W3C http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30)
* Prepend string/HTML to target(s)
* @param string $content Content to be prepended to the selected element
* @param string $selector [optional] Optional jquery selector string
function prepend($content, $selector =
null)
* Append string/HTML to target(s)
* @param string $content Content to be appended to the selected element
* @param string $selector [optional] Optional jquery selector string
function append($content, $selector =
null)
* Magically map to any additional jQuery function.
* To reach this magically called functions, the jquery() selector must be called prior
* to any jquery specific call
function __call($name, $arguments)
foreach ($arguments as &$argument)
$argument = (int)
$argument;
* Magic function to set data to the response before processing
public function __set($name, $value)
$this->arguments[$name] =
$value;
* Magic function to get data appended to the response object
public function __get($name)
if (isset
($this->arguments[$name]))
return $this->arguments[$name];
* Return the JSON encoded data
* Return the JSON encoded data
* if the object is typecasted as a string
* Interface for CustomException
* Protected methods inherited from Exception class
* User-defined Exception code
* An array of the backtrace()
* Formated string of trace
* Overrideable methods inherited from Exception class
* Formated string for display
public function __construct($message =
null, $code =
0);
* CustomException for phery
protected $message =
'Unknown exception'; // Exception message
private $string; // Unknown
protected $code =
0; // User-defined exception code
protected $file; // Source filename of exception
protected $line; // Source line of exception
private $trace; // Unknown
public function __construct($message =
null, $code =
0)
throw
new $this('Unknown '.
get_class($this));
parent::__construct($message, $code);
."{
$this->getTraceAsString()}";
* Exception class for phery specific exceptions
* @subpackage phery_exception
class phery_exception extends CustomException {
Documentation generated on Tue, 14 Jun 2011 15:54:43 -0300 by phpDocumentor 1.4.3