Skip to content

Commit 0cf034b

Browse files
Support scalars, enums and arrays
1 parent 0fa9215 commit 0cf034b

9 files changed

+386
-67
lines changed

docs/sidekicks.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,19 @@ $all = frankenphp_sidekick_get_vars(['redis-watcher', 'feature-flags']);
4343

4444
- `$name` is available as `$_SERVER['FRANKENPHP_SIDEKICK_NAME']` and `$_SERVER['argv'][1]` in the entrypoint script
4545
- Throws `RuntimeException` on timeout, missing entrypoint, or sidekick crash
46+
- Throws `LogicException` if the vars contain an enum class or case that cannot be resolved
4647
- Works in both worker and non-worker mode
4748

4849
### `frankenphp_sidekick_set_vars(array $vars): void`
4950

50-
Publishes a snapshot of string key-value pairs from inside a sidekick.
51+
Publishes a snapshot of key-value pairs from inside a sidekick.
5152
Each call **replaces** the entire snapshot atomically.
5253

54+
Supported value types: `null`, `bool`, `int`, `float`, `string`, `array` (nested), and **enums**.
55+
Objects, resources, and references are rejected.
56+
5357
- Throws `RuntimeException` if not called from a sidekick context
54-
- Throws `ValueError` if keys or values are not strings
58+
- Throws `ValueError` if values contain objects, resources, or references
5559

5660
### `frankenphp_sidekick_get_signaling_stream(): resource`
5761

frankenphp.c

Lines changed: 220 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "frankenphp.h"
22
#include <SAPI.h>
33
#include <Zend/zend_alloc.h>
4+
#include <Zend/zend_enum.h>
45
#include <Zend/zend_exceptions.h>
56
#include <Zend/zend_interfaces.h>
67
#include <errno.h>
@@ -675,63 +676,232 @@ PHP_FUNCTION(frankenphp_handle_request) {
675676
RETURN_TRUE;
676677
}
677678

679+
/* Persistent enum storage */
680+
typedef struct {
681+
zend_string *class_name;
682+
zend_string *case_name;
683+
} sidekick_enum_t;
684+
685+
/* Validate that a zval tree contains only scalars, arrays, and enums */
686+
static bool sidekick_validate_zval(zval *z) {
687+
switch (Z_TYPE_P(z)) {
688+
case IS_NULL:
689+
case IS_FALSE:
690+
case IS_TRUE:
691+
case IS_LONG:
692+
case IS_DOUBLE:
693+
case IS_STRING:
694+
return true;
695+
case IS_OBJECT:
696+
return (Z_OBJCE_P(z)->ce_flags & ZEND_ACC_ENUM) != 0;
697+
case IS_ARRAY: {
698+
zval *val;
699+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z), val) {
700+
if (!sidekick_validate_zval(val))
701+
return false;
702+
}
703+
ZEND_HASH_FOREACH_END();
704+
return true;
705+
}
706+
default:
707+
return false;
708+
}
709+
}
710+
711+
/* Deep-copy a zval into persistent memory */
712+
static void sidekick_persist_zval(zval *dst, zval *src) {
713+
switch (Z_TYPE_P(src)) {
714+
case IS_NULL:
715+
case IS_FALSE:
716+
case IS_TRUE:
717+
ZVAL_COPY_VALUE(dst, src);
718+
break;
719+
case IS_LONG:
720+
ZVAL_LONG(dst, Z_LVAL_P(src));
721+
break;
722+
case IS_DOUBLE:
723+
ZVAL_DOUBLE(dst, Z_DVAL_P(src));
724+
break;
725+
case IS_STRING: {
726+
zend_string *ps = zend_string_init(Z_STRVAL_P(src), Z_STRLEN_P(src), 1);
727+
ZVAL_NEW_STR(dst, ps);
728+
break;
729+
}
730+
case IS_OBJECT: {
731+
/* Must be an enum (validated earlier) */
732+
zend_class_entry *ce = Z_OBJCE_P(src);
733+
sidekick_enum_t *e = pemalloc(sizeof(sidekick_enum_t), 1);
734+
e->class_name = zend_string_init(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), 1);
735+
zval *case_name_zval = zend_enum_fetch_case_name(Z_OBJ_P(src));
736+
e->case_name = zend_string_init(Z_STRVAL_P(case_name_zval),
737+
Z_STRLEN_P(case_name_zval), 1);
738+
ZVAL_PTR(dst, e);
739+
break;
740+
}
741+
case IS_ARRAY: {
742+
HashTable *src_ht = Z_ARRVAL_P(src);
743+
HashTable *dst_ht = pemalloc(sizeof(HashTable), 1);
744+
zend_hash_init(dst_ht, zend_hash_num_elements(src_ht), NULL, NULL, 1);
745+
ZVAL_ARR(dst, dst_ht);
746+
747+
zend_string *key;
748+
zend_ulong idx;
749+
zval *val;
750+
ZEND_HASH_FOREACH_KEY_VAL(src_ht, idx, key, val) {
751+
zval pval;
752+
sidekick_persist_zval(&pval, val);
753+
if (key) {
754+
zend_string *pkey = zend_string_init(ZSTR_VAL(key), ZSTR_LEN(key), 1);
755+
zend_hash_add_new(dst_ht, pkey, &pval);
756+
zend_string_release(pkey);
757+
} else {
758+
zend_hash_index_add_new(dst_ht, idx, &pval);
759+
}
760+
}
761+
ZEND_HASH_FOREACH_END();
762+
break;
763+
}
764+
default:
765+
ZVAL_NULL(dst);
766+
break;
767+
}
768+
}
769+
770+
/* Deep-free a persistent zval tree */
771+
static void sidekick_free_persistent_zval(zval *z) {
772+
switch (Z_TYPE_P(z)) {
773+
case IS_STRING:
774+
zend_string_free(Z_STR_P(z));
775+
break;
776+
case IS_PTR: {
777+
sidekick_enum_t *e = (sidekick_enum_t *)Z_PTR_P(z);
778+
zend_string_free(e->class_name);
779+
zend_string_free(e->case_name);
780+
pefree(e, 1);
781+
break;
782+
}
783+
case IS_ARRAY: {
784+
zval *val;
785+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z), val) {
786+
sidekick_free_persistent_zval(val);
787+
}
788+
ZEND_HASH_FOREACH_END();
789+
zend_hash_destroy(Z_ARRVAL_P(z));
790+
pefree(Z_ARRVAL_P(z), 1);
791+
break;
792+
}
793+
default:
794+
break;
795+
}
796+
}
797+
798+
/* Deep-copy a persistent zval tree into request memory */
799+
static void sidekick_request_copy_zval(zval *dst, zval *src) {
800+
switch (Z_TYPE_P(src)) {
801+
case IS_NULL:
802+
case IS_FALSE:
803+
case IS_TRUE:
804+
ZVAL_COPY_VALUE(dst, src);
805+
break;
806+
case IS_LONG:
807+
ZVAL_LONG(dst, Z_LVAL_P(src));
808+
break;
809+
case IS_DOUBLE:
810+
ZVAL_DOUBLE(dst, Z_DVAL_P(src));
811+
break;
812+
case IS_STRING:
813+
ZVAL_STRINGL(dst, Z_STRVAL_P(src), Z_STRLEN_P(src));
814+
break;
815+
case IS_PTR: {
816+
sidekick_enum_t *e = (sidekick_enum_t *)Z_PTR_P(src);
817+
zend_class_entry *ce = zend_lookup_class(e->class_name);
818+
if (!ce || !(ce->ce_flags & ZEND_ACC_ENUM)) {
819+
zend_throw_exception_ex(spl_ce_LogicException, 0,
820+
"Sidekick enum class \"%s\" not found",
821+
ZSTR_VAL(e->class_name));
822+
ZVAL_NULL(dst);
823+
break;
824+
}
825+
zend_object *enum_obj = zend_enum_get_case_cstr(ce, ZSTR_VAL(e->case_name));
826+
if (!enum_obj) {
827+
zend_throw_exception_ex(spl_ce_LogicException, 0,
828+
"Sidekick enum case \"%s::%s\" not found",
829+
ZSTR_VAL(e->class_name), ZSTR_VAL(e->case_name));
830+
ZVAL_NULL(dst);
831+
break;
832+
}
833+
ZVAL_OBJ_COPY(dst, enum_obj);
834+
break;
835+
}
836+
case IS_ARRAY: {
837+
HashTable *src_ht = Z_ARRVAL_P(src);
838+
array_init_size(dst, zend_hash_num_elements(src_ht));
839+
HashTable *dst_ht = Z_ARRVAL_P(dst);
840+
841+
zend_string *key;
842+
zend_ulong idx;
843+
zval *val;
844+
ZEND_HASH_FOREACH_KEY_VAL(src_ht, idx, key, val) {
845+
zval rval;
846+
sidekick_request_copy_zval(&rval, val);
847+
if (EG(exception)) {
848+
zval_ptr_dtor(&rval);
849+
break;
850+
}
851+
if (key) {
852+
zend_string *rkey = zend_string_init(ZSTR_VAL(key), ZSTR_LEN(key), 0);
853+
ZSTR_H(rkey) = ZSTR_H(key);
854+
zend_hash_add_new(dst_ht, rkey, &rval);
855+
zend_string_release(rkey);
856+
} else {
857+
zend_hash_index_add_new(dst_ht, idx, &rval);
858+
}
859+
}
860+
ZEND_HASH_FOREACH_END();
861+
break;
862+
}
863+
default:
864+
ZVAL_NULL(dst);
865+
break;
866+
}
867+
}
868+
678869
PHP_FUNCTION(frankenphp_sidekick_set_vars) {
679870
zval *vars_array = NULL;
680871

681872
ZEND_PARSE_PARAMETERS_START(1, 1);
682873
Z_PARAM_ARRAY(vars_array);
683874
ZEND_PARSE_PARAMETERS_END();
684875

685-
/* Validate all keys and values are strings */
686-
HashTable *ht = Z_ARRVAL_P(vars_array);
687-
zend_string *key;
876+
/* Validate: reject objects, resources, references */
688877
zval *val;
689-
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) {
690-
if (key == NULL) {
691-
zend_value_error("All keys must be strings");
692-
RETURN_THROWS();
693-
}
694-
if (Z_TYPE_P(val) != IS_STRING) {
695-
zend_value_error("All values must be strings");
878+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(vars_array), val) {
879+
if (!sidekick_validate_zval(val)) {
880+
zend_value_error("Values must be scalars, strings, arrays, or null; "
881+
"objects and resources are not allowed");
696882
RETURN_THROWS();
697883
}
698884
}
699885
ZEND_HASH_FOREACH_END();
700886

701-
HashTable *persistent = pemalloc(sizeof(HashTable), 1);
702-
zend_hash_init(persistent, zend_hash_num_elements(ht), NULL, NULL, 1);
703-
704-
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) {
705-
zend_string *pkey = zend_string_init(ZSTR_VAL(key), ZSTR_LEN(key), 1);
706-
zend_string *pval_str =
707-
zend_string_init(Z_STRVAL_P(val), Z_STRLEN_P(val), 1);
708-
zval pval;
709-
ZVAL_NEW_STR(&pval, pval_str);
710-
zend_hash_add_new(persistent, pkey, &pval);
711-
zend_string_release(pkey);
712-
}
713-
ZEND_HASH_FOREACH_END();
887+
/* Deep-copy to persistent memory */
888+
zval persistent;
889+
sidekick_persist_zval(&persistent, vars_array);
714890

715891
void *old = NULL;
716-
char *error = go_frankenphp_sidekick_set_vars(thread_index, persistent, &old);
892+
char *error =
893+
go_frankenphp_sidekick_set_vars(thread_index, Z_ARRVAL(persistent), &old);
717894
if (error) {
718-
zval *ev;
719-
ZEND_HASH_FOREACH_VAL(persistent, ev) { zend_string_free(Z_STR_P(ev)); }
720-
ZEND_HASH_FOREACH_END();
721-
zend_hash_destroy(persistent);
722-
pefree(persistent, 1);
895+
sidekick_free_persistent_zval(&persistent);
723896
zend_throw_exception(spl_ce_RuntimeException, error, 0);
724897
free(error);
725898
RETURN_THROWS();
726899
}
727900

728901
if (old != NULL) {
729-
HashTable *old_ht = (HashTable *)old;
730-
zval *v;
731-
ZEND_HASH_FOREACH_VAL(old_ht, v) { zend_string_free(Z_STR_P(v)); }
732-
ZEND_HASH_FOREACH_END();
733-
zend_hash_destroy(old_ht);
734-
pefree(old_ht, 1);
902+
zval old_zval;
903+
ZVAL_ARR(&old_zval, (HashTable *)old);
904+
sidekick_free_persistent_zval(&old_zval);
735905
}
736906
}
737907

@@ -772,16 +942,15 @@ PHP_FUNCTION(frankenphp_sidekick_get_vars) {
772942
RETURN_THROWS();
773943
}
774944

775-
array_init(return_value);
776945
if (vars_ptr) {
777-
HashTable *persistent = (HashTable *)vars_ptr;
778-
zend_string *key;
779-
zval *val;
780-
ZEND_HASH_FOREACH_STR_KEY_VAL(persistent, key, val) {
781-
add_assoc_stringl(return_value, ZSTR_VAL(key), Z_STRVAL_P(val),
782-
Z_STRLEN_P(val));
946+
zval src;
947+
ZVAL_ARR(&src, (HashTable *)vars_ptr);
948+
sidekick_request_copy_zval(return_value, &src);
949+
if (EG(exception)) {
950+
RETURN_THROWS();
783951
}
784-
ZEND_HASH_FOREACH_END();
952+
} else {
953+
array_init(return_value);
785954
}
786955
return;
787956
}
@@ -847,16 +1016,16 @@ PHP_FUNCTION(frankenphp_sidekick_get_vars) {
8471016
idx = 0;
8481017
ZEND_HASH_FOREACH_VAL(ht, val) {
8491018
zval sidekick_vars;
850-
array_init(&sidekick_vars);
8511019
if (vars_ptrs[idx]) {
852-
HashTable *persistent = (HashTable *)vars_ptrs[idx];
853-
zend_string *k;
854-
zval *v;
855-
ZEND_HASH_FOREACH_STR_KEY_VAL(persistent, k, v) {
856-
add_assoc_stringl(&sidekick_vars, ZSTR_VAL(k), Z_STRVAL_P(v),
857-
Z_STRLEN_P(v));
1020+
zval src;
1021+
ZVAL_ARR(&src, (HashTable *)vars_ptrs[idx]);
1022+
sidekick_request_copy_zval(&sidekick_vars, &src);
1023+
if (EG(exception)) {
1024+
zval_ptr_dtor(&sidekick_vars);
1025+
break;
8581026
}
859-
ZEND_HASH_FOREACH_END();
1027+
} else {
1028+
array_init(&sidekick_vars);
8601029
}
8611030
add_assoc_zval(return_value, Z_STRVAL_P(val), &sidekick_vars);
8621031
idx++;

0 commit comments

Comments
 (0)