diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 45c2ebdf70a69..9ad8e2566c79d 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -299,6 +299,19 @@ struct _zend_ast_ref { zend_ast *ast; }; +/* primitive data type names */ +#define TYPE_ARRAY "array" +#define TYPE_BOOL "bool" +#define TYPE_CALLABLE "callable" +#define TYPE_FALSE "false" +#define TYPE_FLOAT "float" +#define TYPE_INT "int" +#define TYPE_NULL "null" +#define TYPE_OBJECT "object" +#define TYPE_RESOURCE "resource" +#define TYPE_STRING "string" +#define TYPE_TRUE "true" + /* regular data types */ #define IS_UNDEF 0 #define IS_NULL 1 diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index f887903f268e9..502d6c3693b5a 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -35,6 +35,7 @@ #include "zend_operators.h" #include "ext/standard/php_dns.h" #include "ext/standard/php_uuencode.h" +#include "php_type.h" #ifdef PHP_WIN32 #include "win32/php_win32_globals.h" @@ -2563,6 +2564,11 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_is_callable, 0, 0, 1) ZEND_ARG_INFO(0, syntax_only) ZEND_ARG_INFO(1, callable_name) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_typeof, 0, ZEND_RETURN_VALUE, 1) + ZEND_ARG_INFO(0, var) + ZEND_ARG_INFO(0, extended) +ZEND_END_ARG_INFO() /* }}} */ /* {{{ uniqid.c */ #ifdef HAVE_GETTIMEOFDAY @@ -3060,6 +3066,7 @@ const zend_function_entry basic_functions[] = { /* {{{ */ PHP_FE(is_object, arginfo_is_object) PHP_FE(is_scalar, arginfo_is_scalar) PHP_FE(is_callable, arginfo_is_callable) + PHP_FE(typeof, arginfo_typeof) /* functions from file.c */ PHP_FE(pclose, arginfo_pclose) @@ -3620,6 +3627,7 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */ register_phpinfo_constants(INIT_FUNC_ARGS_PASSTHRU); register_html_constants(INIT_FUNC_ARGS_PASSTHRU); register_string_constants(INIT_FUNC_ARGS_PASSTHRU); + register_type_constants(INIT_FUNC_ARGS_PASSTHRU); BASIC_ADD_SUBMODULE(dl) BASIC_ADD_SUBMODULE(mail) @@ -3998,7 +4006,7 @@ PHP_FUNCTION(long2ip) ********************/ /* {{{ proto string getenv([string varname]) - Get the value of an environment variable or every available environment variable + Get the value of an environment variable or every available environment variable if no varname is present */ PHP_FUNCTION(getenv) { diff --git a/ext/standard/php_type.h b/ext/standard/php_type.h index e9a315557216f..740c1197ad938 100644 --- a/ext/standard/php_type.h +++ b/ext/standard/php_type.h @@ -38,5 +38,8 @@ PHP_FUNCTION(is_array); PHP_FUNCTION(is_object); PHP_FUNCTION(is_scalar); PHP_FUNCTION(is_callable); +PHP_FUNCTION(typeof); -#endif +void register_type_constants(INIT_FUNC_ARGS); + +#endif /* PHP_TYPE_H */ diff --git a/ext/standard/tests/type/type_basic.phpt b/ext/standard/tests/type/type_basic.phpt new file mode 100644 index 0000000000000..d00b672b714d4 --- /dev/null +++ b/ext/standard/tests/type/type_basic.phpt @@ -0,0 +1,38 @@ +--TEST-- +TYPE_* constants are defined with their correct value +--DESCRIPTION-- +This test ensures that users of PHP can rely on the definition of these +constants as well as their actual values and are meant to prevent any kind of +breaking changes in relation to these constants. +--FILE-- + 'array', + 'TYPE_BOOL' => 'bool', + 'TYPE_CALLABLE' => 'callable', + 'TYPE_FLOAT' => 'float', + 'TYPE_INT' => 'int', + 'TYPE_NULL' => 'null', + 'TYPE_OBJECT' => 'object', + 'TYPE_RESOURCE' => 'resource', + 'TYPE_STRING' => 'string', +]; + +foreach ($data as $name => $value) { + if (defined($name) && constant($name) === $value) { + printf("%-13s := %s\n", $name, $value); + } +} + +?> +--EXPECT-- +TYPE_ARRAY := array +TYPE_BOOL := bool +TYPE_CALLABLE := callable +TYPE_FLOAT := float +TYPE_INT := int +TYPE_NULL := null +TYPE_OBJECT := object +TYPE_RESOURCE := resource +TYPE_STRING := string diff --git a/ext/standard/tests/type/typeof_TestClass.php b/ext/standard/tests/type/typeof_TestClass.php new file mode 100644 index 0000000000000..f8f29e98f39ea --- /dev/null +++ b/ext/standard/tests/type/typeof_TestClass.php @@ -0,0 +1,11 @@ + 'i', 'c' => 't'], + [TestClass::class, 'staticMethod'], + [TestClass::class, 'instanceMethod'], + [new TestClass, 'staticMethod'], + [new TestClass, 'instanceMethod'], +]; + +echo "\n*** typeof test: array ***\n"; +foreach ($data as $datum) { + var_dump(typeof($datum)); +} + +echo "\n*** typeof test extended: array ***\n"; +foreach ($data as $datum) { + var_dump(typeof($datum, true)); +} + +?> +--EXPECTF-- + +*** typeof test: array *** +string(5) "array" +string(5) "array" +string(5) "array" +string(5) "array" +string(5) "array" +string(5) "array" +string(5) "array" +string(5) "array" + +*** typeof test extended: array *** +string(5) "array" +string(5) "array" +string(5) "array" +string(5) "array" +string(14) "callable array" + +Deprecated: Non-static method Php\Test\Typeof\TestClass::instanceMethod() should not be called statically in %s on line %d +string(14) "callable array" +string(14) "callable array" +string(14) "callable array" diff --git a/ext/standard/tests/type/typeof_basic_bool.phpt b/ext/standard/tests/type/typeof_basic_bool.phpt new file mode 100644 index 0000000000000..1b7bf24eb55dc --- /dev/null +++ b/ext/standard/tests/type/typeof_basic_bool.phpt @@ -0,0 +1,36 @@ +--TEST-- +typeof() correctly recognizes bool vars +--FILE-- + +--EXPECTF-- + +*** typeof test: bool *** +string(4) "bool" +string(4) "bool" +string(4) "bool" +string(4) "bool" + +*** typeof test extended: bool *** +string(10) "bool false" +string(10) "bool false" +string(9) "bool true" +string(9) "bool true" diff --git a/ext/standard/tests/type/typeof_basic_float.phpt b/ext/standard/tests/type/typeof_basic_float.phpt new file mode 100644 index 0000000000000..0bbe77b6eb2b3 --- /dev/null +++ b/ext/standard/tests/type/typeof_basic_float.phpt @@ -0,0 +1,102 @@ +--TEST-- +typeof() correctly recognizes float vars +--FILE-- + +--EXPECTF-- + +*** typeof test: float *** +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" +string(5) "float" + +*** typeof test extended: float *** +string(13) "invalid float" +string(14) "infinite float" +string(14) "negative float" +string(14) "negative float" +string(14) "negative float" +string(14) "negative float" +string(14) "negative float" +string(14) "negative float" +string(14) "negative float" +string(14) "negative float" +string(14) "negative float" +string(14) "negative float" +string(10) "zero float" +string(10) "zero float" +string(14) "positive float" +string(14) "positive float" +string(14) "positive float" +string(14) "positive float" +string(14) "positive float" +string(14) "positive float" +string(14) "positive float" +string(14) "positive float" +string(14) "positive float" +string(14) "positive float" +string(14) "infinite float" +string(13) "invalid float" diff --git a/ext/standard/tests/type/typeof_basic_int.phpt b/ext/standard/tests/type/typeof_basic_int.phpt new file mode 100644 index 0000000000000..239caefa967ac --- /dev/null +++ b/ext/standard/tests/type/typeof_basic_int.phpt @@ -0,0 +1,60 @@ +--TEST-- +typeof() correctly recognizes int vars +--FILE-- + +--EXPECTF-- + +*** typeof test: int *** +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" + +*** typeof test extended: int *** +string(12) "negative int" +string(12) "negative int" +string(12) "negative int" +string(12) "negative int" +string(12) "negative int" +string(8) "zero int" +string(8) "zero int" +string(12) "positive int" +string(12) "positive int" +string(12) "positive int" +string(12) "positive int" +string(12) "positive int" diff --git a/ext/standard/tests/type/typeof_basic_null.phpt b/ext/standard/tests/type/typeof_basic_null.phpt new file mode 100644 index 0000000000000..0199f9e84f5c6 --- /dev/null +++ b/ext/standard/tests/type/typeof_basic_null.phpt @@ -0,0 +1,42 @@ +--TEST-- +typeof() correctly recognizes null vars +--FILE-- + +--EXPECTF-- + +*** typeof test: null *** +string(4) "null" +string(4) "null" + +Notice: Undefined variable: undefined_variable_1 in %s on line %d +string(4) "null" + +Notice: Undefined variable: unset in %s on line %d +string(4) "null" + +*** typeof test extended: null *** +string(4) "null" +string(4) "null" + +Notice: Undefined variable: undefined_variable_2 in %s on line %d +string(4) "null" + +Notice: Undefined variable: unset in %s on line %d +string(4) "null" diff --git a/ext/standard/tests/type/typeof_basic_object.phpt b/ext/standard/tests/type/typeof_basic_object.phpt new file mode 100644 index 0000000000000..119f96c18b78e --- /dev/null +++ b/ext/standard/tests/type/typeof_basic_object.phpt @@ -0,0 +1,40 @@ +--TEST-- +typeof() correctly recognizes object vars +--FILE-- + +--EXPECTF-- + +*** typeof test: object *** +string(6) "object" +string(6) "object" +string(6) "object" +string(6) "object" + +*** typeof test extended: object *** +string(24) "object of class stdClass" +string(41) "object of class Php\Test\Typeof\TestClass" +string(23) "object of class Closure" +string(38) "object of class __PHP_Incomplete_Class" diff --git a/ext/standard/tests/type/typeof_basic_resource.phpt b/ext/standard/tests/type/typeof_basic_resource.phpt new file mode 100644 index 0000000000000..797a06dc73caa --- /dev/null +++ b/ext/standard/tests/type/typeof_basic_resource.phpt @@ -0,0 +1,39 @@ +--TEST-- +typeof() correctly recognizes resource vars +--FILE-- + +--EXPECTF-- + +*** typeof test: resource *** +string(8) "resource" +string(8) "resource" +string(8) "resource" +string(8) "resource" + +*** typeof test extended: resource *** +string(23) "resource of type stream" +string(23) "resource of type stream" +string(23) "resource of type stream" +string(15) "closed resource" diff --git a/ext/standard/tests/type/typeof_basic_string.phpt b/ext/standard/tests/type/typeof_basic_string.phpt new file mode 100644 index 0000000000000..7104fab53c581 --- /dev/null +++ b/ext/standard/tests/type/typeof_basic_string.phpt @@ -0,0 +1,55 @@ +--TEST-- +typeof() correctly recognizes string vars +--FILE-- + +--EXPECTF-- + +*** typeof test: string *** +string(6) "string" +string(6) "string" +string(6) "string" +string(6) "string" +string(6) "string" +string(6) "string" +string(6) "string" +string(6) "string" +string(6) "string" + +*** typeof test extended: string *** +string(6) "string" +string(6) "string" +string(14) "numeric string" +string(14) "numeric string" +string(6) "string" +string(6) "string" +string(15) "callable string" +string(15) "callable string" + +Deprecated: Non-static method Php\Test\Typeof\TestClass::instanceMethod() should not be called statically in %s on line %d +string(15) "callable string" diff --git a/ext/standard/tests/type/typeof_error.phpt b/ext/standard/tests/type/typeof_error.phpt new file mode 100644 index 0000000000000..34bd3aeb2a6c0 --- /dev/null +++ b/ext/standard/tests/type/typeof_error.phpt @@ -0,0 +1,21 @@ +--TEST-- +typeof() argument definition and parsing +--FILE-- + +--EXPECTF-- +Warning: typeof() expects at least 1 parameter, 0 given in %s on line %d +NULL + +Warning: typeof() expects at most 2 parameters, 3 given in %s on line %d +NULL + +Warning: typeof() expects parameter 2 to be boolean, array given in %s on line %d +NULL diff --git a/ext/standard/type.c b/ext/standard/type.c index 529d666d02db5..831282ece3ebd 100644 --- a/ext/standard/type.c +++ b/ext/standard/type.c @@ -21,6 +21,22 @@ #include "php.h" #include "php_incomplete_class.h" +/* {{{ register_type_constants + */ +void register_type_constants(INIT_FUNC_ARGS) +{ + REGISTER_STRING_CONSTANT("TYPE_ARRAY", TYPE_ARRAY, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("TYPE_BOOL", TYPE_BOOL, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("TYPE_CALLABLE", TYPE_CALLABLE, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("TYPE_FLOAT", TYPE_FLOAT, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("TYPE_INT", TYPE_INT, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("TYPE_NULL", TYPE_NULL, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("TYPE_OBJECT", TYPE_OBJECT, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("TYPE_RESOURCE", TYPE_RESOURCE, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("TYPE_STRING", TYPE_STRING, CONST_CS | CONST_PERSISTENT); +} +/* }}} */ + /* {{{ proto string gettype(mixed var) Returns the type of the variable */ PHP_FUNCTION(gettype) @@ -434,6 +450,135 @@ PHP_FUNCTION(is_callable) } /* }}} */ +#define RETURN_TYPE_NAME(t) { RETURN_STRINGL(t, sizeof(t) - 1) } + +/* {{{ proto string typeof(mixed var[, bool extended]) + * Get the type of a variable with (optional) extended information. */ +PHP_FUNCTION(typeof) +{ + zval *var; + zend_bool extended = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|b", &var, &extended) == FAILURE) { + return; + } + + switch (Z_TYPE_P(var)) { + case IS_ARRAY: + if (extended && zend_is_callable(var, IS_CALLABLE_CHECK_SILENT, NULL)) { + RETURN_TYPE_NAME(TYPE_CALLABLE " " TYPE_ARRAY); + } + RETURN_TYPE_NAME(TYPE_ARRAY); + + case IS_FALSE: + case IS_TRUE: + if (extended) { + if (Z_TYPE_P(var) == IS_FALSE) { + RETURN_TYPE_NAME(TYPE_BOOL " " TYPE_FALSE); + } + + if (Z_TYPE_P(var) == IS_TRUE) { + RETURN_TYPE_NAME(TYPE_BOOL " " TYPE_TRUE); + } + } + RETURN_TYPE_NAME(TYPE_BOOL); + + /* case IS_FLOAT: */ + case IS_DOUBLE: + if (extended) { + if (zend_isinf(Z_DVAL_P(var))) { + RETURN_TYPE_NAME("infinite " TYPE_FLOAT); + } + + if (zend_isnan(Z_DVAL_P(var))) { + RETURN_TYPE_NAME("invalid " TYPE_FLOAT); + } + + if (Z_DVAL_P(var) > 0.0) { + RETURN_TYPE_NAME("positive " TYPE_FLOAT); + } + + if (Z_DVAL_P(var) < -0.0) { + RETURN_TYPE_NAME("negative " TYPE_FLOAT); + } + + RETURN_TYPE_NAME("zero " TYPE_FLOAT); + } + RETURN_TYPE_NAME(TYPE_FLOAT); + + /* case IS_INT: */ + case IS_LONG: + if (extended) { + if (Z_LVAL_P(var) > 0) { + RETURN_TYPE_NAME("positive " TYPE_INT); + } + + if (Z_LVAL_P(var) < -0) { + RETURN_TYPE_NAME("negative " TYPE_INT); + } + + RETURN_TYPE_NAME("zero " TYPE_INT); + } + RETURN_TYPE_NAME(TYPE_INT); + + case IS_NULL: + RETURN_TYPE_NAME(TYPE_NULL); + + case IS_OBJECT: + if (extended) { + const size_t prefix_len = sizeof(TYPE_OBJECT " of class ") - 1; + const size_t type_len = prefix_len + ZSTR_LEN(Z_OBJCE_P(var)->name); + + char *type = emalloc(type_len); + memcpy(type, TYPE_OBJECT " of class ", prefix_len); + memcpy(type + prefix_len, ZSTR_VAL(Z_OBJCE_P(var)->name), ZSTR_LEN(Z_OBJCE_P(var)->name)); + + RETVAL_STRINGL(type, type_len); + efree(type); + return; + } + RETURN_TYPE_NAME(TYPE_OBJECT); + + case IS_RESOURCE: + if (extended) { + const char *rsrc_type = zend_rsrc_list_get_rsrc_type(Z_RES_P(var)); + + if (rsrc_type) { + const size_t prefix_len = sizeof(TYPE_RESOURCE " of type ") - 1; + const size_t rsrc_type_len = strlen(rsrc_type); + const size_t type_len = prefix_len + rsrc_type_len; + + char *type = emalloc(type_len); + memcpy(type, TYPE_RESOURCE " of type ", prefix_len); + memcpy(type + prefix_len, rsrc_type, rsrc_type_len); + + RETVAL_STRINGL(type, type_len); + efree(type); + return; + } + + RETURN_TYPE_NAME("closed " TYPE_RESOURCE); + } + RETURN_TYPE_NAME(TYPE_RESOURCE); + + case IS_STRING: + if (extended) { + if (is_numeric_string(Z_STRVAL_P(var), Z_STRLEN_P(var), NULL, NULL, 0)) { + RETURN_TYPE_NAME("numeric " TYPE_STRING); + } + + if (zend_is_callable(var, IS_CALLABLE_CHECK_SILENT, NULL)) { + RETURN_TYPE_NAME("callable " TYPE_STRING); + } + } + RETURN_TYPE_NAME(TYPE_STRING); + } + + /* (should be) unreachable in userland */ + RETURN_TYPE_NAME("unknown"); +} +/* }}} */ + /* * Local variables: * tab-width: 4