Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

[Debug] Symfony debug extension #10500

Closed
wants to merge 2 commits into from

6 participants

@nicolas-grekas
Collaborator
Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets none
License MIT
Doc PR none

Add a symfony_zval_info($key, $array, $options = 0) function in a PHP extension written in C, that:

  • exposes zval_hash/refcount, allowing efficient exploration of arbitrary structure in PHP,
  • does work with references, preventing memory copying.

Please see the provided README for more info.

src/Symfony/Component/Debug/ext/symfony_debug.php
@@ -0,0 +1,21 @@
+<?php
+$br = (php_sapi_name() == "cli")? "":"<br>";

missing headers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jpauli jpauli referenced this pull request
Closed

Symfony debug extension #10498

@Tobion
Collaborator

What's the relation to symfony?

@nicolas-grekas
Collaborator

The first short term use of this function is porting https://github.com/nicolas-grekas/Patchwork-Dumper to Symfony Debug component. With a target for enhancing the debug toolbar. Patchwork Dumper relies on an algorithm that allows exploring soft and hard references in arbitrary PHP structures. But PHP has no primitive to make this happen easily (== fast and memory efficient).
With this function, we add this missing simple primitive.
We also have other ideas with Julien to add more debug functions in this extension, that could lead to better debugging of memory leaks for example.

@stof
Collaborator

Given that such extension does not have any PHP counter-part, I find it weird to include it in the same repo than the PHP code while it requires a separate installation process (for Twig, the same feature is provided without the extension and the algorithms must stay in sync, so it is fine to have it in the repo).

Thus, the function is not even specific to Symfony. So IMO, it should be maintained in a separate repo (or even contributed to XDebug if possible, to have a single debugging extension instead of 2).
Btw, how does this function relate to xdebug_debug_zval ?

@Tobion
Collaborator

I also don't really think it belongs into symfony since it's not symfony specific.

@jpauli

We've been told to merge it here by @fabpot , so let's wait for his answer, so that he will clarify all this. Thx.

@nicolas-grekas
Collaborator

I didn't explain well: the dumper is already implemented in pure PHP. That won't change. And when this extension is enabled, it could be used to gain a LOT of performance. This compares to the twig extension on this point: not required, but you gain perf. when enabled.

Does it belong to Symfony? What is Symfony? It's not a full stack framework. We are talking here of the Debug component. Symfony is dedicated to give powerful tools to PHP developers. A debug extension, that hooks into the PHP engine to give more information to devs is a very good thing.

For your question about xdebug_debug_zval, it's different in many way: It gives new information that is not available in PHP (zval_hash). That is of premium use for a dumper. Moreover, it is not targeted at one specific use (it does no output but returns an array). That means that even if I personally plan to use it for dumping efficiently, anyone else can come with an other use case, or a better dumper, and build something cool upon it. That! is powerfulness :)

@stof
Collaborator

@nicolas-grekas I don't disagree about having it as part of Symfony (I suggested contributing it to XDebug to have a single debugging extension needing to be installed, but this is not a blocker for me). But IMO, the C extension should probably live inside its own repo

@nicolas-grekas
Collaborator

Replaced by #10640

@nicolas-grekas nicolas-grekas deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 26, 2014
  1. @jpauli @nicolas-grekas

    [Debug] Symfony debug extension

    jpauli authored nicolas-grekas committed
  2. @nicolas-grekas
This page is out of date. Refresh to see the latest.
View
71 src/Symfony/Component/Debug/Resources/ext/README.rst
@@ -0,0 +1,71 @@
+Symfony Debug Extension
+=======================
+
+This extension adds a ``symfony_zval_info($key, $array, $options = 0)`` function that:
+
+- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP,
+- does work with references, preventing memory copying.
+
+Its behavior is about the same as:
+
+.. code-block:: php
+
+ <?php
+
+ function symfony_zval_info($key, $array, $options = 0)
+ {
+ // $options is currenlty not used, but could be in future version.
+
+ if (!array_key_exists($key, $array)) {
+ return null;
+ }
+
+ $info = array(
+ 'type' => gettype($array[$key]),
+ 'zval_hash' => /* hashed memory address of $array[$key] */,
+ 'zval_refcount' => /* internal zval refcount of $array[$key] */,
+ 'zval_isref' => /* is_ref status of $array[$key] */,
+ );
+
+ switch ($info['type']) {
+ case 'object':
+ $info += array(
+ 'object_class' => get_class($array[$key]),
+ 'object_refcount' => /* internal object refcount of $array[$key] */,
+ 'object_hash' => spl_object_hash($array[$key]),
+ );
+ break;
+
+ case 'resource':
+ $info += array(
+ 'resource_id' => (int) substr((string) $array[$key], 13),
+ 'resource_type' => get_resource_type($array[$key]),
+ 'resource_refcount' => /* internal resource refcount of $array[$key] */,
+ );
+ break;
+
+ case 'array':
+ $info += array(
+ 'array_count' => count($array[$key]),
+ );
+ break;
+
+ case 'string':
+ $info += array(
+ 'strlen' => strlen($array[$key]),
+ );
+ break;
+ }
+
+ return $info;
+ }
+
+To enable the extension from source, run:
+
+.. code-block:: sh
+
+ phpize
+ ./configure
+ make
+ sudo make install
+
View
63 src/Symfony/Component/Debug/Resources/ext/config.m4
@@ -0,0 +1,63 @@
+dnl $Id$
+dnl config.m4 for extension symfony_debug
+
+dnl Comments in this file start with the string 'dnl'.
+dnl Remove where necessary. This file will not work
+dnl without editing.
+
+dnl If your extension references something external, use with:
+
+dnl PHP_ARG_WITH(symfony_debug, for symfony_debug support,
+dnl Make sure that the comment is aligned:
+dnl [ --with-symfony_debug Include symfony_debug support])
+
+dnl Otherwise use enable:
+
+PHP_ARG_ENABLE(symfony_debug, whether to enable symfony_debug support,
+dnl Make sure that the comment is aligned:
+[ --enable-symfony_debug Enable symfony_debug support])
+
+if test "$PHP_SYMFONY_DEBUG" != "no"; then
+ dnl Write more examples of tests here...
+
+ dnl # --with-symfony_debug -> check with-path
+ dnl SEARCH_PATH="/usr/local /usr" # you might want to change this
+ dnl SEARCH_FOR="/include/symfony_debug.h" # you most likely want to change this
+ dnl if test -r $PHP_SYMFONY_DEBUG/$SEARCH_FOR; then # path given as parameter
+ dnl SYMFONY_DEBUG_DIR=$PHP_SYMFONY_DEBUG
+ dnl else # search default path list
+ dnl AC_MSG_CHECKING([for symfony_debug files in default path])
+ dnl for i in $SEARCH_PATH ; do
+ dnl if test -r $i/$SEARCH_FOR; then
+ dnl SYMFONY_DEBUG_DIR=$i
+ dnl AC_MSG_RESULT(found in $i)
+ dnl fi
+ dnl done
+ dnl fi
+ dnl
+ dnl if test -z "$SYMFONY_DEBUG_DIR"; then
+ dnl AC_MSG_RESULT([not found])
+ dnl AC_MSG_ERROR([Please reinstall the symfony_debug distribution])
+ dnl fi
+
+ dnl # --with-symfony_debug -> add include path
+ dnl PHP_ADD_INCLUDE($SYMFONY_DEBUG_DIR/include)
+
+ dnl # --with-symfony_debug -> check for lib and symbol presence
+ dnl LIBNAME=symfony_debug # you may want to change this
+ dnl LIBSYMBOL=symfony_debug # you most likely want to change this
+
+ dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
+ dnl [
+ dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SYMFONY_DEBUG_DIR/lib, SYMFONY_DEBUG_SHARED_LIBADD)
+ dnl AC_DEFINE(HAVE_SYMFONY_DEBUGLIB,1,[ ])
+ dnl ],[
+ dnl AC_MSG_ERROR([wrong symfony_debug lib version or lib not found])
+ dnl ],[
+ dnl -L$SYMFONY_DEBUG_DIR/lib -lm
+ dnl ])
+ dnl
+ dnl PHP_SUBST(SYMFONY_DEBUG_SHARED_LIBADD)
+
+ PHP_NEW_EXTENSION(symfony_debug, symfony_debug.c, $ext_shared)
+fi
View
13 src/Symfony/Component/Debug/Resources/ext/config.w32
@@ -0,0 +1,13 @@
+// $Id$
+// vim:ft=javascript
+
+// If your extension references something external, use ARG_WITH
+// ARG_WITH("symfony_debug", "for symfony_debug support", "no");
+
+// Otherwise, use ARG_ENABLE
+// ARG_ENABLE("symfony_debug", "enable symfony_debug support", "no");
+
+if (PHP_SYMFONY_DEBUG != "no") {
+ EXTENSION("symfony_debug", "symfony_debug.c");
+}
+
View
55 src/Symfony/Component/Debug/Resources/ext/php_symfony_debug.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#ifndef PHP_SYMFONY_DEBUG_H
+#define PHP_SYMFONY_DEBUG_H
+
+extern zend_module_entry symfony_debug_module_entry;
+#define phpext_symfony_debug_ptr &symfony_debug_module_entry
+
+#define PHP_SYMFONY_DEBUG_VERSION "1.0"
+
+#ifdef PHP_WIN32
+# define PHP_SYMFONY_DEBUG_API __declspec(dllexport)
+#elif defined(__GNUC__) && __GNUC__ >= 4
+# define PHP_SYMFONY_DEBUG_API __attribute__ ((visibility("default")))
+#else
+# define PHP_SYMFONY_DEBUG_API
+#endif
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+ZEND_BEGIN_MODULE_GLOBALS(symfony_debug)
+ intptr_t req_rand_init;
+ZEND_END_MODULE_GLOBALS(symfony_debug)
+
+PHP_MINIT_FUNCTION(symfony_debug);
+PHP_MSHUTDOWN_FUNCTION(symfony_debug);
+PHP_RINIT_FUNCTION(symfony_debug);
+PHP_RSHUTDOWN_FUNCTION(symfony_debug);
+PHP_MINFO_FUNCTION(symfony_debug);
+PHP_GINIT_FUNCTION(symfony_debug);
+PHP_GSHUTDOWN_FUNCTION(symfony_debug);
+
+PHP_FUNCTION(symfony_zval_info);
+
+static char *_symfony_debug_memory_address_hash(void *);
+static const char *_symfony_debug_zval_type(zval *);
+static const char* _symfony_debug_get_resource_type(long);
+static int _symfony_debug_get_resource_refcount(long);
+
+#ifdef ZTS
+#define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v)
+#else
+#define SYMFONY_DEBUG_G(v) (symfony_debug_globals.v)
+#endif
+
+#endif /* PHP_SYMFONY_DEBUG_H */
View
223 src/Symfony/Component/Debug/Resources/ext/symfony_debug.c
@@ -0,0 +1,223 @@
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_symfony_debug.h"
+#include "ext/standard/php_rand.h"
+#include "ext/standard/php_lcg.h"
+#include "ext/spl/php_spl.h"
+#include "Zend/zend_gc.h"
+
+ZEND_DECLARE_MODULE_GLOBALS(symfony_debug)
+
+ZEND_BEGIN_ARG_INFO_EX(symfony_zval_arginfo, 0, 0, 2)
+ ZEND_ARG_INFO(0, key)
+ ZEND_ARG_ARRAY_INFO(0, array, 0)
+ ZEND_ARG_INFO(0, options)
+ZEND_END_ARG_INFO()
+
+const zend_function_entry symfony_debug_functions[] = {
+ PHP_FE(symfony_zval_info, symfony_zval_arginfo)
+ PHP_FE_END
+};
+
+PHP_FUNCTION(symfony_zval_info)
+{
+ zval *key = NULL, *arg = NULL;
+ zval **data = NULL;
+ HashTable *array = NULL;
+ long options = 0;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "zh|l", &key, &array, &options) == FAILURE) {
+ return;
+ }
+
+ switch (Z_TYPE_P(key)) {
+ case IS_STRING:
+ if (zend_symtable_find(array, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&data) == FAILURE) {
+ return;
+ }
+ break;
+ case IS_LONG:
+ if (zend_hash_index_find(array, Z_LVAL_P(key), (void **)&data)) {
+ return;
+ }
+ break;
+ }
+
+ arg = *data;
+
+ array_init(return_value);
+
+ add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1);
+ add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg), 16, 1);
+ add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg));
+ add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg));
+
+ if (Z_TYPE_P(arg) == IS_OBJECT) {
+ static char hash[33] = {0};
+ php_spl_object_hash(arg, (char *)hash);
+ add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1);
+ add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount);
+ add_assoc_string(return_value, "object_hash", hash, 1);
+ } else if (Z_TYPE_P(arg) == IS_ARRAY) {
+ add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg)));
+ } else if(Z_TYPE_P(arg) == IS_RESOURCE) {
+ add_assoc_long(return_value, "resource_id", Z_LVAL_P(arg));
+ add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg)), 1);
+ add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg)));
+ } else if (Z_TYPE_P(arg) == IS_STRING) {
+ add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg));
+ }
+}
+
+static const char* _symfony_debug_get_resource_type(long rsid)
+{
+ const char *res_type;
+ res_type = zend_rsrc_list_get_rsrc_type(rsid);
+
+ if (!res_type) {
+ return "Unknown";
+ }
+
+ return res_type;
+}
+
+static int _symfony_debug_get_resource_refcount(long rsid)
+{
+ zend_rsrc_list_entry *le;
+
+ if (zend_hash_index_find(&EG(regular_list), rsid, (void **) &le)==SUCCESS) {
+ return le->refcount;
+ }
+
+ return 0;
+}
+
+static char *_symfony_debug_memory_address_hash(void *address)
+{
+ static char result[17] = {0};
+ intptr_t address_rand;
+
+ if (!SYMFONY_DEBUG_G(req_rand_init)) {
+ if (!BG(mt_rand_is_seeded)) {
+ php_mt_srand(GENERATE_SEED() TSRMLS_CC);
+ }
+ SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand();
+ }
+
+ address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init);
+
+ snprintf(result, 17, "%016zx", address_rand);
+
+ return result;
+}
+
+static const char *_symfony_debug_zval_type(zval *zv)
+{
+ switch (Z_TYPE_P(zv)) {
+ case IS_NULL:
+ return "NULL";
+ break;
+
+ case IS_BOOL:
+ return "boolean";
+ break;
+
+ case IS_LONG:
+ return "integer";
+ break;
+
+ case IS_DOUBLE:
+ return "double";
+ break;
+
+ case IS_STRING:
+ return "string";
+ break;
+
+ case IS_ARRAY:
+ return "array";
+ break;
+
+ case IS_OBJECT:
+ return "object";
+
+ case IS_RESOURCE:
+ return "resource";
+
+ default:
+ return "unknown type";
+ }
+}
+
+zend_module_entry symfony_debug_module_entry = {
+ STANDARD_MODULE_HEADER,
+ "symfony_debug",
+ symfony_debug_functions,
+ PHP_MINIT(symfony_debug),
+ PHP_MSHUTDOWN(symfony_debug),
+ PHP_RINIT(symfony_debug),
+ PHP_RSHUTDOWN(symfony_debug),
+ PHP_MINFO(symfony_debug),
+ PHP_SYMFONY_DEBUG_VERSION,
+ PHP_MODULE_GLOBALS(symfony_debug),
+ PHP_GINIT(symfony_debug),
+ PHP_GSHUTDOWN(symfony_debug),
+ NULL,
+ STANDARD_MODULE_PROPERTIES_EX
+};
+
+#ifdef COMPILE_DL_SYMFONY_DEBUG
+ZEND_GET_MODULE(symfony_debug)
+#endif
+
+PHP_GINIT_FUNCTION(symfony_debug)
+{
+ symfony_debug_globals->req_rand_init = 0;
+}
+
+PHP_GSHUTDOWN_FUNCTION(symfony_debug)
+{
+
+}
+
+PHP_MINIT_FUNCTION(symfony_debug)
+{
+ return SUCCESS;
+}
+
+PHP_MSHUTDOWN_FUNCTION(symfony_debug)
+{
+ return SUCCESS;
+}
+
+PHP_RINIT_FUNCTION(symfony_debug)
+{
+ return SUCCESS;
+}
+
+PHP_RSHUTDOWN_FUNCTION(symfony_debug)
+{
+ return SUCCESS;
+}
+
+PHP_MINFO_FUNCTION(symfony_debug)
+{
+ php_info_print_table_start();
+ php_info_print_table_header(2, "Symfony Debug support", "enabled");
+ php_info_print_table_header(2, "Symfony Debug version", PHP_SYMFONY_DEBUG_VERSION);
+ php_info_print_table_end();
+}
View
149 src/Symfony/Component/Debug/Resources/ext/tests/001.phpt
@@ -0,0 +1,149 @@
+--TEST--
+Test symfony_zval_info API
+--SKIPIF--
+<?php if (!extension_loaded("symfony_debug")) print "skip"; ?>
+--FILE--
+<?php
+
+$int = 42;
+$float = 42.42;
+$str = "foobar";
+$object = new StdClass;
+$array = array('foo', 'bar');
+$resource = tmpfile();
+$null = null;
+$bool = true;
+
+$anotherint = 42;
+$refcount2 = &$anotherint;
+
+$var = array('int' => $int,
+ 'float' => $float,
+ 'str' => $str,
+ 'object' => $object,
+ 'array' => $array,
+ 'resource' => $resource,
+ 'null' => $null,
+ 'bool' => $bool,
+ 'refcount' => &$refcount2);
+
+var_dump(symfony_zval_info('int', $var));
+var_dump(symfony_zval_info('float', $var));
+var_dump(symfony_zval_info('str', $var));
+var_dump(symfony_zval_info('object', $var));
+var_dump(symfony_zval_info('array', $var));
+var_dump(symfony_zval_info('resource', $var));
+var_dump(symfony_zval_info('null', $var));
+var_dump(symfony_zval_info('bool', $var));
+
+var_dump(symfony_zval_info('refcount', $var));
+var_dump(symfony_zval_info('not-exist', $var));
+?>
+--EXPECTF--
+array(4) {
+ ["type"]=>
+ string(7) "integer"
+ ["zval_hash"]=>
+ string(16) "%s"
+ ["zval_refcount"]=>
+ int(2)
+ ["zval_isref"]=>
+ bool(false)
+}
+array(4) {
+ ["type"]=>
+ string(6) "double"
+ ["zval_hash"]=>
+ string(16) "%s"
+ ["zval_refcount"]=>
+ int(2)
+ ["zval_isref"]=>
+ bool(false)
+}
+array(5) {
+ ["type"]=>
+ string(6) "string"
+ ["zval_hash"]=>
+ string(16) "%s"
+ ["zval_refcount"]=>
+ int(2)
+ ["zval_isref"]=>
+ bool(false)
+ ["strlen"]=>
+ int(6)
+}
+array(7) {
+ ["type"]=>
+ string(6) "object"
+ ["zval_hash"]=>
+ string(16) "%s"
+ ["zval_refcount"]=>
+ int(2)
+ ["zval_isref"]=>
+ bool(false)
+ ["object_class"]=>
+ string(8) "stdClass"
+ ["object_refcount"]=>
+ int(1)
+ ["object_hash"]=>
+ string(32) "%s"
+}
+array(5) {
+ ["type"]=>
+ string(5) "array"
+ ["zval_hash"]=>
+ string(16) "%s"
+ ["zval_refcount"]=>
+ int(2)
+ ["zval_isref"]=>
+ bool(false)
+ ["array_count"]=>
+ int(2)
+}
+array(7) {
+ ["type"]=>
+ string(8) "resource"
+ ["zval_hash"]=>
+ string(16) "%s"
+ ["zval_refcount"]=>
+ int(2)
+ ["zval_isref"]=>
+ bool(false)
+ ["resource_id"]=>
+ int(4)
+ ["resource_type"]=>
+ string(6) "stream"
+ ["resource_refcount"]=>
+ int(1)
+}
+array(4) {
+ ["type"]=>
+ string(4) "NULL"
+ ["zval_hash"]=>
+ string(16) "%s"
+ ["zval_refcount"]=>
+ int(2)
+ ["zval_isref"]=>
+ bool(false)
+}
+array(4) {
+ ["type"]=>
+ string(7) "boolean"
+ ["zval_hash"]=>
+ string(16) "%s"
+ ["zval_refcount"]=>
+ int(2)
+ ["zval_isref"]=>
+ bool(false)
+}
+array(4) {
+ ["type"]=>
+ string(7) "integer"
+ ["zval_hash"]=>
+ string(16) "%s"
+ ["zval_refcount"]=>
+ int(3)
+ ["zval_isref"]=>
+ bool(true)
+}
+NULL
Something went wrong with that request. Please try again.