├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── LICENSE ├── README.md ├── aspect.c ├── aspect.stub.php ├── aspect_arginfo.h ├── composer.json ├── config.m4 ├── config.w32 ├── php_aspect.h └── tests ├── memoize_001.phpt ├── memoize_002.phpt ├── memoize_003.phpt ├── memoize_004.phpt ├── memoize_005.phpt ├── memoize_006.phpt ├── memoize_007.phpt ├── memoize_008.phpt ├── memoize_009.phpt └── memoize_010.phpt /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | composer-validate: 9 | name: "Validate using Composer" 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Setup PHP 14 | uses: shivammathur/setup-php@v2 15 | with: 16 | php-version: 8.3 17 | tools: composer:snapshot 18 | - name: Validate 19 | run: composer validate 20 | windows-get-extension-matrix: 21 | runs-on: ubuntu-latest 22 | outputs: 23 | matrix: ${{ steps.extension-matrix.outputs.matrix }} 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | - name: Get the extension matrix 28 | id: extension-matrix 29 | uses: php/php-windows-builder/extension-matrix@v1 30 | with: 31 | php-version-list: '8.0, 8.1, 8.2, 8.3, 8.4' 32 | windows-phpt-tests: 33 | needs: windows-get-extension-matrix 34 | name: "Windows PHPT Tests" 35 | runs-on: ${{ matrix.os }} 36 | strategy: 37 | matrix: ${{fromJson(needs.windows-get-extension-matrix.outputs.matrix)}} 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | - name: Build and test the extension 42 | uses: php/php-windows-builder/extension@v1 43 | with: 44 | php-version: ${{ matrix.php-version }} 45 | ts: ${{ matrix.ts }} 46 | arch: ${{ matrix.arch }} 47 | run-tests: true 48 | linux-phpt-tests: 49 | name: "Linux PHPT Tests" 50 | runs-on: ${{ matrix.os }} 51 | strategy: 52 | fail-fast: true 53 | matrix: 54 | os: 55 | - ubuntu-22.04 56 | php-version: 57 | - 8.0 58 | - 8.1 59 | - 8.2 60 | - 8.3 61 | - 8.4 62 | thread-safety: 63 | - zts 64 | - nts 65 | steps: 66 | - uses: actions/checkout@v4 67 | - name: Setup PHP ${{ matrix.php-version }} ${{ matrix.thread-safety }} 68 | uses: shivammathur/setup-php@v2 69 | with: 70 | php-version: ${{ matrix.php-version }} 71 | env: 72 | phpts: ${{ matrix.thread-safety }} 73 | fail-fast: true 74 | - name: Build 75 | run: | 76 | phpize 77 | make 78 | - name: Test 79 | run: make test 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dep 2 | *.lo 3 | *.la 4 | .libs 5 | acinclude.m4 6 | aclocal.m4 7 | autom4te.cache 8 | build 9 | config.cache 10 | config.guess 11 | config.h 12 | config.h.in 13 | config.log 14 | config.nice 15 | config.nice.bat 16 | config.status 17 | config.sub 18 | configure 19 | configure.ac 20 | configure.bat 21 | configure.in 22 | configure.js 23 | include 24 | install-sh 25 | libtool 26 | ltmain.sh 27 | Makefile 28 | Makefile.fragments 29 | Makefile.global 30 | Makefile.objects 31 | missing 32 | mkinstalldirs 33 | modules 34 | php_test_results_*.txt 35 | phpt.* 36 | run-test-info.php 37 | run-tests.php 38 | tests/**/*.diff 39 | tests/**/*.out 40 | tests/**/*.php 41 | tests/**/*.exp 42 | tests/**/*.log 43 | tests/**/*.sh 44 | tests/**/*.db 45 | tests/**/*.mem 46 | tmp-php.ini 47 | *~ 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Pierre du Plessis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aspect PHP Extension 2 | 3 | Aspect is a PHP extension that provides a collection of utilities designed to enhance your development workflow. By introducing advanced features like attribute-based memoization, Aspect allows developers to write more efficient, clean, and maintainable code without the overhead of implementing complex patterns manually. 4 | 5 | ## Table of Contents 6 | 7 | - [Installation](#installation) 8 | - [Features](#features) 9 | - [Memoization with Attributes](#memoization-with-attributes) 10 | - [Usage](#usage) 11 | - [Examples](#examples) 12 | - [Benefits](#benefits) 13 | - [License](#license) 14 | 15 | ## Installation 16 | 17 | To install the Aspect extension, you can use [Pie](https://github.com/php/pie): 18 | 19 | ```bash 20 | pie install solidworx/aspect 21 | ``` 22 | 23 | ## Features 24 | 25 | ### Memoization with Attributes 26 | 27 | The `#[Memoize]` attribute enables you to effortlessly cache the results of function or method calls based on their input arguments. This means that repeated calls with the same parameters will return the cached result instantly, without re-executing the underlying code. 28 | 29 | #### Usage 30 | 31 | To use the memoization feature, simply add the `#[Memoize]` attribute above the function or method you wish to cache. 32 | 33 | ```php 34 | 'Sample data from ' . $url]; 84 | } 85 | } 86 | 87 | $fetcher = new DataFetcher(); 88 | $data = $fetcher->fetchData('https://api.example.com'); // Takes 3 seconds 89 | $data = $fetcher->fetchData('https://api.example.com'); // Returns instantly from cache 90 | ``` 91 | 92 | In this example, the `fetchData` method will only perform the API call once per unique URL. 93 | 94 | **Example 3: Different Arguments Cache Separately** 95 | 96 | ```php 97 | 6 | # include 7 | 8 | #endif 9 | 10 | #include "php.h" 11 | #include "ext/standard/info.h" 12 | #include "ext/standard/php_standard.h" 13 | #include "php_aspect.h" 14 | #include "aspect_arginfo.h" 15 | #include "zend_attributes.h" 16 | #include "zend_exceptions.h" 17 | 18 | /* For compatibility with older PHP versions */ 19 | #ifndef ZEND_PARSE_PARAMETERS_NONE 20 | #define ZEND_PARSE_PARAMETERS_NONE() \ 21 | ZEND_PARSE_PARAMETERS_START(0, 0) \ 22 | ZEND_PARSE_PARAMETERS_END() 23 | #endif 24 | 25 | ZEND_API zend_class_entry *zend_ce_memoize; 26 | 27 | ZEND_DECLARE_MODULE_GLOBALS(aspect) 28 | 29 | static void (*original_zend_execute_ex)(zend_execute_data *execute_data); 30 | 31 | static void aspect_execute_ex(zend_execute_data *execute_data); 32 | 33 | static void handle_memoize_functions(zend_execute_data *execute_data); 34 | 35 | static zend_string *compute_cache_key(zend_execute_data *execute_data) { 36 | smart_str key = {0}; 37 | zend_function *func = execute_data->func; 38 | 39 | // Check if it's a method call 40 | if (func->common.scope) { 41 | // Check if it's a static method call 42 | if (func->common.fn_flags & ZEND_ACC_STATIC) { 43 | // Append fully qualified class name 44 | smart_str_appends(&key, ZSTR_VAL(func->common.scope->name)); 45 | } else { 46 | // Append object handle 47 | smart_str_append_long(&key, (long) Z_OBJ_HANDLE(execute_data->This)); 48 | } 49 | // Append method name 50 | smart_str_appends(&key, "::"); 51 | smart_str_appends(&key, ZSTR_VAL(func->common.function_name)); 52 | } else { 53 | // Append function name 54 | smart_str_appends(&key, ZSTR_VAL(func->common.function_name)); 55 | } 56 | 57 | // Serialize arguments 58 | zval *params = ZEND_CALL_ARG(execute_data, 1); 59 | for (uint32_t i = 0; i < ZEND_CALL_NUM_ARGS(execute_data); i++) { 60 | switch (Z_TYPE(params[i])) { 61 | case IS_OBJECT: { 62 | // Use object handle (unique for each object instance) 63 | smart_str_append_long(&key, (long) Z_OBJ_HANDLE(params[i])); 64 | break; 65 | } 66 | case IS_CALLABLE: { 67 | // Serialize callable as string 68 | smart_str_appendc(&key, ':'); 69 | zend_string *callable_str = zval_get_string(¶ms[i]); 70 | smart_str_appends(&key, ZSTR_VAL(callable_str)); 71 | zend_string_release(callable_str); 72 | break; 73 | } 74 | case IS_RESOURCE: { 75 | // Serialize resource as string 76 | smart_str_appendc(&key, ':'); 77 | smart_str_append_long(&key, Z_RES_HANDLE(params[i])); 78 | break; 79 | } 80 | default: { 81 | // Serialize other types as usual 82 | smart_str_appendc(&key, ':'); 83 | php_serialize_data_t var_hash; 84 | PHP_VAR_SERIALIZE_INIT(var_hash); 85 | php_var_serialize(&key, ¶ms[i], &var_hash); 86 | PHP_VAR_SERIALIZE_DESTROY(var_hash); 87 | break; 88 | } 89 | } 90 | } 91 | 92 | smart_str_0(&key); 93 | 94 | return key.s; 95 | } 96 | 97 | static void aspect_execute_ex(zend_execute_data *execute_data) { 98 | zend_function *func = execute_data->func; 99 | 100 | // Check if function name is set 101 | if (!func->common.function_name) { 102 | original_zend_execute_ex(execute_data); 103 | return; 104 | } 105 | 106 | if (func->common.attributes && zend_get_attribute_str(func->common.attributes, "memoize", sizeof("memoize") - 1)) { 107 | if (func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { 108 | if (func->common.arg_info != NULL) { 109 | zend_arg_info *ret_info = func->common.arg_info - 1; 110 | if (ret_info != NULL) { 111 | zend_type return_type = ret_info->type; 112 | uint32_t type_mask = return_type.type_mask; 113 | 114 | if (type_mask & ((uint32_t)1 << IS_VOID) 115 | #if PHP_VERSION_ID >= 80100 116 | || type_mask & ((uint32_t)1 << IS_NEVER) 117 | #endif 118 | ) { 119 | // cannot memoize functions with void or never return types 120 | // @TODO: Should this be an error/exception? 121 | original_zend_execute_ex(execute_data); 122 | return; 123 | } 124 | } 125 | } 126 | } 127 | 128 | handle_memoize_functions(execute_data); 129 | return; 130 | } 131 | 132 | // Default execution for non-memoized functions 133 | original_zend_execute_ex(execute_data); 134 | } 135 | 136 | static void handle_memoize_functions(zend_execute_data *execute_data) { 137 | zend_string *cache_key = compute_cache_key(execute_data); 138 | zval *cached_value = zend_hash_find(&ASPECT_G(memoize_cache), cache_key); 139 | 140 | if (cached_value) { 141 | // Return the cached value 142 | if (EX(return_value)) { 143 | ZVAL_COPY(EX(return_value), cached_value); 144 | } 145 | zend_string_release(cache_key); 146 | return; 147 | } 148 | 149 | zval temp_return_value, *actual_return_value; 150 | if (EX(return_value)) { 151 | actual_return_value = EX(return_value); 152 | } else { 153 | actual_return_value = &temp_return_value; 154 | } 155 | 156 | // Call the original function 157 | original_zend_execute_ex(execute_data); 158 | 159 | if (EG(exception) || EG(exit_status)) { 160 | zend_string_release(cache_key); 161 | return; 162 | } 163 | 164 | if (Z_TYPE_P(actual_return_value) == IS_UNDEF) { 165 | zend_string_release(cache_key); 166 | return; 167 | } 168 | 169 | // Cache the result if no exception or exit occurred 170 | zval cache_copy; 171 | ZVAL_DUP(&cache_copy, actual_return_value); 172 | 173 | if (zend_hash_add(&ASPECT_G(memoize_cache), cache_key, &cache_copy) == NULL) { 174 | php_error_docref(NULL, E_WARNING, "Failed to add cache entry"); 175 | zval_ptr_dtor(&cache_copy); // Clean up if adding fails 176 | zend_string_release(cache_key); 177 | return; 178 | } 179 | 180 | 181 | zend_string_release(cache_key); 182 | } 183 | 184 | PHP_MINIT_FUNCTION (aspect) { 185 | // Initialize the memoize cache 186 | zend_hash_init(&ASPECT_G(memoize_cache), 8, NULL, ZVAL_PTR_DTOR, 1); 187 | 188 | original_zend_execute_ex = zend_execute_ex; 189 | 190 | zend_execute_ex = aspect_execute_ex; 191 | 192 | // Register the Memoize attribute 193 | zend_ce_memoize = register_class_Memoize(); 194 | 195 | return SUCCESS; 196 | } 197 | 198 | PHP_MSHUTDOWN_FUNCTION (aspect) { 199 | // Destroy the memoize cache 200 | zend_hash_destroy(&ASPECT_G(memoize_cache)); 201 | 202 | zend_execute_ex = original_zend_execute_ex; 203 | return SUCCESS; 204 | } 205 | 206 | PHP_RINIT_FUNCTION (aspect) { 207 | #if defined(ZTS) && defined(COMPILE_DL_ASPECT) 208 | ZEND_TSRMLS_CACHE_UPDATE(); 209 | #endif 210 | 211 | return SUCCESS; 212 | } 213 | 214 | PHP_RSHUTDOWN_FUNCTION (aspect) { 215 | zend_hash_clean(&ASPECT_G(memoize_cache)); 216 | return SUCCESS; 217 | } 218 | 219 | PHP_MINFO_FUNCTION (aspect) { 220 | php_info_print_table_start(); 221 | php_info_print_table_row(2, "aspect support", "enabled"); 222 | php_info_print_table_row(2, "Version", PHP_ASPECT_VERSION); 223 | php_info_print_table_end(); 224 | } 225 | 226 | zend_module_entry aspect_module_entry = { 227 | STANDARD_MODULE_HEADER, 228 | "aspect", /* Extension name */ 229 | NULL, /* zend_function_entry */ 230 | PHP_MINIT(aspect), /* PHP_MINIT - Module initialization */ 231 | PHP_MSHUTDOWN(aspect), /* PHP_MSHUTDOWN - Module shutdown */ 232 | PHP_RINIT(aspect), /* PHP_RINIT - Request initialization */ 233 | PHP_RSHUTDOWN(aspect), /* PHP_RSHUTDOWN - Request shutdown */ 234 | PHP_MINFO(aspect), /* PHP_MINFO - Module info */ 235 | PHP_ASPECT_VERSION, /* Version */ 236 | STANDARD_MODULE_PROPERTIES 237 | }; 238 | 239 | #ifdef COMPILE_DL_ASPECT 240 | # ifdef ZTS 241 | ZEND_TSRMLS_CACHE_DEFINE() 242 | # endif 243 | 244 | ZEND_GET_MODULE(aspect) 245 | 246 | #endif 247 | -------------------------------------------------------------------------------- /aspect.stub.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* This is a generated file, edit the .stub.php file instead. 4 | * Stub hash: 7fe2851260165fea167af14286eceaf94f1a6699 */ 5 | 6 | 7 | 8 | 9 | static const zend_function_entry class_Memoize_methods[] = { 10 | ZEND_FE_END 11 | }; 12 | 13 | static zend_class_entry *register_class_Memoize(void) 14 | { 15 | zend_class_entry ce, *class_entry; 16 | 17 | INIT_CLASS_ENTRY(ce, "Memoize", class_Memoize_methods); 18 | class_entry = zend_register_internal_class_ex(&ce, NULL); 19 | class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES; 20 | 21 | zend_string *attribute_name_Attribute_class_Memoize_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, 1); 22 | zend_attribute *attribute_Attribute_class_Memoize_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_Memoize_0, 1); 23 | zend_string_release(attribute_name_Attribute_class_Memoize_0); 24 | zval attribute_Attribute_class_Memoize_0_arg0; 25 | ZVAL_LONG(&attribute_Attribute_class_Memoize_0_arg0, ZEND_ATTRIBUTE_TARGET_FUNCTION | ZEND_ATTRIBUTE_TARGET_METHOD); 26 | ZVAL_COPY_VALUE(&attribute_Attribute_class_Memoize_0->args[0].value, &attribute_Attribute_class_Memoize_0_arg0); 27 | 28 | return class_entry; 29 | } 30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solidworx/aspect", 3 | "type": "php-ext", 4 | "license": "MIT", 5 | "description": "PHP Aspect Extension", 6 | "require": { 7 | "php": "^8.0" 8 | }, 9 | "replace": { 10 | "ext-aspect": "*" 11 | }, 12 | "php-ext": { 13 | "extension-name": "ext-aspect", 14 | "priority": 80 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl Autotools config.m4 for PHP extension aspect 2 | 3 | dnl Comments in this file start with the string 'dnl' (discard to next line). 4 | dnl Remove where necessary. 5 | 6 | dnl If extension references and depends on an external library package, use 7 | dnl the '--with-aspect' configure option: 8 | dnl PHP_ARG_WITH([aspect], 9 | dnl [for aspect support], 10 | dnl [AS_HELP_STRING([--with-aspect], 11 | dnl [Include aspect support])]) 12 | 13 | dnl Otherwise use the '--enable-aspect' configure option: 14 | PHP_ARG_ENABLE([aspect], 15 | [whether to enable aspect support], 16 | [AS_HELP_STRING([--enable-aspect], 17 | [Enable aspect support])], 18 | [no]) 19 | 20 | AS_VAR_IF([PHP_ASPECT], [no],, [ 21 | dnl This section is executed when extension is enabled with one of the above 22 | dnl configure options. Adjust and add tests here. 23 | 24 | dnl 25 | dnl Use and adjust this code block if extension depends on external library 26 | dnl package which supports pkg-config. 27 | dnl 28 | dnl Find library package with pkg-config. 29 | dnl PKG_CHECK_MODULES([LIBFOO], [foo]) 30 | dnl 31 | dnl Or if you need to check for a particular library version with pkg-config, 32 | dnl you can use comparison operators. For example: 33 | dnl PKG_CHECK_MODULES([LIBFOO], [foo >= 1.2.3]) 34 | dnl PKG_CHECK_MODULES([LIBFOO], [foo < 3.4]) 35 | dnl PKG_CHECK_MODULES([LIBFOO], [foo = 1.2.3]) 36 | dnl 37 | dnl Add library compilation and linker flags to extension. 38 | dnl PHP_EVAL_INCLINE([$LIBFOO_CFLAGS]) 39 | dnl PHP_EVAL_LIBLINE([$LIBFOO_LIBS], [ASPECT_SHARED_LIBADD]) 40 | dnl 41 | dnl Check for library and symbol presence. 42 | dnl LIBNAME=aspect # you may want to change this 43 | dnl LIBSYMBOL=aspect # you most likely want to change this 44 | dnl 45 | dnl If you need to check for a particular library function (e.g. a conditional 46 | dnl or version-dependent feature) and you are using pkg-config: 47 | dnl PHP_CHECK_LIBRARY([$LIBNAME], [$LIBSYMBOL], 48 | dnl [AC_DEFINE([HAVE_ASPECT_FEATURE], [1], 49 | dnl [Define to 1 if aspect has the 'FEATURE'.])], 50 | dnl [AC_MSG_FAILURE([FEATURE not supported by your aspect library.])], 51 | dnl [$LIBFOO_LIBS]) 52 | dnl 53 | 54 | dnl 55 | dnl Or use and adjust this code block if extension depends on external library 56 | dnl package, which does not support pkg-config. 57 | dnl 58 | dnl Path to library package can be given as parameter (--with-aspect=) 59 | dnl SEARCH_PATH="/usr/local /usr" # you might want to change this 60 | dnl SEARCH_FOR="/include/aspect.h" # you most likely want to change this 61 | dnl AS_IF([test -r $PHP_ASPECT/$SEARCH_FOR], 62 | dnl [ASPECT_DIR=$PHP_ASPECT], 63 | dnl [ 64 | dnl for i in $SEARCH_PATH; do 65 | dnl AS_IF([test -r $i/$SEARCH_FOR], 66 | dnl [ASPECT_DIR=$i; break;]) 67 | dnl done 68 | dnl ]) 69 | dnl 70 | dnl AC_MSG_CHECKING([for aspect library package]) 71 | dnl AS_VAR_IF([ASPECT_DIR],, [ 72 | dnl AC_MSG_RESULT([not found]) 73 | dnl AC_MSG_ERROR([Please reinstall the aspect library package]) 74 | dnl ], [AC_MSG_RESULT([found in $ASPECT_DIR])]) 75 | dnl 76 | dnl Add include flag where library package headers are located on the system. 77 | dnl PHP_ADD_INCLUDE([$ASPECT_DIR/include]) 78 | dnl 79 | dnl Check for library and symbol presence. 80 | dnl LIBNAME=aspect # you may want to change this 81 | dnl LIBSYMBOL=aspect # you most likely want to change this 82 | dnl 83 | dnl If you need to check for a particular library function (e.g. a conditional 84 | dnl or version-dependent feature) and you are not using pkg-config: 85 | dnl PHP_CHECK_LIBRARY([$LIBNAME], [$LIBSYMBOL], [ 86 | dnl PHP_ADD_LIBRARY_WITH_PATH([$LIBNAME], 87 | dnl [$ASPECT_DIR/$PHP_LIBDIR], 88 | dnl [ASPECT_SHARED_LIBADD]) 89 | dnl AC_DEFINE([HAVE_ASPECT_FEATURE], [1], 90 | dnl [Define to 1 if aspect has the 'FEATURE'.]) 91 | dnl ], 92 | dnl [AC_MSG_FAILURE([FEATURE not supported by your aspect library.])], 93 | dnl [-L$ASPECT_DIR/$PHP_LIBDIR -lm]) 94 | dnl 95 | 96 | dnl Add linked libraries flags for shared extension to the generated Makefile. 97 | dnl PHP_SUBST([ASPECT_SHARED_LIBADD]) 98 | 99 | dnl Define a preprocessor macro to indicate that this PHP extension can 100 | dnl be dynamically loaded as a shared module or is statically built into PHP. 101 | AC_DEFINE([HAVE_ASPECT], [1], 102 | [Define to 1 if the PHP extension 'aspect' is available.]) 103 | 104 | dnl Configure extension sources and compilation flags. 105 | PHP_NEW_EXTENSION([aspect], 106 | [aspect.c], 107 | [$ext_shared],, 108 | [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1]) 109 | ]) 110 | -------------------------------------------------------------------------------- /config.w32: -------------------------------------------------------------------------------- 1 | ARG_ENABLE('aspect', 'aspect support', 'no'); 2 | 3 | if (PHP_ASPECT != 'no') { 4 | AC_DEFINE('HAVE_ASPECT', 1, "Define to 1 if the PHP extension 'aspect' is available."); 5 | 6 | EXTENSION('aspect', 'aspect.c', null, '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1'); 7 | } 8 | -------------------------------------------------------------------------------- /php_aspect.h: -------------------------------------------------------------------------------- 1 | /* aspect extension for PHP */ 2 | 3 | #ifndef PHP_ASPECT_H 4 | # define PHP_ASPECT_H 5 | 6 | extern zend_module_entry aspect_module_entry; 7 | # define phpext_aspect_ptr &aspect_module_entry 8 | 9 | # define PHP_ASPECT_VERSION "0.1.1" 10 | 11 | PHP_MINIT_FUNCTION(aspect); 12 | PHP_MSHUTDOWN_FUNCTION(aspect); 13 | PHP_RINIT_FUNCTION(aspect); 14 | PHP_RSHUTDOWN_FUNCTION(aspect); 15 | 16 | ZEND_BEGIN_MODULE_GLOBALS(aspect) 17 | HashTable memoize_cache; // Global cache 18 | ZEND_END_MODULE_GLOBALS(aspect) 19 | 20 | #define ASPECT_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(aspect, v) 21 | 22 | BEGIN_EXTERN_C() 23 | extern ZEND_API zend_class_entry *zend_ce_memoize; 24 | END_EXTERN_C() 25 | 26 | # if defined(ZTS) && defined(COMPILE_DL_ASPECT) 27 | ZEND_TSRMLS_CACHE_EXTERN() 28 | # endif 29 | 30 | #endif /* PHP_ASPECT_H */ 31 | -------------------------------------------------------------------------------- /tests/memoize_001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if aspect is loaded 3 | --EXTENSIONS-- 4 | aspect 5 | --FILE-- 6 | 9 | --EXPECT-- 10 | The extension "aspect" is available 11 | -------------------------------------------------------------------------------- /tests/memoize_002.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Memoize attribute exists 3 | --EXTENSIONS-- 4 | aspect 5 | --FILE-- 6 | getAttributes()[0]->getName()); 15 | 16 | class Bar { 17 | #[Memoize] 18 | public function bar() { 19 | return 'bar'; 20 | } 21 | } 22 | 23 | $bar = new Bar(); 24 | var_dump((new ReflectionMethod($bar, 'bar'))->getAttributes()[0]->getName()); 25 | 26 | ?> 27 | --EXPECT-- 28 | bool(true) 29 | string(7) "Memoize" 30 | string(7) "Memoize" 31 | -------------------------------------------------------------------------------- /tests/memoize_003.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Memoize function calls 3 | --EXTENSIONS-- 4 | aspect 5 | --FILE-- 6 | 24 | --EXPECT-- 25 | running foo 26 | bool(false) 27 | bool(true) 28 | bool(true) 29 | -------------------------------------------------------------------------------- /tests/memoize_004.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Functions aren't memoized if they throw an exception 3 | --EXTENSIONS-- 4 | aspect 5 | --FILE-- 6 | getMessage(), PHP_EOL; 25 | } 26 | 27 | try { 28 | foo('foo'); 29 | } catch (Exception $e) { 30 | echo $e->getMessage(), PHP_EOL; 31 | } 32 | 33 | try { 34 | foo('bar'); 35 | } catch (Exception $e) { 36 | echo $e->getMessage(), PHP_EOL; 37 | } 38 | 39 | var_dump(foo() === foo()); 40 | var_dump($first === foo()); 41 | 42 | ?> 43 | --EXPECT-- 44 | bool(true) 45 | foo 46 | foo 47 | bar 48 | bool(true) 49 | bool(true) 50 | -------------------------------------------------------------------------------- /tests/memoize_005.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Functions are memoized with different parameters 3 | --EXTENSIONS-- 4 | aspect 5 | --FILE-- 6 | id = bin2hex(random_bytes(5)); 22 | } 23 | } 24 | 25 | #[Memoize] 26 | function bar(Baz $baz) { 27 | return bin2hex(random_bytes(5)); 28 | } 29 | 30 | $baz1 = new Baz; 31 | 32 | var_dump(bar($baz1) !== bar(new Baz)); 33 | var_dump(bar($baz1) === bar($baz1)); 34 | 35 | ?> 36 | --EXPECT-- 37 | bool(true) 38 | bool(true) 39 | bool(true) 40 | bool(true) 41 | bool(true) 42 | -------------------------------------------------------------------------------- /tests/memoize_006.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Class methods can be memoized 3 | --EXTENSIONS-- 4 | aspect 5 | --FILE-- 6 | foo() === $foo->foo()); 26 | var_dump($foo->baz() !== $foo->baz()); 27 | var_dump($foo->bar('a', 'b') === $foo->bar('a', 'b')); 28 | var_dump($foo->bar('a', 'c') !== $foo->bar('a', 'b')); 29 | var_dump($foo->bar('a', 'c') === $foo->bar('a', 'c')); 30 | 31 | // only instances of the same class is memoized 32 | $foo1 = new Foo; 33 | $foo2 = new Foo; 34 | var_dump($foo1->foo() === $foo1->foo()); 35 | var_dump($foo2->foo() === $foo2->foo()); 36 | var_dump($foo1->foo() !== $foo2->foo()); 37 | var_dump($foo1->bar('a', 'b') !== $foo2->bar('a', 'b')); 38 | 39 | ?> 40 | --EXPECT-- 41 | bool(true) 42 | bool(true) 43 | bool(true) 44 | bool(true) 45 | bool(true) 46 | bool(true) 47 | bool(true) 48 | bool(true) 49 | bool(true) 50 | -------------------------------------------------------------------------------- /tests/memoize_007.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Static class methods can be memoized 3 | --EXTENSIONS-- 4 | aspect 5 | --FILE-- 6 | 30 | --EXPECT-- 31 | bool(true) 32 | bool(true) 33 | bool(true) 34 | bool(true) 35 | -------------------------------------------------------------------------------- /tests/memoize_008.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Handle different argument types 3 | --EXTENSIONS-- 4 | aspect 5 | --FILE-- 6 | 5; 44 | $closure2 = fn() => 6; 45 | $class1 = new stdClass; 46 | $class2 = new stdClass; 47 | $resource1 = fopen(__FILE__, 'r'); 48 | 49 | var_dump(test_int(5) === test_int(5)); 50 | var_dump(test_string('hello') === test_string('hello')); 51 | var_dump(test_array([1, 2, 3]) === test_array([1, 2, 3])); 52 | var_dump(test_mixed(5) === test_mixed(5)); 53 | var_dump(test_closure($closure1) === test_closure($closure1)); 54 | var_dump(test_object($class1) === test_object($class1)); 55 | var_dump(test_resource($resource1) === test_resource($resource1)); 56 | 57 | var_dump(test_int(6) !== test_int(5)); 58 | var_dump(test_string('hello') !== test_string('world')); 59 | var_dump(test_array([1, 2, 3]) !== test_array([1, 2, 4])); 60 | var_dump(test_mixed(6) !== test_mixed(5)); 61 | var_dump(test_closure($closure1) !== test_closure($closure2)); 62 | var_dump(test_object($class1) !== test_object($class2)); 63 | var_dump(test_resource(fopen(__FILE__, 'r')) !== test_resource(fopen('php://memory', 'r'))); 64 | 65 | ?> 66 | --EXPECT-- 67 | bool(true) 68 | bool(true) 69 | bool(true) 70 | bool(true) 71 | bool(true) 72 | bool(true) 73 | bool(true) 74 | bool(true) 75 | bool(true) 76 | bool(true) 77 | bool(true) 78 | bool(true) 79 | bool(true) 80 | bool(true) 81 | -------------------------------------------------------------------------------- /tests/memoize_009.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | It returns the same object 3 | --EXTENSIONS-- 4 | aspect 5 | --FILE-- 6 | 16 | --EXPECT-- 17 | bool(true) 18 | -------------------------------------------------------------------------------- /tests/memoize_010.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | It doesn't memoize if the function returns void 3 | --EXTENSIONS-- 4 | aspect 5 | --FILE-- 6 | 16 | --EXPECT-- 17 | running function 18 | running function 19 | bool(true) 20 | --------------------------------------------------------------------------------