
/*
 * This file is part of Pimple.
 *
 * Copyright (c) 2014 Fabien Potencier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_pimple.h"
#include "pimple_compat.h"
#include "zend_interfaces.h"
#include "zend.h"
#include "ext/spl/spl_exceptions.h"
#include "Zend/zend_exceptions.h"
#include "main/php_output.h"
#include "SAPI.h"

static zend_class_entry *pimple_ce;
static zend_object_handlers pimple_object_handlers;
static zend_class_entry *pimple_closure_ce;
static zend_class_entry *pimple_serviceprovider_ce;
static zend_object_handlers pimple_closure_object_handlers;

#define FETCH_DIM_HANDLERS_VARS 	pimple_object *pimple_obj = NULL; \
									ulong index; \
									pimple_obj = (pimple_object *)zend_object_store_get_object(object TSRMLS_CC); \

#define PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS	do { \
	if (ce != pimple_ce) { \
		zend_hash_find(&ce->function_table, ZEND_STRS("offsetget"), (void **)&function); \
		if (function->common.scope != ce) { /* if the function is not defined in this actual class */ \
			pimple_object_handlers.read_dimension = pimple_object_read_dimension; /* then overwrite the handler to use custom one */ \
		} \
		zend_hash_find(&ce->function_table, ZEND_STRS("offsetset"), (void **)&function); \
		if (function->common.scope != ce) { \
			pimple_object_handlers.write_dimension = pimple_object_write_dimension; \
		} \
		zend_hash_find(&ce->function_table, ZEND_STRS("offsetexists"), (void **)&function); \
		if (function->common.scope != ce) { \
			pimple_object_handlers.has_dimension = pimple_object_has_dimension; \
		} \
		zend_hash_find(&ce->function_table, ZEND_STRS("offsetunset"), (void **)&function); \
		if (function->common.scope != ce) { \
			pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \
		} \
	} else { \
		pimple_object_handlers.read_dimension = pimple_object_read_dimension; \
		pimple_object_handlers.write_dimension = pimple_object_write_dimension; \
		pimple_object_handlers.has_dimension = pimple_object_has_dimension; \
		pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \
	}\
											} while(0);

#define PIMPLE_CALL_CB	do { \
			zend_fcall_info_argn(&fci TSRMLS_CC, 1, &object); \
			fci.size           = sizeof(fci); \
			fci.object_ptr     = retval->fcc->object_ptr; \
			fci.function_name  = retval->value; \
			fci.no_separation  = 1; \
			fci.retval_ptr_ptr = &retval_ptr_ptr; \
\
			zend_call_function(&fci, retval->fcc TSRMLS_CC); \
			efree(fci.params); \
			if (EG(exception)) { \
				return EG(uninitialized_zval_ptr); \
			} \
						} while(0);

ZEND_BEGIN_ARG_INFO_EX(arginfo___construct, 0, 0, 0)
ZEND_ARG_ARRAY_INFO(0, value, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetset, 0, 0, 2)
ZEND_ARG_INFO(0, offset)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetget, 0, 0, 1)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetexists, 0, 0, 1)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetunset, 0, 0, 1)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_factory, 0, 0, 1)
ZEND_ARG_INFO(0, callable)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_protect, 0, 0, 1)
ZEND_ARG_INFO(0, callable)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_raw, 0, 0, 1)
ZEND_ARG_INFO(0, id)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_extend, 0, 0, 2)
ZEND_ARG_INFO(0, id)
ZEND_ARG_INFO(0, callable)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_register, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, provider, Pimple\\ServiceProviderInterface, 0)
ZEND_ARG_ARRAY_INFO(0, values, 1)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_pimpleclosure___invoke, 0, 0, 1)
ZEND_ARG_INFO(0, callarg)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_serviceprovider_register, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, pimple, Pimple\\Container, 0)
ZEND_END_ARG_INFO()

static const zend_function_entry pimple_ce_functions[] = {
	PHP_ME(Pimple, __construct,	arginfo___construct, ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, factory,         arginfo_factory,         ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, protect,         arginfo_protect,         ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, raw,             arginfo_raw,             ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, extend,          arginfo_extend,          ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, keys,            arginfo_keys,            ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, register,		arginfo_register,		 ZEND_ACC_PUBLIC)

	PHP_ME(Pimple, offsetSet,       arginfo_offsetset,       ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, offsetGet,       arginfo_offsetget,       ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, offsetExists,    arginfo_offsetexists,    ZEND_ACC_PUBLIC)
	PHP_ME(Pimple, offsetUnset,     arginfo_offsetunset,     ZEND_ACC_PUBLIC)
	PHP_FE_END
};

static const zend_function_entry pimple_closure_ce_functions[] = {
	PHP_ME(PimpleClosure, __invoke,     arginfo_pimpleclosure___invoke,    ZEND_ACC_PRIVATE)
	PHP_FE_END
};

static const zend_function_entry pimple_serviceprovider_iface_ce_functions[] = {
	PHP_ABSTRACT_ME(ServiceProviderInterface, register, arginfo_serviceprovider_register)
	PHP_FE_END
};

static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC)
{
	zend_object_std_dtor(&obj->zobj TSRMLS_CC);
	if (obj->factory) {
		zval_ptr_dtor(&obj->factory);
	}
	if (obj->callable) {
		zval_ptr_dtor(&obj->callable);
	}
	efree(obj);
}

static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC)
{
	zend_hash_destroy(&obj->factories);
	zend_hash_destroy(&obj->protected);
	zend_hash_destroy(&obj->values);
	zend_object_std_dtor(&obj->zobj TSRMLS_CC);
	efree(obj);
}

static void pimple_free_bucket(pimple_bucket_value *bucket)
{
	if (bucket->fcc) {
		efree(bucket->fcc);
		bucket->fcc = NULL;
	}
	if (bucket->raw) {
		zval_ptr_dtor(&bucket->raw);
	}
}

static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC)
{
	zend_object_value retval;
	pimple_closure_object *pimple_closure_obj = NULL;

	pimple_closure_obj = ecalloc(1, sizeof(pimple_closure_object));
	ZEND_OBJ_INIT(&pimple_closure_obj->zobj, ce);

	pimple_closure_object_handlers.get_constructor = pimple_closure_get_constructor;
	retval.handlers = &pimple_closure_object_handlers;
	retval.handle   = zend_objects_store_put(pimple_closure_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_closure_free_object_storage, NULL TSRMLS_CC);

	return retval;
}

static zend_function *pimple_closure_get_constructor(zval *obj TSRMLS_DC)
{
	zend_error(E_ERROR, "Pimple\\ContainerClosure is an internal class and cannot be instantiated");

	return NULL;
}

static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC)
{
	zend_object_value retval;
	pimple_object *pimple_obj  = NULL;
	zend_function *function    = NULL;

	pimple_obj = emalloc(sizeof(pimple_object));
	ZEND_OBJ_INIT(&pimple_obj->zobj, ce);

	PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS

	retval.handlers = &pimple_object_handlers;
	retval.handle   = zend_objects_store_put(pimple_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_free_object_storage, NULL TSRMLS_CC);

	zend_hash_init(&pimple_obj->factories, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0);
	zend_hash_init(&pimple_obj->protected, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0);
	zend_hash_init(&pimple_obj->values, PIMPLE_DEFAULT_ZVAL_VALUES_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0);

	return retval;
}

static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC)
{
	FETCH_DIM_HANDLERS_VARS

	pimple_bucket_value pimple_value = {0}, *found_value = NULL;
	ulong hash;

	pimple_init_bucket(&pimple_value);
	pimple_zval_to_pimpleval(value, &pimple_value TSRMLS_CC);

	if (!offset) {/* $p[] = 'foo' when not overloaded */
		zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL);
		Z_ADDREF_P(value);
		return;
	}

	switch (Z_TYPE_P(offset)) {
	case IS_STRING:
		hash = zend_hash_func(Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
		zend_hash_quick_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void **)&found_value);
		if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) {
			pimple_free_bucket(&pimple_value);
			zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, "Cannot override frozen service \"%s\".", Z_STRVAL_P(offset));
			return;
		}
		if (zend_hash_quick_update(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) {
			pimple_free_bucket(&pimple_value);
			return;
		}
		Z_ADDREF_P(value);
	break;
	case IS_DOUBLE:
	case IS_BOOL:
	case IS_LONG:
		if (Z_TYPE_P(offset) == IS_DOUBLE) {
			index = (ulong)Z_DVAL_P(offset);
		} else {
			index = Z_LVAL_P(offset);
		}
		zend_hash_index_find(&pimple_obj->values, index, (void **)&found_value);
		if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) {
			pimple_free_bucket(&pimple_value);
			zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, "Cannot override frozen service \"%ld\".", index);
			return;
		}
		if (zend_hash_index_update(&pimple_obj->values, index, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) {
			pimple_free_bucket(&pimple_value);
			return;
		}
		Z_ADDREF_P(value);
	break;
	case IS_NULL: /* $p[] = 'foo' when overloaded */
		zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL);
		Z_ADDREF_P(value);
	break;
	default:
		pimple_free_bucket(&pimple_value);
		zend_error(E_WARNING, "Unsupported offset type");
	}
}

static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC)
{
	FETCH_DIM_HANDLERS_VARS

	switch (Z_TYPE_P(offset)) {
	case IS_STRING:
		zend_symtable_del(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
		zend_symtable_del(&pimple_obj->factories, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
		zend_symtable_del(&pimple_obj->protected, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
	break;
	case IS_DOUBLE:
	case IS_BOOL:
	case IS_LONG:
		if (Z_TYPE_P(offset) == IS_DOUBLE) {
			index = (ulong)Z_DVAL_P(offset);
		} else {
			index = Z_LVAL_P(offset);
		}
		zend_hash_index_del(&pimple_obj->values, index);
		zend_hash_index_del(&pimple_obj->factories, index);
		zend_hash_index_del(&pimple_obj->protected, index);
	break;
	default:
		zend_error(E_WARNING, "Unsupported offset type");
	}
}

static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC)
{
	FETCH_DIM_HANDLERS_VARS

	pimple_bucket_value *retval = NULL;

	switch (Z_TYPE_P(offset)) {
	case IS_STRING:
		if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == SUCCESS) {
			switch (check_empty) {
			case 0: /* isset */
				return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;) */
			case 1: /* empty */
			default:
				return zend_is_true(retval->value);
			}
		}
		return 0;
	break;
	case IS_DOUBLE:
	case IS_BOOL:
	case IS_LONG:
		if (Z_TYPE_P(offset) == IS_DOUBLE) {
			index = (ulong)Z_DVAL_P(offset);
		} else {
			index = Z_LVAL_P(offset);
		}
		if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == SUCCESS) {
			switch (check_empty) {
				case 0: /* isset */
					return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;)*/
				case 1: /* empty */
				default:
					return zend_is_true(retval->value);
			}
		}
		return 0;
	break;
	default:
		zend_error(E_WARNING, "Unsupported offset type");
		return 0;
	}
}

static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC)
{
	FETCH_DIM_HANDLERS_VARS

	pimple_bucket_value *retval = NULL;
	zend_fcall_info fci         = {0};
	zval *retval_ptr_ptr        = NULL;

	switch (Z_TYPE_P(offset)) {
	case IS_STRING:
		if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == FAILURE) {
			zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset));
			return EG(uninitialized_zval_ptr);
		}
	break;
	case IS_DOUBLE:
	case IS_BOOL:
	case IS_LONG:
		if (Z_TYPE_P(offset) == IS_DOUBLE) {
			index = (ulong)Z_DVAL_P(offset);
		} else {
			index = Z_LVAL_P(offset);
		}
		if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == FAILURE) {
			return EG(uninitialized_zval_ptr);
		}
	break;
	case IS_NULL: /* $p[][3] = 'foo' first dim access */
		return EG(uninitialized_zval_ptr);
	break;
	default:
		zend_error(E_WARNING, "Unsupported offset type");
		return EG(uninitialized_zval_ptr);
	}

	if(retval->type == PIMPLE_IS_PARAM) {
		return retval->value;
	}

	if (zend_hash_index_exists(&pimple_obj->protected, retval->handle_num)) {
		/* Service is protected, return the value every time */
		return retval->value;
	}

	if (zend_hash_index_exists(&pimple_obj->factories, retval->handle_num)) {
		/* Service is a factory, call it everytime and never cache its result */
		PIMPLE_CALL_CB
		Z_DELREF_P(retval_ptr_ptr); /* fetch dim addr will increment refcount */
		return retval_ptr_ptr;
	}

	if (retval->initialized == 1) {
		/* Service has already been called, return its cached value */
		return retval->value;
	}

	ALLOC_INIT_ZVAL(retval->raw);
	MAKE_COPY_ZVAL(&retval->value, retval->raw);

	PIMPLE_CALL_CB

	retval->initialized = 1;
	zval_ptr_dtor(&retval->value);
	retval->value = retval_ptr_ptr;

	return retval->value;
}

static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC)
{
	if (Z_TYPE_P(_zval) != IS_OBJECT) {
		return FAILURE;
	}

	if (_pimple_bucket_value->fcc->called_scope) {
		return SUCCESS;
	}

	if (Z_OBJ_HANDLER_P(_zval, get_closure) && Z_OBJ_HANDLER_P(_zval, get_closure)(_zval, &_pimple_bucket_value->fcc->calling_scope, &_pimple_bucket_value->fcc->function_handler, &_pimple_bucket_value->fcc->object_ptr TSRMLS_CC) == SUCCESS) {
		_pimple_bucket_value->fcc->called_scope = _pimple_bucket_value->fcc->calling_scope;
		return SUCCESS;
	} else {
		return FAILURE;
	}
}

static void pimple_init_bucket(pimple_bucket_value *bucket)
{
	memset(bucket, 0, sizeof(pimple_bucket_value));
	bucket->fcc = ecalloc(1, sizeof(zend_fcall_info_cache));
}

static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC)
{
	_pimple_bucket_value->value = _zval;

	if (Z_TYPE_P(_zval) != IS_OBJECT) {
		return PIMPLE_IS_PARAM;
	}

	if (pimple_zval_is_valid_callback(_zval, _pimple_bucket_value TSRMLS_CC) == SUCCESS) {
		_pimple_bucket_value->type       = PIMPLE_IS_SERVICE;
		_pimple_bucket_value->handle_num = Z_OBJ_HANDLE_P(_zval);
	}

	return PIMPLE_IS_SERVICE;
}

static void pimple_bucket_dtor(pimple_bucket_value *bucket)
{
	zval_ptr_dtor(&bucket->value);
	pimple_free_bucket(bucket);
}

PHP_METHOD(Pimple, protect)
{
	zval *protected     = NULL;
	pimple_object *pobj = NULL;
	pimple_bucket_value bucket = {0};

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &protected) == FAILURE) {
		return;
	}

	pimple_init_bucket(&bucket);

	if (pimple_zval_is_valid_callback(protected, &bucket TSRMLS_CC) == FAILURE) {
		pimple_free_bucket(&bucket);
		zend_throw_exception(spl_ce_InvalidArgumentException, "Callable is not a Closure or invokable object.", 0 TSRMLS_CC);
		return;
	}

	pimple_zval_to_pimpleval(protected, &bucket TSRMLS_CC);
	pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC);

	if (zend_hash_index_update(&pobj->protected, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) {
		RETURN_ZVAL(protected, 1 , 0);
	} else {
		pimple_free_bucket(&bucket);
	}
	RETURN_FALSE;
}

PHP_METHOD(Pimple, raw)
{
	zval *offset = NULL;
	pimple_object *pobj        = NULL;
	pimple_bucket_value *value = NULL;
	ulong index;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
		return;
	}

	pobj = zend_object_store_get_object(getThis() TSRMLS_CC);

	switch (Z_TYPE_P(offset)) {
		case IS_STRING:
			if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) {
				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset));
				RETURN_NULL();
			}
		break;
		case IS_DOUBLE:
		case IS_BOOL:
		case IS_LONG:
			if (Z_TYPE_P(offset) == IS_DOUBLE) {
				index = (ulong)Z_DVAL_P(offset);
			} else {
				index = Z_LVAL_P(offset);
			}
			if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) {
				RETURN_NULL();
			}
		break;
		case IS_NULL:
		default:
			zend_error(E_WARNING, "Unsupported offset type");
	}

	if (value->raw) {
		RETVAL_ZVAL(value->raw, 1, 0);
	} else {
		RETVAL_ZVAL(value->value, 1, 0);
	}
}

PHP_METHOD(Pimple, extend)
{
	zval *offset = NULL, *callable = NULL, *pimple_closure_obj = NULL;
	pimple_bucket_value bucket = {0}, *value = NULL;
	pimple_object *pobj          = NULL;
	pimple_closure_object *pcobj = NULL;
	ulong index;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &callable) == FAILURE) {
		return;
	}

	pobj = zend_object_store_get_object(getThis() TSRMLS_CC);

	switch (Z_TYPE_P(offset)) {
		case IS_STRING:
			if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) {
				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset));
				RETURN_NULL();
			}
			if (value->type != PIMPLE_IS_SERVICE) {
				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" does not contain an object definition.", Z_STRVAL_P(offset));
				RETURN_NULL();
			}
		break;
		case IS_DOUBLE:
		case IS_BOOL:
		case IS_LONG:
			if (Z_TYPE_P(offset) == IS_DOUBLE) {
				index = (ulong)Z_DVAL_P(offset);
			} else {
				index = Z_LVAL_P(offset);
			}
			if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) {
				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%ld\" is not defined.", index);
				RETURN_NULL();
			}
			if (value->type != PIMPLE_IS_SERVICE) {
				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%ld\" does not contain an object definition.", index);
				RETURN_NULL();
			}
		break;
		case IS_NULL:
		default:
			zend_error(E_WARNING, "Unsupported offset type");
	}

	pimple_init_bucket(&bucket);

	if (pimple_zval_is_valid_callback(callable, &bucket TSRMLS_CC) == FAILURE) {
		pimple_free_bucket(&bucket);
		zend_throw_exception(spl_ce_InvalidArgumentException, "Extension service definition is not a Closure or invokable object.", 0 TSRMLS_CC);
		RETURN_NULL();
	}
	pimple_free_bucket(&bucket);

	ALLOC_INIT_ZVAL(pimple_closure_obj);
	object_init_ex(pimple_closure_obj, pimple_closure_ce);

	pcobj = zend_object_store_get_object(pimple_closure_obj TSRMLS_CC);
	pcobj->callable = callable;
	pcobj->factory  = value->value;
	Z_ADDREF_P(callable);
	Z_ADDREF_P(value->value);

	if (zend_hash_index_exists(&pobj->factories, value->handle_num)) {
		pimple_init_bucket(&bucket);
		pimple_zval_to_pimpleval(pimple_closure_obj, &bucket TSRMLS_CC);
		zend_hash_index_del(&pobj->factories, value->handle_num);
		zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL);
		Z_ADDREF_P(pimple_closure_obj);
	}

	pimple_object_write_dimension(getThis(), offset, pimple_closure_obj TSRMLS_CC);

	RETVAL_ZVAL(pimple_closure_obj, 1, 1);
}

PHP_METHOD(Pimple, keys)
{
	HashPosition pos;
	pimple_object *pobj = NULL;
	zval **value        = NULL;
	zval *endval        = NULL;
	char *str_index     = NULL;
	int str_len;
	ulong num_index;

	if (zend_parse_parameters_none() == FAILURE) {
		return;
	}

	pobj = zend_object_store_get_object(getThis() TSRMLS_CC);
	array_init_size(return_value, zend_hash_num_elements(&pobj->values));

	zend_hash_internal_pointer_reset_ex(&pobj->values, &pos);

	while(zend_hash_get_current_data_ex(&pobj->values, (void **)&value, &pos) == SUCCESS) {
		MAKE_STD_ZVAL(endval);
		switch (zend_hash_get_current_key_ex(&pobj->values, &str_index, (uint *)&str_len, &num_index, 0, &pos)) {
			case HASH_KEY_IS_STRING:
				ZVAL_STRINGL(endval, str_index, str_len - 1, 1);
				zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL);
			break;
			case HASH_KEY_IS_LONG:
				ZVAL_LONG(endval, num_index);
				zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL);
			break;
		}
	zend_hash_move_forward_ex(&pobj->values, &pos);
	}
}

PHP_METHOD(Pimple, factory)
{
	zval *factory       = NULL;
	pimple_object *pobj = NULL;
	pimple_bucket_value bucket = {0};

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &factory) == FAILURE) {
		return;
	}

	pimple_init_bucket(&bucket);

	if (pimple_zval_is_valid_callback(factory, &bucket TSRMLS_CC) == FAILURE) {
		pimple_free_bucket(&bucket);
		zend_throw_exception(spl_ce_InvalidArgumentException, "Service definition is not a Closure or invokable object.", 0 TSRMLS_CC);
		return;
	}

	pimple_zval_to_pimpleval(factory, &bucket TSRMLS_CC);
	pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC);

	if (zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) {
		Z_ADDREF_P(factory);
		RETURN_ZVAL(factory, 1 , 0);
	} else {
		pimple_free_bucket(&bucket);
	}

	RETURN_FALSE;
}

PHP_METHOD(Pimple, offsetSet)
{
	zval *offset = NULL, *value = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &value) == FAILURE) {
		return;
	}

	pimple_object_write_dimension(getThis(), offset, value TSRMLS_CC);
}

PHP_METHOD(Pimple, offsetGet)
{
	zval *offset = NULL, *retval = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
		return;
	}

	retval = pimple_object_read_dimension(getThis(), offset, 0 TSRMLS_CC);

	RETVAL_ZVAL(retval, 1, 0);
}

PHP_METHOD(Pimple, offsetUnset)
{
	zval *offset = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
		return;
	}

	pimple_object_unset_dimension(getThis(), offset TSRMLS_CC);
}

PHP_METHOD(Pimple, offsetExists)
{
	zval *offset = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
		return;
	}

	RETVAL_BOOL(pimple_object_has_dimension(getThis(), offset, 1 TSRMLS_CC));
}

PHP_METHOD(Pimple, register)
{
	zval *provider;
	zval **data;
	zval *retval = NULL;
	zval key;

	HashTable *array = NULL;
	HashPosition pos;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|h", &provider, pimple_serviceprovider_ce, &array) == FAILURE) {
		return;
	}

	zend_call_method_with_1_params(&provider, Z_OBJCE_P(provider), NULL, "register", &retval, getThis());

	if (retval) {
		zval_ptr_dtor(&retval);
	}

	if (!array) {
		return;
	}

	zend_hash_internal_pointer_reset_ex(array, &pos);

	while(zend_hash_get_current_data_ex(array, (void **)&data, &pos) == SUCCESS) {
		zend_hash_get_current_key_zval_ex(array, &key, &pos);
		pimple_object_write_dimension(getThis(), &key, *data TSRMLS_CC);
		zend_hash_move_forward_ex(array, &pos);
	}

	RETVAL_ZVAL(getThis(), 1, 0);
}

PHP_METHOD(Pimple, __construct)
{
	zval *values = NULL, **pData = NULL, offset = {0};
	HashPosition pos;
	char *str_index = NULL;
	zend_uint str_length;
	ulong num_index;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &values) == FAILURE || !values) {
		return;
	}

	zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos);
	while (zend_hash_has_more_elements_ex(Z_ARRVAL_P(values), &pos) == SUCCESS) {
			zend_hash_get_current_data_ex(Z_ARRVAL_P(values), (void **)&pData, &pos);
			zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &str_index, &str_length, &num_index, 0, &pos);
			if (zend_hash_get_current_key_type_ex(Z_ARRVAL_P(values), &pos) == HASH_KEY_IS_LONG) {
				ZVAL_LONG(&offset, num_index);
			} else {
				ZVAL_STRINGL(&offset, str_index, (str_length - 1), 0);
			}
		pimple_object_write_dimension(getThis(), &offset, *pData TSRMLS_CC);
		zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos);
	}
}

/*
 * This is PHP code snippet handling extend()s calls :

  $extended = function ($c) use ($callable, $factory) {
      return $callable($factory($c), $c);
  };

 */
PHP_METHOD(PimpleClosure, __invoke)
{
	pimple_closure_object *pcobj = NULL;
	zval *arg = NULL, *retval = NULL, *newretval = NULL;
	zend_fcall_info fci        = {0};
	zval **args[2];

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE) {
		return;
	}

	pcobj = zend_object_store_get_object(getThis() TSRMLS_CC);

	fci.function_name = pcobj->factory;
	args[0] = &arg;
	zend_fcall_info_argp(&fci TSRMLS_CC, 1, args);
	fci.retval_ptr_ptr = &retval;
	fci.size = sizeof(fci);

	if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) {
		efree(fci.params);
		return; /* Should here return default zval */
	}

	efree(fci.params);
	memset(&fci, 0, sizeof(fci));
	fci.size = sizeof(fci);

	fci.function_name = pcobj->callable;
	args[0] = &retval;
	args[1] = &arg;
	zend_fcall_info_argp(&fci TSRMLS_CC, 2, args);
	fci.retval_ptr_ptr = &newretval;

	if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) {
		efree(fci.params);
		zval_ptr_dtor(&retval);
		return;
	}

	efree(fci.params);
	zval_ptr_dtor(&retval);
	RETVAL_ZVAL(newretval, 1 ,1);
}

PHP_MINIT_FUNCTION(pimple)
{
	zend_class_entry tmp_pimple_ce, tmp_pimple_closure_ce, tmp_pimple_serviceprovider_iface_ce;
	INIT_NS_CLASS_ENTRY(tmp_pimple_ce, PIMPLE_NS, "Container", pimple_ce_functions);
	INIT_NS_CLASS_ENTRY(tmp_pimple_closure_ce, PIMPLE_NS, "ContainerClosure", pimple_closure_ce_functions);
	INIT_NS_CLASS_ENTRY(tmp_pimple_serviceprovider_iface_ce, PIMPLE_NS, "ServiceProviderInterface", pimple_serviceprovider_iface_ce_functions);

	tmp_pimple_ce.create_object         = pimple_object_create;
	tmp_pimple_closure_ce.create_object = pimple_closure_object_create;

	pimple_ce = zend_register_internal_class(&tmp_pimple_ce TSRMLS_CC);
	zend_class_implements(pimple_ce TSRMLS_CC, 1, zend_ce_arrayaccess);

	pimple_closure_ce = zend_register_internal_class(&tmp_pimple_closure_ce TSRMLS_CC);
	pimple_closure_ce->ce_flags |= ZEND_ACC_FINAL_CLASS;

	pimple_serviceprovider_ce = zend_register_internal_interface(&tmp_pimple_serviceprovider_iface_ce TSRMLS_CC);

	pimple_closure_object_handlers = std_object_handlers;
	pimple_object_handlers         = std_object_handlers;

	return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(pimple)
{
	return SUCCESS;
}

PHP_RINIT_FUNCTION(pimple)
{
	return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(pimple)
{
	return SUCCESS;
}

PHP_MINFO_FUNCTION(pimple)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "SensioLabs Pimple C support", "enabled");
	php_info_print_table_row(2, "Pimple supported version", PIMPLE_VERSION);
	php_info_print_table_end();

	php_info_print_box_start(0);
	php_write((void *)ZEND_STRL("SensioLabs Pimple C support developed by Julien Pauli") TSRMLS_CC);
	if (!sapi_module.phpinfo_as_text) {
		php_write((void *)ZEND_STRL(sensiolabs_logo) TSRMLS_CC);
	}
	php_info_print_box_end();
}

zend_module_entry pimple_module_entry = {
	STANDARD_MODULE_HEADER,
	"pimple",
	NULL,
	PHP_MINIT(pimple),
	PHP_MSHUTDOWN(pimple),
	PHP_RINIT(pimple),
	PHP_RSHUTDOWN(pimple),
	PHP_MINFO(pimple),
	PIMPLE_VERSION,
	STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_PIMPLE
ZEND_GET_MODULE(pimple)
#endif
