├── .editorconfig ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── context.c ├── context.go ├── context_test.go ├── engine.c ├── engine.go ├── engine_test.go ├── include ├── context.h ├── engine.h ├── php5 │ ├── _context.h │ ├── _engine.h │ ├── _receiver.h │ └── _value.h ├── php7 │ ├── _context.h │ ├── _engine.h │ ├── _receiver.h │ └── _value.h ├── receiver.h └── value.h ├── php5.go ├── php7-debian.go ├── php7-static.go ├── php7.go ├── receiver.c ├── receiver.go ├── receiver_test.go ├── src ├── php5 │ ├── _context.c │ ├── _engine.c │ ├── _receiver.c │ └── _value.c └── php7 │ ├── _context.c │ ├── _engine.c │ ├── _receiver.c │ └── _value.c ├── value.c ├── value.go └── value_test.go /.editorconfig: -------------------------------------------------------------------------------- 1 | # Code styles for this project. 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 4 9 | indent_style = tab 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | *.deb 3 | *.tar.xz 4 | *.out 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.6.36 4 | - 7.0.30 5 | - 7.1.18 6 | - 7.2.6 7 | 8 | sudo: required 9 | services: 10 | - docker 11 | 12 | script: 13 | - make docker-test PHP_VERSION="$TRAVIS_PHP_VERSION" 14 | 15 | notifications: 16 | email: false 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10-stretch 2 | 3 | # The full PHP version to target, i.e. "7.1.10". 4 | ARG PHP_VERSION 5 | 6 | # Whether or not to build PHP as a static library. 7 | ARG STATIC=false 8 | 9 | # Environment variables used across the build. 10 | ENV PHP_URL="https://secure.php.net/get/php-${PHP_VERSION}.tar.xz/from/this/mirror" 11 | ENV PHP_BASE_DIR="/tmp/php" 12 | ENV PHP_SRC_DIR="${PHP_BASE_DIR}/src" 13 | 14 | # Build variables. 15 | ENV PHP_LDFLAGS="-Wl,-O1 -Wl,--hash-style=both -pie" 16 | ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O2" 17 | ENV PHP_CPPFLAGS="${PHP_CFLAGS}" 18 | 19 | # Fetch PHP source code. This step does not currently validate keys or checksums, as this process 20 | # will eventually transition to using the base `php` Docker images. 21 | ENV FETCH_DEPS="ca-certificates wget" 22 | RUN set -xe && \ 23 | apt-get update && apt-get install -y --no-install-recommends ${FETCH_DEPS} && \ 24 | mkdir -p ${PHP_BASE_DIR} && cd ${PHP_BASE_DIR} && \ 25 | wget -O php.tar.xz ${PHP_URL} && \ 26 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false ${FETCH_DEPS} 27 | 28 | # Build PHP library from source. 29 | ENV BUILD_DEPS="build-essential file libpcre3-dev dpkg-dev libcurl4-openssl-dev libedit-dev libsqlite3-dev libssl1.0-dev libxml2-dev zlib1g-dev" 30 | RUN set -xe && \ 31 | apt-get update && apt-get install -y --no-install-recommends ${BUILD_DEPS}; \ 32 | export CFLAGS="${PHP_CFLAGS}" CPPFLAGS="${PHP_CPPFLAGS}" LDFLAGS="${PHP_LDFLAGS}"; \ 33 | arch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" && multiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ 34 | [ "x$STATIC" = "xfalse" ] \ 35 | && options="--enable-embed" \ 36 | || options="--enable-embed=static --enable-static"; \ 37 | [ ! -d /usr/include/curl ] && ln -sT "/usr/include/$multiarch/curl" /usr/local/include/curl; \ 38 | mkdir -p ${PHP_SRC_DIR} && cd ${PHP_SRC_DIR} && \ 39 | tar -xJf ${PHP_BASE_DIR}/php.tar.xz -C . --strip-components=1 && \ 40 | ./configure \ 41 | --prefix=/usr --build="$arch" \ 42 | --with-libdir="lib/$multiarch" \ 43 | --with-pcre-regex=/usr \ 44 | --disable-cgi --disable-fpm \ 45 | --enable-ftp --enable-mbstring \ 46 | --with-curl --with-libedit --with-openssl --with-zlib \ 47 | $options \ 48 | && \ 49 | make -j "$(nproc)" && \ 50 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false ${BUILD_DEPS} 51 | 52 | # Install runtime dependencies for testing, building packages etc, and clean up source. 53 | ENV RUNTIME_DEPS="build-essential git curl libssl1.0 libpcre3-dev libcurl4-openssl-dev libedit-dev libxml2-dev zlib1g-dev" 54 | RUN set -xe && \ 55 | apt-get update && apt-get install -y --no-install-recommends ${RUNTIME_DEPS} && \ 56 | cd ${PHP_SRC_DIR} && make -j "$(nproc)" PHP_SAPI=embed install-sapi install-headers && \ 57 | cd / && rm -Rf ${PHP_BASE_DIR} ${PHP_SRC_DIR} 58 | 59 | ENTRYPOINT ["/bin/sh", "-c"] 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Alex Palaistras 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Package options. 2 | NAME := go-php 3 | DESCRIPTION := PHP bindings for the Go programming language 4 | IMPORT_PATH := github.com/deuill/$(NAME) 5 | VERSION := $(shell git describe --tags --always --dirty="-dev") 6 | 7 | # Generic build options. 8 | PHP_VERSION := 7.0.30 9 | STATIC := false 10 | DOCKER_IMAGE := deuill/$(NAME):$(PHP_VERSION) 11 | 12 | # Go build options. 13 | GO := go 14 | TAGS := -tags 'php$(word 1,$(subst ., ,$(PHP_VERSION))) $(if $(findstring true,$(STATIC)),static)' 15 | 16 | # Install options. 17 | PREFIX := /usr 18 | 19 | # Default Makefile options. 20 | VERBOSE := 21 | 22 | # Variables to pass down to sub-invocations of 'make'. 23 | MAKE_OPTIONS := PHP_VERSION=$(PHP_VERSION) GO=$(GO) PREFIX=$(PREFIX) VERBOSE=$(VERBOSE) STATIC=$(STATIC) 24 | 25 | ## Build binary distribution for library. 26 | build: .build/env/GOPATH/.ok 27 | @echo "Building '$(NAME)'..." 28 | $Q $(GO) install $(if $(VERBOSE),-v) $(TAGS) $(IMPORT_PATH) 29 | 30 | ## Run test for all local packages or specified PACKAGE. 31 | test: .build/env/GOPATH/.ok 32 | @echo "Running tests for '$(NAME)'..." 33 | $Q $(GO) test -race $(if $(VERBOSE),-v) $(TAGS) $(if $(PACKAGE),$(PACKAGE),$(PACKAGES)) 34 | @echo "Running 'vet' for '$(NAME)'..." 35 | $Q $(GO) vet $(if $(VERBOSE),-v) $(TAGS) $(if $(PACKAGE),$(PACKAGE),$(PACKAGES)) 36 | 37 | ## Create test coverage report for all local packages or specified PACKAGE. 38 | cover: .build/env/GOPATH/.ok 39 | @echo "Creating code coverage report for '$(NAME)'..." 40 | $Q rm -Rf .build/tmp && mkdir -p .build/tmp 41 | $Q for pkg in $(if $(PACKAGE),$(PACKAGE),$(PACKAGES)); do \ 42 | name=`echo $$pkg.cover | tr '/' '.'`; \ 43 | imports=`go list -f '{{ join .Imports " " }}' $$pkg`; \ 44 | coverpkg=`echo "$$imports $(PACKAGES)" | tr ' ' '\n' | sort | uniq -d | tr '\n' ','`; \ 45 | $(GO) test $(if $(VERBOSE),-v) $(TAGS) -coverpkg $$coverpkg$$pkg -coverprofile .build/tmp/$$name $$pkg; done 46 | $Q awk "$$COVERAGE_MERGE" .build/tmp/*.cover > .build/tmp/cover.merged 47 | $Q $(GO) tool cover -html .build/tmp/cover.merged -o .build/tmp/coverage.html 48 | @echo "Coverage report written to '.build/tmp/coverage.html'" 49 | @echo "Total coverage for '$(NAME)':" 50 | $Q $(GO) tool cover -func .build/tmp/cover.merged 51 | 52 | ## Remove temporary files and packages required for build. 53 | clean: 54 | @echo "Cleaning '$(NAME)'..." 55 | $Q $(GO) clean 56 | $Q rm -Rf .build 57 | 58 | ## Show usage information for this Makefile. 59 | help: 60 | @printf "$(BOLD)$(DESCRIPTION)$(RESET)\n\n" 61 | @printf "This Makefile contains tasks for processing auxiliary actions, such as\n" 62 | @printf "building binaries, packages, or running tests against the test suite.\n\n" 63 | @printf "$(UNDERLINE)Available Tasks$(RESET)\n\n" 64 | @awk -F \ 65 | ':|##' '/^##/ {c=$$2; getline; printf "$(BLUE)%10s$(RESET) %s\n", $$1, c}' \ 66 | $(MAKEFILE_LIST) 67 | @printf "\n" 68 | 69 | .PHONY: build test cover clean 70 | 71 | .DEFAULT: 72 | $Q $(MAKE) -s -f $(MAKEFILE) help 73 | 74 | # Pull or build Docker image for PHP version specified. 75 | docker-image: 76 | $Q docker image pull $(DOCKER_IMAGE) || \ 77 | docker build --build-arg=PHP_VERSION=$(PHP_VERSION) --build-arg=STATIC=$(STATIC) \ 78 | -t $(DOCKER_IMAGE) -f Dockerfile . \ 79 | 80 | # Run Make target in Docker container. For instance, to run 'test', call as 'docker-test'. 81 | docker-%: docker-image 82 | $Q docker run --rm -e GOPATH="/tmp/go" \ 83 | -v "$(CURDIR):/tmp/go/src/$(IMPORT_PATH)" $(DOCKER_IMAGE) \ 84 | "$(MAKE) -C /tmp/go/src/$(IMPORT_PATH) $(word 2,$(subst -, ,$@)) $(MAKE_OPTIONS)" 85 | 86 | .build/env/GOPATH/.ok: 87 | $Q mkdir -p "$(dir .build/env/GOPATH/src/$(IMPORT_PATH))" && touch $@ 88 | $Q ln -s ../../../../../.. ".build/env/GOPATH/src/$(IMPORT_PATH)" 89 | 90 | MAKEFILE := $(lastword $(MAKEFILE_LIST)) 91 | Q := $(if $(VERBOSE),,@) 92 | 93 | PACKAGES = $(shell ( \ 94 | cd $(CURDIR)/.build/env/GOPATH/src/$(IMPORT_PATH) && \ 95 | GOPATH=$(CURDIR)/.build/env/GOPATH go list ./... | grep -v "vendor" \ 96 | )) 97 | 98 | export GOPATH := $(CURDIR)/.build/env/GOPATH 99 | 100 | BOLD = \033[1m 101 | UNDERLINE = \033[4m 102 | BLUE = \033[36m 103 | RESET = \033[0m 104 | 105 | define COVERAGE_MERGE 106 | /^mode: (set|count|atomic)/ { 107 | if ($$2 == "set") mode = "set" 108 | next 109 | } 110 | /^mode: / { 111 | printf "Unknown mode '%s' in %s, line %d", $$2, FILENAME, FNR | "cat >&2" 112 | exit 1 113 | } 114 | { 115 | val = $$NF; $$NF = "" 116 | blocks[$$0] += val 117 | } 118 | END { 119 | printf "mode: %s\n", (mode == "set") ? "set" : "count" 120 | for (b in blocks) { 121 | printf "%s%d\n", b, (mode == "set" && blocks[b] > 1) ? 1 : blocks[b] 122 | } 123 | } 124 | endef 125 | export COVERAGE_MERGE 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP bindings for Go 2 | 3 | [![API Documentation][godoc-svg]][godoc-url] [![MIT License][license-svg]][license-url] 4 | 5 | This package implements support for executing PHP scripts, exporting Go variables for use in PHP contexts, attaching Go method receivers as PHP classes and returning PHP variables for use in Go contexts. 6 | 7 | Both PHP 5.x and PHP 7.x series are supported. 8 | 9 | ## Building 10 | 11 | Building this package requires that you have PHP installed as a library. For most Linux systems, this can usually be found in the `php-embed` package, or variations thereof. 12 | 13 | Once the PHP library is available, the bindings can be compiled with `go build` and are `go get`-able. 14 | 15 | **Note**: Building against PHP 5.x requires that the `php5` tag is provided, i.e.: 16 | 17 | ```bash 18 | go get -tags php5 github.com/deuill/go-php 19 | ``` 20 | 21 | This is due to the fact that PHP 7.x is the default build target. 22 | 23 | ## Status 24 | 25 | Executing PHP [script files][Context.Exec] as well as [inline strings][Context.Eval] is supported and stable. 26 | 27 | [Binding Go values][NewValue] as PHP variables is allowed for most base types, and PHP values returned from eval'd strings can be converted and used in Go contexts as `interface{}` values. 28 | 29 | It is possible to [attach Go method receivers][NewReceiver] as PHP classes, with full support for calling expored methods, as well as getting and setting embedded fields (for `struct`-type method receivers). 30 | 31 | ### Caveats 32 | 33 | Be aware that, by default, PHP is **not** designed to be used in multithreaded environments (which severely restricts the use of these bindings with Goroutines) if not built with [ZTS support](https://secure.php.net/manual/en/pthreads.requirements.php). However, ZTS support has seen major refactoring between PHP 5 and PHP 7, and as such is currently unsupported by this package. 34 | 35 | Currently, it is recommended to either sync use of seperate Contexts between Goroutines, or share a single Context among all running Goroutines. 36 | 37 | ## Roadmap 38 | 39 | Currently, the package lacks in several respects: 40 | 41 | * ZTS/multi-threading support. This basically means using Go-PHP in Goroutines is severely limited. 42 | * Documentation and examples, both package-level and external. 43 | * Performance. There's no reason to believe Go-PHP suffers from any serious performance issues in particular, but adding benchmarks, especially compared against vanilla PHP, might help. 44 | * Your feature request here? 45 | 46 | These items will be tackled in order of significance (which may not be the order shown above). 47 | 48 | ## Usage 49 | 50 | ### Basic 51 | 52 | Executing a script is simple: 53 | 54 | ```go 55 | package main 56 | 57 | import ( 58 | php "github.com/deuill/go-php" 59 | "os" 60 | ) 61 | 62 | func main() { 63 | engine, _ := php.New() 64 | 65 | context, _ := engine.NewContext() 66 | context.Output = os.Stdout 67 | 68 | context.Exec("index.php") 69 | engine.Destroy() 70 | } 71 | ``` 72 | 73 | The above will execute script file `index.php` located in the current folder and will write any output to the `io.Writer` assigned to `Context.Output` (in this case, the standard output). 74 | 75 | ### Binding and returning variables 76 | 77 | The following example demonstrates binding a Go variable to the running PHP context, and returning a PHP variable for use in Go: 78 | 79 | ```go 80 | package main 81 | 82 | import ( 83 | "fmt" 84 | php "github.com/deuill/go-php" 85 | ) 86 | 87 | func main() { 88 | engine, _ := php.New() 89 | context, _ := engine.NewContext() 90 | 91 | var str string = "Hello" 92 | context.Bind("var", str) 93 | 94 | val, _ := context.Eval("return $var.' World';") 95 | fmt.Printf("%s", val.Interface()) 96 | // Prints 'Hello World' back to the user. 97 | 98 | engine.Destroy() 99 | } 100 | ``` 101 | 102 | A string value "Hello" is attached using `Context.Bind` under a name `var` (available in PHP as `$var`). A script is executed inline using `Context.Eval`, combinding the attached value with a PHP string and returning it to the user. 103 | 104 | Finally, the value is returned as an `interface{}` using `Value.Interface()` (one could also use `Value.String()`, though the both are equivalent in this case). 105 | 106 | ## License 107 | 108 | All code in this repository is covered by the terms of the MIT License, the full text of which can be found in the LICENSE file. 109 | 110 | [godoc-url]: https://godoc.org/github.com/deuill/go-php 111 | [godoc-svg]: https://godoc.org/github.com/deuill/go-php?status.svg 112 | 113 | [license-url]: https://github.com/deuill/go-php/blob/master/LICENSE 114 | [license-svg]: https://img.shields.io/badge/license-MIT-blue.svg 115 | 116 | [Context.Exec]: https://godoc.org/github.com/deuill/go-php/engine#Context.Exec 117 | [Context.Eval]: https://godoc.org/github.com/deuill/go-php/engine#Context.Eval 118 | [NewValue]: https://godoc.org/github.com/deuill/go-php/engine#NewValue 119 | [NewReceiver]: https://godoc.org/github.com/deuill/go-php/engine#NewReceiver 120 | -------------------------------------------------------------------------------- /context.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #include 6 | #include 7 | 8 | #include
9 | #include
10 | 11 | #include "value.h" 12 | #include "context.h" 13 | 14 | engine_context *context_new() { 15 | engine_context *context; 16 | 17 | // Initialize context. 18 | context = malloc((sizeof(engine_context))); 19 | if (context == NULL) { 20 | errno = 1; 21 | return NULL; 22 | } 23 | 24 | SG(server_context) = context; 25 | 26 | // Initialize request lifecycle. 27 | if (php_request_startup() == FAILURE) { 28 | SG(server_context) = NULL; 29 | free(context); 30 | 31 | errno = 1; 32 | return NULL; 33 | } 34 | 35 | errno = 0; 36 | return context; 37 | } 38 | 39 | void context_exec(engine_context *context, char *filename) { 40 | int ret; 41 | 42 | // Attempt to execute script file. 43 | zend_first_try { 44 | zend_file_handle script; 45 | 46 | script.type = ZEND_HANDLE_FILENAME; 47 | script.filename = filename; 48 | script.opened_path = NULL; 49 | script.free_filename = 0; 50 | 51 | ret = php_execute_script(&script); 52 | } zend_catch { 53 | errno = 1; 54 | return; 55 | } zend_end_try(); 56 | 57 | if (ret == FAILURE) { 58 | errno = 1; 59 | return; 60 | } 61 | 62 | errno = 0; 63 | return; 64 | } 65 | 66 | void *context_eval(engine_context *context, char *script) { 67 | zval *str = _value_init(); 68 | _value_set_string(&str, script); 69 | 70 | // Compile script value. 71 | uint32_t compiler_options = CG(compiler_options); 72 | 73 | CG(compiler_options) = ZEND_COMPILE_DEFAULT_FOR_EVAL; 74 | zend_op_array *op = zend_compile_string(str, "gophp-engine"); 75 | CG(compiler_options) = compiler_options; 76 | 77 | zval_dtor(str); 78 | 79 | // Return error if script failed to compile. 80 | if (!op) { 81 | errno = 1; 82 | return NULL; 83 | } 84 | 85 | // Attempt to execute compiled string. 86 | zval tmp; 87 | _context_eval(op, &tmp); 88 | 89 | // Allocate result value and copy temporary execution result in. 90 | zval *result = malloc(sizeof(zval)); 91 | value_copy(result, &tmp); 92 | 93 | errno = 0; 94 | return result; 95 | } 96 | 97 | void context_bind(engine_context *context, char *name, void *value) { 98 | engine_value *v = (engine_value *) value; 99 | _context_bind(name, v->internal); 100 | } 101 | 102 | void context_destroy(engine_context *context) { 103 | php_request_shutdown(NULL); 104 | 105 | SG(server_context) = NULL; 106 | free(context); 107 | } 108 | 109 | #include "_context.c" 110 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | package php 6 | 7 | // #cgo CFLAGS: -I/usr/include/php -I/usr/include/php/main -I/usr/include/php/TSRM 8 | // #cgo CFLAGS: -I/usr/include/php/Zend -Iinclude 9 | // 10 | // #include 11 | // #include
12 | // #include "context.h" 13 | import "C" 14 | 15 | import ( 16 | "fmt" 17 | "io" 18 | "net/http" 19 | "unsafe" 20 | ) 21 | 22 | // Context represents an individual execution context. 23 | type Context struct { 24 | // Output and Log are unbuffered writers used for regular and debug output, 25 | // respectively. If left unset, any data written into either by the calling 26 | // context will be lost. 27 | Output io.Writer 28 | Log io.Writer 29 | 30 | // Header represents the HTTP headers set by current PHP context. 31 | Header http.Header 32 | 33 | context *C.struct__engine_context 34 | values []*Value 35 | } 36 | 37 | // Bind allows for binding Go values into the current execution context under 38 | // a certain name. Bind returns an error if attempting to bind an invalid value 39 | // (check the documentation for NewValue for what is considered to be a "valid" 40 | // value). 41 | func (c *Context) Bind(name string, val interface{}) error { 42 | v, err := NewValue(val) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | n := C.CString(name) 48 | defer C.free(unsafe.Pointer(n)) 49 | 50 | C.context_bind(c.context, n, v.Ptr()) 51 | c.values = append(c.values, v) 52 | 53 | return nil 54 | } 55 | 56 | // Exec executes a PHP script pointed to by filename in the current execution 57 | // context, and returns an error, if any. Output produced by the script is 58 | // written to the context's pre-defined io.Writer instance. 59 | func (c *Context) Exec(filename string) error { 60 | f := C.CString(filename) 61 | defer C.free(unsafe.Pointer(f)) 62 | 63 | _, err := C.context_exec(c.context, f) 64 | if err != nil { 65 | return fmt.Errorf("Error executing script '%s' in context", filename) 66 | } 67 | 68 | return nil 69 | } 70 | 71 | // Eval executes the PHP expression contained in script, and returns a Value 72 | // containing the PHP value returned by the expression, if any. Any output 73 | // produced is written context's pre-defined io.Writer instance. 74 | func (c *Context) Eval(script string) (*Value, error) { 75 | s := C.CString(script) 76 | defer C.free(unsafe.Pointer(s)) 77 | 78 | result, err := C.context_eval(c.context, s) 79 | if err != nil { 80 | return nil, fmt.Errorf("Error executing script '%s' in context", script) 81 | } 82 | 83 | defer C.free(result) 84 | 85 | val, err := NewValueFromPtr(result) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | c.values = append(c.values, val) 91 | 92 | return val, nil 93 | } 94 | 95 | // Destroy tears down the current execution context along with any active value 96 | // bindings for that context. 97 | func (c *Context) Destroy() { 98 | if c.context == nil { 99 | return 100 | } 101 | 102 | for _, v := range c.values { 103 | v.Destroy() 104 | } 105 | 106 | c.values = nil 107 | 108 | C.context_destroy(c.context) 109 | c.context = nil 110 | } 111 | -------------------------------------------------------------------------------- /context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | package php 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "net/http" 11 | "reflect" 12 | "strings" 13 | "testing" 14 | ) 15 | 16 | func TestContextStart(t *testing.T) { 17 | e, _ = New() 18 | t.SkipNow() 19 | } 20 | 21 | func TestContextNew(t *testing.T) { 22 | c, err := e.NewContext() 23 | if err != nil { 24 | t.Fatalf("NewContext(): %s", err) 25 | } 26 | 27 | if c.context == nil || c.Header == nil || c.values == nil { 28 | t.Fatalf("NewContext(): Struct fields are `nil` but no error returned") 29 | } 30 | 31 | c.Destroy() 32 | } 33 | 34 | var execTests = []struct { 35 | name string 36 | script string 37 | expected string 38 | }{ 39 | { 40 | "helloworld.php", 41 | 42 | ` 6 | #include 7 | 8 | #include
9 | #include
10 | #include
11 | #include
12 | 13 | #include "context.h" 14 | #include "engine.h" 15 | #include "_cgo_export.h" 16 | 17 | // The php.ini defaults for the Go-PHP engine. 18 | const char engine_ini_defaults[] = { 19 | "expose_php = 0\n" 20 | "default_mimetype =\n" 21 | "html_errors = 0\n" 22 | "register_argc_argv = 1\n" 23 | "implicit_flush = 1\n" 24 | "output_buffering = 0\n" 25 | "max_execution_time = 0\n" 26 | "max_input_time = -1\n\0" 27 | }; 28 | 29 | static int engine_ub_write(const char *str, uint len) { 30 | engine_context *context = SG(server_context); 31 | 32 | int written = engineWriteOut(context, (void *) str, len); 33 | if (written != len) { 34 | php_handle_aborted_connection(); 35 | } 36 | 37 | return len; 38 | } 39 | 40 | static int engine_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers) { 41 | engine_context *context = SG(server_context); 42 | 43 | switch (op) { 44 | case SAPI_HEADER_REPLACE: 45 | case SAPI_HEADER_ADD: 46 | case SAPI_HEADER_DELETE: 47 | engineSetHeader(context, op, (void *) sapi_header->header, sapi_header->header_len); 48 | break; 49 | } 50 | 51 | return 0; 52 | } 53 | 54 | static void engine_send_header(sapi_header_struct *sapi_header, void *server_context) { 55 | // Do nothing. 56 | } 57 | 58 | static char *engine_read_cookies() { 59 | return NULL; 60 | } 61 | 62 | static void engine_register_variables(zval *track_vars_array) { 63 | php_import_environment_variables(track_vars_array); 64 | } 65 | 66 | #if PHP_VERSION_ID < 70100 67 | static void engine_log_message(char *str) { 68 | #else 69 | static void engine_log_message(char *str, int syslog_type_int) { 70 | #endif 71 | engine_context *context = SG(server_context); 72 | 73 | engineWriteLog(context, (void *) str, strlen(str)); 74 | } 75 | 76 | static sapi_module_struct engine_module = { 77 | "gophp-engine", // Name 78 | "Go PHP Engine Library", // Pretty Name 79 | 80 | NULL, // Startup 81 | php_module_shutdown_wrapper, // Shutdown 82 | 83 | NULL, // Activate 84 | NULL, // Deactivate 85 | 86 | _engine_ub_write, // Unbuffered Write 87 | NULL, // Flush 88 | NULL, // Get UID 89 | NULL, // Getenv 90 | 91 | php_error, // Error Handler 92 | 93 | engine_header_handler, // Header Handler 94 | NULL, // Send Headers Handler 95 | engine_send_header, // Send Header Handler 96 | 97 | NULL, // Read POST Data 98 | engine_read_cookies, // Read Cookies 99 | 100 | engine_register_variables, // Register Server Variables 101 | engine_log_message, // Log Message 102 | NULL, // Get Request Time 103 | NULL, // Child Terminate 104 | 105 | STANDARD_SAPI_MODULE_PROPERTIES 106 | }; 107 | 108 | php_engine *engine_init(void) { 109 | php_engine *engine; 110 | 111 | #ifdef HAVE_SIGNAL_H 112 | #if defined(SIGPIPE) && defined(SIG_IGN) 113 | signal(SIGPIPE, SIG_IGN); 114 | #endif 115 | #endif 116 | 117 | sapi_startup(&engine_module); 118 | 119 | engine_module.ini_entries = malloc(sizeof(engine_ini_defaults)); 120 | memcpy(engine_module.ini_entries, engine_ini_defaults, sizeof(engine_ini_defaults)); 121 | 122 | if (php_module_startup(&engine_module, NULL, 0) == FAILURE) { 123 | sapi_shutdown(); 124 | 125 | errno = 1; 126 | return NULL; 127 | } 128 | 129 | engine = malloc((sizeof(php_engine))); 130 | 131 | errno = 0; 132 | return engine; 133 | } 134 | 135 | void engine_shutdown(php_engine *engine) { 136 | php_module_shutdown(); 137 | sapi_shutdown(); 138 | 139 | free(engine_module.ini_entries); 140 | free(engine); 141 | } 142 | 143 | #include "_engine.c" 144 | -------------------------------------------------------------------------------- /engine.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | // Package engine provides methods allowing for the initialization and teardown 6 | // of PHP engine bindings, off which execution contexts can be launched. 7 | package php 8 | 9 | // #cgo CFLAGS: -I/usr/include/php -I/usr/include/php/main -I/usr/include/php/TSRM 10 | // #cgo CFLAGS: -I/usr/include/php/Zend -Iinclude 11 | // 12 | // #include 13 | // #include
14 | // #include "receiver.h" 15 | // #include "context.h" 16 | // #include "engine.h" 17 | import "C" 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "net/http" 23 | "strings" 24 | "unsafe" 25 | ) 26 | 27 | // Engine represents the core PHP engine bindings. 28 | type Engine struct { 29 | engine *C.struct__php_engine 30 | contexts map[*C.struct__engine_context]*Context 31 | receivers map[string]*Receiver 32 | } 33 | 34 | // This contains a reference to the active engine, if any. 35 | var engine *Engine 36 | 37 | // New initializes a PHP engine instance on which contexts can be executed. It 38 | // corresponds to PHP's MINIT (module init) phase. 39 | func New() (*Engine, error) { 40 | if engine != nil { 41 | return nil, fmt.Errorf("Cannot activate multiple engine instances") 42 | } 43 | 44 | ptr, err := C.engine_init() 45 | if err != nil { 46 | return nil, fmt.Errorf("PHP engine failed to initialize") 47 | } 48 | 49 | engine = &Engine{ 50 | engine: ptr, 51 | contexts: make(map[*C.struct__engine_context]*Context), 52 | receivers: make(map[string]*Receiver), 53 | } 54 | 55 | return engine, nil 56 | } 57 | 58 | // NewContext creates a new execution context for the active engine and returns 59 | // an error if the execution context failed to initialize at any point. This 60 | // corresponds to PHP's RINIT (request init) phase. 61 | func (e *Engine) NewContext() (*Context, error) { 62 | ptr, err := C.context_new() 63 | if err != nil { 64 | return nil, fmt.Errorf("Failed to initialize context for PHP engine") 65 | } 66 | 67 | ctx := &Context{ 68 | Header: make(http.Header), 69 | context: ptr, 70 | values: make([]*Value, 0), 71 | } 72 | 73 | // Store reference to context, using pointer as key. 74 | e.contexts[ptr] = ctx 75 | 76 | return ctx, nil 77 | } 78 | 79 | // Define registers a PHP class for the name passed, using function fn as 80 | // constructor for individual object instances as needed by the PHP context. 81 | // 82 | // The class name registered is assumed to be unique for the active engine. 83 | // 84 | // The constructor function accepts a slice of arguments, as passed by the PHP 85 | // context, and should return a method receiver instance, or nil on error (in 86 | // which case, an exception is thrown on the PHP object constructor). 87 | func (e *Engine) Define(name string, fn func(args []interface{}) interface{}) error { 88 | if _, exists := e.receivers[name]; exists { 89 | return fmt.Errorf("Failed to define duplicate receiver '%s'", name) 90 | } 91 | 92 | rcvr := &Receiver{ 93 | name: name, 94 | create: fn, 95 | objects: make(map[*C.struct__engine_receiver]*ReceiverObject), 96 | } 97 | 98 | n := C.CString(name) 99 | defer C.free(unsafe.Pointer(n)) 100 | 101 | C.receiver_define(n) 102 | e.receivers[name] = rcvr 103 | 104 | return nil 105 | } 106 | 107 | // Destroy shuts down and frees any resources related to the PHP engine bindings. 108 | func (e *Engine) Destroy() { 109 | if e.engine == nil { 110 | return 111 | } 112 | 113 | for _, r := range e.receivers { 114 | r.Destroy() 115 | } 116 | 117 | e.receivers = nil 118 | 119 | for _, c := range e.contexts { 120 | c.Destroy() 121 | } 122 | 123 | e.contexts = nil 124 | 125 | C.engine_shutdown(e.engine) 126 | e.engine = nil 127 | 128 | engine = nil 129 | } 130 | 131 | func write(w io.Writer, buffer unsafe.Pointer, length C.uint) C.int { 132 | // Do not return error if writer is unavailable. 133 | if w == nil { 134 | return C.int(length) 135 | } 136 | 137 | written, err := w.Write(C.GoBytes(buffer, C.int(length))) 138 | if err != nil { 139 | return -1 140 | } 141 | 142 | return C.int(written) 143 | } 144 | 145 | //export engineWriteOut 146 | func engineWriteOut(ctx *C.struct__engine_context, buffer unsafe.Pointer, length C.uint) C.int { 147 | if engine == nil || engine.contexts[ctx] == nil { 148 | return -1 149 | } 150 | 151 | return write(engine.contexts[ctx].Output, buffer, length) 152 | } 153 | 154 | //export engineWriteLog 155 | func engineWriteLog(ctx *C.struct__engine_context, buffer unsafe.Pointer, length C.uint) C.int { 156 | if engine == nil || engine.contexts[ctx] == nil { 157 | return -1 158 | } 159 | 160 | return write(engine.contexts[ctx].Log, buffer, length) 161 | } 162 | 163 | //export engineSetHeader 164 | func engineSetHeader(ctx *C.struct__engine_context, operation C.uint, buffer unsafe.Pointer, length C.uint) { 165 | if engine == nil || engine.contexts[ctx] == nil { 166 | return 167 | } 168 | 169 | header := (string)(C.GoBytes(buffer, C.int(length))) 170 | split := strings.SplitN(header, ":", 2) 171 | 172 | for i := range split { 173 | split[i] = strings.TrimSpace(split[i]) 174 | } 175 | 176 | switch operation { 177 | case 0: // Replace header. 178 | if len(split) == 2 && split[1] != "" { 179 | engine.contexts[ctx].Header.Set(split[0], split[1]) 180 | } 181 | case 1: // Append header. 182 | if len(split) == 2 && split[1] != "" { 183 | engine.contexts[ctx].Header.Add(split[0], split[1]) 184 | } 185 | case 2: // Delete header. 186 | if split[0] != "" { 187 | engine.contexts[ctx].Header.Del(split[0]) 188 | } 189 | } 190 | } 191 | 192 | //export engineReceiverNew 193 | func engineReceiverNew(rcvr *C.struct__engine_receiver, args unsafe.Pointer) C.int { 194 | n := C.GoString(C._receiver_get_name(rcvr)) 195 | if engine == nil || engine.receivers[n] == nil { 196 | return 1 197 | } 198 | 199 | va, err := NewValueFromPtr(args) 200 | if err != nil { 201 | return 1 202 | } 203 | 204 | defer va.Destroy() 205 | 206 | obj, err := engine.receivers[n].NewObject(va.Slice()) 207 | if err != nil { 208 | return 1 209 | } 210 | 211 | engine.receivers[n].objects[rcvr] = obj 212 | 213 | return 0 214 | } 215 | 216 | //export engineReceiverGet 217 | func engineReceiverGet(rcvr *C.struct__engine_receiver, name *C.char) unsafe.Pointer { 218 | n := C.GoString(C._receiver_get_name(rcvr)) 219 | if engine == nil || engine.receivers[n].objects[rcvr] == nil { 220 | return nil 221 | } 222 | 223 | val, err := engine.receivers[n].objects[rcvr].Get(C.GoString(name)) 224 | if err != nil { 225 | return nil 226 | } 227 | 228 | return val.Ptr() 229 | } 230 | 231 | //export engineReceiverSet 232 | func engineReceiverSet(rcvr *C.struct__engine_receiver, name *C.char, val unsafe.Pointer) { 233 | n := C.GoString(C._receiver_get_name(rcvr)) 234 | if engine == nil || engine.receivers[n].objects[rcvr] == nil { 235 | return 236 | } 237 | 238 | v, err := NewValueFromPtr(val) 239 | if err != nil { 240 | return 241 | } 242 | 243 | engine.receivers[n].objects[rcvr].Set(C.GoString(name), v.Interface()) 244 | } 245 | 246 | //export engineReceiverExists 247 | func engineReceiverExists(rcvr *C.struct__engine_receiver, name *C.char) C.int { 248 | n := C.GoString(C._receiver_get_name(rcvr)) 249 | if engine == nil || engine.receivers[n].objects[rcvr] == nil { 250 | return 0 251 | } 252 | 253 | if engine.receivers[n].objects[rcvr].Exists(C.GoString(name)) { 254 | return 1 255 | } 256 | 257 | return 0 258 | } 259 | 260 | //export engineReceiverCall 261 | func engineReceiverCall(rcvr *C.struct__engine_receiver, name *C.char, args unsafe.Pointer) unsafe.Pointer { 262 | n := C.GoString(C._receiver_get_name(rcvr)) 263 | if engine == nil || engine.receivers[n].objects[rcvr] == nil { 264 | return nil 265 | } 266 | 267 | // Process input arguments. 268 | va, err := NewValueFromPtr(args) 269 | if err != nil { 270 | return nil 271 | } 272 | 273 | defer va.Destroy() 274 | 275 | val := engine.receivers[n].objects[rcvr].Call(C.GoString(name), va.Slice()) 276 | if val == nil { 277 | return nil 278 | } 279 | 280 | return val.Ptr() 281 | } 282 | -------------------------------------------------------------------------------- /engine_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | package php 6 | 7 | import ( 8 | "io/ioutil" 9 | "os" 10 | "testing" 11 | ) 12 | 13 | type Script struct { 14 | *os.File 15 | } 16 | 17 | func NewScript(name, contents string) (*Script, error) { 18 | file, err := ioutil.TempFile("", name) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | if _, err := file.WriteString(contents); err != nil { 24 | file.Close() 25 | os.Remove(file.Name()) 26 | 27 | return nil, err 28 | } 29 | 30 | return &Script{file}, nil 31 | } 32 | 33 | func (s *Script) Remove() { 34 | s.Close() 35 | os.Remove(s.Name()) 36 | } 37 | 38 | var e *Engine 39 | 40 | func TestEngineNew(t *testing.T) { 41 | var err error 42 | 43 | if e, err = New(); err != nil { 44 | t.Fatalf("New(): %s", err) 45 | } 46 | 47 | if e.engine == nil || e.contexts == nil || e.receivers == nil { 48 | t.Fatalf("New(): Struct fields are `nil` but no error returned") 49 | } 50 | } 51 | 52 | func TestEngineNewContext(t *testing.T) { 53 | _, err := e.NewContext() 54 | if err != nil { 55 | t.Errorf("NewContext(): %s", err) 56 | } 57 | 58 | if len(e.contexts) != 1 { 59 | t.Errorf("NewContext(): `Engine.contexts` length is %d, should be 1", len(e.contexts)) 60 | } 61 | } 62 | 63 | func TestEngineDefine(t *testing.T) { 64 | ctor := func(args []interface{}) interface{} { 65 | return nil 66 | } 67 | 68 | if err := e.Define("TestDefine", ctor); err != nil { 69 | t.Errorf("Engine.Define(): %s", err) 70 | } 71 | 72 | if len(e.receivers) != 1 { 73 | t.Errorf("Engine.Define(): `Engine.receivers` length is %d, should be 1", len(e.receivers)) 74 | } 75 | 76 | if err := e.Define("TestDefine", ctor); err == nil { 77 | t.Errorf("Engine.Define(): Incorrectly defined duplicate receiver") 78 | } 79 | } 80 | 81 | func TestEngineDestroy(t *testing.T) { 82 | e.Destroy() 83 | 84 | if e.engine != nil || e.contexts != nil || e.receivers != nil { 85 | t.Errorf("Engine.Destroy(): Did not set internal fields to `nil`") 86 | } 87 | 88 | // Attempting to destroy an engine instance twice should be a no-op. 89 | e.Destroy() 90 | } 91 | -------------------------------------------------------------------------------- /include/context.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef __CONTEXT_H__ 6 | #define __CONTEXT_H__ 7 | 8 | typedef struct _engine_context { 9 | } engine_context; 10 | 11 | engine_context *context_new(); 12 | void context_exec(engine_context *context, char *filename); 13 | void *context_eval(engine_context *context, char *script); 14 | void context_bind(engine_context *context, char *name, void *value); 15 | void context_destroy(engine_context *context); 16 | 17 | #include "_context.h" 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /include/engine.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef __ENGINE_H__ 6 | #define __ENGINE_H__ 7 | 8 | typedef struct _php_engine { 9 | } php_engine; 10 | 11 | php_engine *engine_init(void); 12 | void engine_shutdown(php_engine *engine); 13 | 14 | #include "_engine.h" 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/php5/_context.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef ___CONTEXT_H___ 6 | #define ___CONTEXT_H___ 7 | 8 | static void _context_bind(char *name, zval *value); 9 | static void _context_eval(zend_op_array *op, zval *ret); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /include/php5/_engine.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef ___ENGINE_H___ 6 | #define ___ENGINE_H___ 7 | 8 | static int _engine_ub_write(const char *str, uint len); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/php5/_receiver.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef ___RECEIVER_H___ 6 | #define ___RECEIVER_H___ 7 | 8 | static zval *_receiver_get(zval *object, zval *member, int type, const zend_literal *key); 9 | static void _receiver_set(zval *object, zval *member, zval *value, const zend_literal *key); 10 | static int _receiver_exists(zval *object, zval *member, int check, const zend_literal *key); 11 | 12 | static int _receiver_method_call(const char *method, INTERNAL_FUNCTION_PARAMETERS); 13 | static zend_function *_receiver_method_get(zval **object, char *name, int len, const zend_literal *key); 14 | static zend_function *_receiver_constructor_get(zval *object); 15 | 16 | static void _receiver_free(void *object); 17 | static zend_object_value _receiver_init(zend_class_entry *class_type); 18 | static void _receiver_destroy(char *name); 19 | 20 | static engine_receiver *_receiver_this(zval *object); 21 | static void _receiver_handlers_set(zend_object_handlers *handlers); 22 | char *_receiver_get_name(engine_receiver *rcvr); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/php5/_value.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef ___VALUE_H___ 6 | #define ___VALUE_H___ 7 | 8 | zval *_value_init(); 9 | void _value_destroy(engine_value *val); 10 | 11 | int _value_truth(zval *val); 12 | void _value_set_string(zval **val, char *str); 13 | 14 | static int _value_current_key_get(HashTable *ht, char **str_index, ulong *num_index); 15 | static void _value_current_key_set(HashTable *ht, engine_value *val); 16 | 17 | static void _value_array_next_get(HashTable *ht, engine_value *val); 18 | static void _value_array_index_get(HashTable *ht, unsigned long index, engine_value *val); 19 | static void _value_array_key_get(HashTable *ht, char *key, engine_value *val); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /include/php7/_context.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef ___CONTEXT_H___ 6 | #define ___CONTEXT_H___ 7 | 8 | static void _context_bind(char *name, zval *value); 9 | static void _context_eval(zend_op_array *op, zval *ret); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /include/php7/_engine.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef ___ENGINE_H___ 6 | #define ___ENGINE_H___ 7 | 8 | static size_t _engine_ub_write(const char *str, size_t len); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/php7/_receiver.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef ___RECEIVER_H___ 6 | #define ___RECEIVER_H___ 7 | 8 | static zval *_receiver_get(zval *object, zval *member, int type, void **cache_slot, zval *retval); 9 | static void _receiver_set(zval *object, zval *member, zval *value, void **cache_slot); 10 | static int _receiver_exists(zval *object, zval *member, int check, void **cache_slot); 11 | 12 | static int _receiver_method_call(zend_string *method, zend_object *object, INTERNAL_FUNCTION_PARAMETERS); 13 | static zend_function *_receiver_method_get(zend_object **object, zend_string *name, const zval *key); 14 | static zend_function *_receiver_constructor_get(zend_object *object); 15 | 16 | static void _receiver_free(zend_object *object); 17 | static zend_object *_receiver_init(zend_class_entry *class_type); 18 | static void _receiver_destroy(char *name); 19 | 20 | static engine_receiver *_receiver_this(zval *object); 21 | static void _receiver_handlers_set(zend_object_handlers *handlers); 22 | char *_receiver_get_name(engine_receiver *rcvr); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/php7/_value.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef ___VALUE_H___ 6 | #define ___VALUE_H___ 7 | 8 | zval *_value_init(); 9 | void _value_destroy(engine_value *val); 10 | 11 | int _value_truth(zval *val); 12 | void _value_set_string(zval **val, char *str); 13 | 14 | static int _value_current_key_get(HashTable *ht, zend_string **str_index, zend_ulong *num_index); 15 | static void _value_current_key_set(HashTable *ht, engine_value *val); 16 | 17 | static void _value_array_next_get(HashTable *ht, engine_value *val); 18 | static void _value_array_index_get(HashTable *ht, unsigned long index, engine_value *val); 19 | static void _value_array_key_get(HashTable *ht, char *key, engine_value *val); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /include/receiver.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef __RECEIVER_H__ 6 | #define __RECEIVER_H__ 7 | 8 | typedef struct _engine_receiver { 9 | zend_object obj; 10 | } engine_receiver; 11 | 12 | void receiver_define(char *name); 13 | void receiver_destroy(char *name); 14 | 15 | #include "_receiver.h" 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /include/value.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #ifndef __VALUE_H__ 6 | #define __VALUE_H__ 7 | 8 | typedef struct _engine_value { 9 | zval *internal; 10 | int kind; 11 | } engine_value; 12 | 13 | enum { 14 | KIND_NULL, 15 | KIND_LONG, 16 | KIND_DOUBLE, 17 | KIND_BOOL, 18 | KIND_STRING, 19 | KIND_ARRAY, 20 | KIND_MAP, 21 | KIND_OBJECT 22 | }; 23 | 24 | engine_value *value_new(); 25 | void value_copy(zval *dst, zval *src); 26 | int value_kind(engine_value *val); 27 | 28 | void value_set_null(engine_value *val); 29 | void value_set_long(engine_value *val, long int num); 30 | void value_set_double(engine_value *val, double num); 31 | void value_set_bool(engine_value *val, bool status); 32 | void value_set_string(engine_value *val, char *str); 33 | void value_set_array(engine_value *val, unsigned int size); 34 | void value_set_object(engine_value *val); 35 | void value_set_zval(engine_value *val, zval *src); 36 | 37 | void value_array_next_set(engine_value *arr, engine_value *val); 38 | void value_array_index_set(engine_value *arr, unsigned long idx, engine_value *val); 39 | void value_array_key_set(engine_value *arr, const char *key, engine_value *val); 40 | void value_object_property_set(engine_value *obj, const char *key, engine_value *val); 41 | 42 | int value_get_long(engine_value *val); 43 | double value_get_double(engine_value *val); 44 | bool value_get_bool(engine_value *val); 45 | char *value_get_string(engine_value *val); 46 | 47 | unsigned int value_array_size(engine_value *arr); 48 | engine_value *value_array_keys(engine_value *arr); 49 | void value_array_reset(engine_value *arr); 50 | engine_value *value_array_next_get(engine_value *arr); 51 | engine_value *value_array_index_get(engine_value *arr, unsigned long idx); 52 | engine_value *value_array_key_get(engine_value *arr, char *key); 53 | 54 | #include "_value.h" 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /php5.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | // 5 | // +build php5 6 | 7 | package php 8 | 9 | // #cgo CFLAGS: -I/usr/include/php5 -I/usr/include/php5/main -I/usr/include/php5/TSRM 10 | // #cgo CFLAGS: -I/usr/include/php5/Zend -Iinclude/php5 -Isrc/php5 11 | // #cgo LDFLAGS: -lphp5 12 | import "C" 13 | -------------------------------------------------------------------------------- /php7-debian.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | // 5 | // Build tags specific to Debian (and Debian-derived, such as Ubuntu) 6 | // distributions. Debian builds its PHP7 packages with non-standard naming 7 | // conventions for include and library paths, so we need a specific build tag 8 | // for building against those packages. 9 | // 10 | // +build debian,!php5 11 | 12 | package php 13 | 14 | // #cgo CFLAGS: -I/usr/include/php/20151012 -Iinclude/php7 -Isrc/php7 15 | // #cgo CFLAGS: -I/usr/include/php/20151012/main -I/usr/include/php/20151012/Zend 16 | // #cgo CFLAGS: -I/usr/include/php/20151012/TSRM 17 | // #cgo LDFLAGS: -lphp7.0 18 | import "C" 19 | -------------------------------------------------------------------------------- /php7-static.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | // 5 | // +build static 6 | 7 | package php 8 | 9 | // #cgo LDFLAGS: -ldl -lm -lcurl -lpcre -lssl -lcrypto -lresolv -ledit -lz -lxml2 10 | import "C" 11 | -------------------------------------------------------------------------------- /php7.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | // 5 | // +build !php5 6 | 7 | package php 8 | 9 | // #cgo CFLAGS: -Iinclude/php7 -Isrc/php7 10 | // #cgo LDFLAGS: -lphp7 11 | import "C" 12 | -------------------------------------------------------------------------------- /receiver.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #include 6 | #include 7 | 8 | #include
9 | #include 10 | #include 11 | 12 | #include "value.h" 13 | #include "receiver.h" 14 | #include "_cgo_export.h" 15 | 16 | // Fetch and return field for method receiver. 17 | static engine_value *receiver_get(zval *object, zval *member) { 18 | engine_receiver *this = _receiver_this(object); 19 | return engineReceiverGet(this, Z_STRVAL_P(member)); 20 | } 21 | 22 | // Set field for method receiver. 23 | static void receiver_set(zval *object, zval *member, zval *value) { 24 | engine_receiver *this = _receiver_this(object); 25 | engineReceiverSet(this, Z_STRVAL_P(member), (void *) value); 26 | } 27 | 28 | // Check if field exists for method receiver. 29 | static int receiver_exists(zval *object, zval *member, int check) { 30 | engine_receiver *this = _receiver_this(object); 31 | 32 | if (!engineReceiverExists(this, Z_STRVAL_P(member))) { 33 | // Value does not exist. 34 | return 0; 35 | } else if (check == 2) { 36 | // Value exists. 37 | return 1; 38 | } 39 | 40 | int result = 0; 41 | engine_value *val = engineReceiverGet(this, Z_STRVAL_P(member)); 42 | 43 | if (check == 1) { 44 | // Value exists and is "truthy". 45 | convert_to_boolean(val->internal); 46 | result = _value_truth(val->internal); 47 | } else if (check == 0) { 48 | // Value exists and is not null. 49 | result = (val->kind != KIND_NULL) ? 1 : 0; 50 | } else { 51 | // Check value is invalid. 52 | result = 0; 53 | } 54 | 55 | _value_destroy(val); 56 | return result; 57 | } 58 | 59 | // Call function with arguments passed and return value (if any). 60 | static int receiver_method_call(char *name, INTERNAL_FUNCTION_PARAMETERS) { 61 | zval args; 62 | engine_receiver *this = _receiver_this(getThis()); 63 | 64 | array_init_size(&args, ZEND_NUM_ARGS()); 65 | 66 | if (zend_copy_parameters_array(ZEND_NUM_ARGS(), &args) == FAILURE) { 67 | RETVAL_NULL(); 68 | } else { 69 | engine_value *result = engineReceiverCall(this, name, (void *) &args); 70 | if (result == NULL) { 71 | RETVAL_NULL(); 72 | } else { 73 | value_copy(return_value, result->internal); 74 | _value_destroy(result); 75 | } 76 | } 77 | 78 | zval_dtor(&args); 79 | } 80 | 81 | // Create new method receiver instance and attach to instantiated PHP object. 82 | // Returns an exception if method receiver failed to initialize for any reason. 83 | static void receiver_new(INTERNAL_FUNCTION_PARAMETERS) { 84 | zval args; 85 | engine_receiver *this = _receiver_this(getThis()); 86 | 87 | array_init_size(&args, ZEND_NUM_ARGS()); 88 | 89 | if (zend_copy_parameters_array(ZEND_NUM_ARGS(), &args) == FAILURE) { 90 | zend_throw_exception(NULL, "Could not parse parameters for method receiver", 0); 91 | } else { 92 | // Create receiver instance. Throws an exception if creation fails. 93 | int result = engineReceiverNew(this, (void *) &args); 94 | if (result != 0) { 95 | zend_throw_exception(NULL, "Failed to instantiate method receiver", 0); 96 | } 97 | } 98 | 99 | zval_dtor(&args); 100 | } 101 | 102 | // Fetch and return function definition for method receiver. The method call 103 | // happens in the method handler, as returned by this function. 104 | static zend_internal_function *receiver_method_get(zend_object *object) { 105 | zend_internal_function *func = emalloc(sizeof(zend_internal_function)); 106 | 107 | func->type = ZEND_OVERLOADED_FUNCTION; 108 | func->handler = NULL; 109 | func->arg_info = NULL; 110 | func->num_args = 0; 111 | func->scope = object->ce; 112 | func->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; 113 | 114 | return func; 115 | } 116 | 117 | // Fetch and return constructor function definition for method receiver. The 118 | // construct call happens in the constructor handler, as returned by this 119 | // function. 120 | static zend_internal_function *receiver_constructor_get(zend_object *object) { 121 | static zend_internal_function func; 122 | 123 | func.type = ZEND_INTERNAL_FUNCTION; 124 | func.handler = receiver_new; 125 | func.arg_info = NULL; 126 | func.num_args = 0; 127 | func.scope = object->ce; 128 | func.fn_flags = 0; 129 | func.function_name = object->ce->name; 130 | 131 | return &func; 132 | } 133 | 134 | // Table of handler functions for method receivers. 135 | static zend_object_handlers receiver_handlers = { 136 | ZEND_OBJECTS_STORE_HANDLERS, 137 | 138 | _receiver_get, // read_property 139 | _receiver_set, // write_property 140 | NULL, // read_dimension 141 | NULL, // write_dimension 142 | 143 | NULL, // get_property_ptr_ptr 144 | NULL, // get 145 | NULL, // set 146 | 147 | _receiver_exists, // has_property 148 | NULL, // unset_property 149 | NULL, // has_dimension 150 | NULL, // unset_dimension 151 | 152 | NULL, // get_properties 153 | 154 | _receiver_method_get, // get_method 155 | _receiver_method_call, // call_method 156 | 157 | _receiver_constructor_get // get_constructor 158 | }; 159 | 160 | // Define class with unique name. 161 | void receiver_define(char *name) { 162 | zend_class_entry tmp; 163 | INIT_CLASS_ENTRY_EX(tmp, name, strlen(name), NULL); 164 | 165 | zend_class_entry *this = zend_register_internal_class(&tmp); 166 | 167 | this->create_object = _receiver_init; 168 | this->ce_flags |= ZEND_ACC_FINAL; 169 | 170 | // Set standard handlers for receiver. 171 | _receiver_handlers_set(&receiver_handlers); 172 | } 173 | 174 | void receiver_destroy(char *name) { 175 | name = php_strtolower(name, strlen(name)); 176 | _receiver_destroy(name); 177 | } 178 | 179 | #include "_receiver.c" 180 | -------------------------------------------------------------------------------- /receiver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | package php 6 | 7 | // #cgo CFLAGS: -I/usr/include/php -I/usr/include/php/main -I/usr/include/php/TSRM 8 | // #cgo CFLAGS: -I/usr/include/php/Zend -Iinclude 9 | // 10 | // #include 11 | // #include
12 | // #include "receiver.h" 13 | import "C" 14 | 15 | import ( 16 | "fmt" 17 | "reflect" 18 | "unsafe" 19 | ) 20 | 21 | // Receiver represents a method receiver. 22 | type Receiver struct { 23 | name string 24 | create func(args []interface{}) interface{} 25 | objects map[*C.struct__engine_receiver]*ReceiverObject 26 | } 27 | 28 | // NewObject instantiates a new method receiver object, using the Receiver's 29 | // create function and passing in a slice of values as a parameter. 30 | func (r *Receiver) NewObject(args []interface{}) (*ReceiverObject, error) { 31 | obj := &ReceiverObject{ 32 | instance: r.create(args), 33 | values: make(map[string]reflect.Value), 34 | methods: make(map[string]reflect.Value), 35 | } 36 | 37 | if obj.instance == nil { 38 | return nil, fmt.Errorf("Failed to instantiate method receiver") 39 | } 40 | 41 | v := reflect.ValueOf(obj.instance) 42 | vi := reflect.Indirect(v) 43 | 44 | for i := 0; i < v.NumMethod(); i++ { 45 | // Skip unexported methods. 46 | if v.Type().Method(i).PkgPath != "" { 47 | continue 48 | } 49 | 50 | obj.methods[v.Type().Method(i).Name] = v.Method(i) 51 | } 52 | 53 | if vi.Kind() == reflect.Struct { 54 | for i := 0; i < vi.NumField(); i++ { 55 | // Skip unexported fields. 56 | if vi.Type().Field(i).PkgPath != "" { 57 | continue 58 | } 59 | 60 | obj.values[vi.Type().Field(i).Name] = vi.Field(i) 61 | } 62 | } 63 | 64 | return obj, nil 65 | } 66 | 67 | // Destroy removes references to the generated PHP class for this receiver and 68 | // frees any memory used by object instances. 69 | func (r *Receiver) Destroy() { 70 | if r.create == nil { 71 | return 72 | } 73 | 74 | n := C.CString(r.name) 75 | defer C.free(unsafe.Pointer(n)) 76 | 77 | C.receiver_destroy(n) 78 | r.create = nil 79 | r.objects = nil 80 | } 81 | 82 | // ReceiverObject represents an object instance of a pre-defined method receiver. 83 | type ReceiverObject struct { 84 | instance interface{} 85 | values map[string]reflect.Value 86 | methods map[string]reflect.Value 87 | } 88 | 89 | // Get returns a named internal property of the receiver object instance, or an 90 | // error if the property does not exist or is not addressable. 91 | func (o *ReceiverObject) Get(name string) (*Value, error) { 92 | if _, exists := o.values[name]; !exists || !o.values[name].CanInterface() { 93 | return nil, fmt.Errorf("Value '%s' does not exist or is not addressable", name) 94 | } 95 | 96 | val, err := NewValue(o.values[name].Interface()) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | return val, nil 102 | } 103 | 104 | // Set assigns value to named internal property. If the named property does not 105 | // exist or cannot be set, the method does nothing. 106 | func (o *ReceiverObject) Set(name string, val interface{}) { 107 | // Do not attempt to set non-existing or unset-able field. 108 | if _, exists := o.values[name]; !exists || !o.values[name].CanSet() { 109 | return 110 | } 111 | 112 | o.values[name].Set(reflect.ValueOf(val)) 113 | } 114 | 115 | // Exists checks if named internal property exists and returns true, or false if 116 | // property does not exist. 117 | func (o *ReceiverObject) Exists(name string) bool { 118 | if _, exists := o.values[name]; !exists { 119 | return false 120 | } 121 | 122 | return true 123 | } 124 | 125 | // Call executes a method receiver's named internal method, passing a slice of 126 | // values as arguments to the method. If the method fails to execute or returns 127 | // no value, nil is returned, otherwise a Value instance is returned. 128 | func (o *ReceiverObject) Call(name string, args []interface{}) *Value { 129 | if _, exists := o.methods[name]; !exists { 130 | return nil 131 | } 132 | 133 | var in []reflect.Value 134 | for _, v := range args { 135 | in = append(in, reflect.ValueOf(v)) 136 | } 137 | 138 | // Call receiver method. 139 | var result interface{} 140 | val := o.methods[name].Call(in) 141 | 142 | // Process results, returning a single value if result slice contains a single 143 | // element, otherwise returns a slice of values. 144 | if len(val) > 1 { 145 | t := make([]interface{}, len(val)) 146 | for i, v := range val { 147 | t[i] = v.Interface() 148 | } 149 | 150 | result = t 151 | } else if len(val) == 1 { 152 | result = val[0].Interface() 153 | } else { 154 | return nil 155 | } 156 | 157 | v, err := NewValue(result) 158 | if err != nil { 159 | return nil 160 | } 161 | 162 | return v 163 | } 164 | -------------------------------------------------------------------------------- /receiver_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | package php 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | func TestReceiverStart(t *testing.T) { 13 | e, _ = New() 14 | t.SkipNow() 15 | } 16 | 17 | type testReceiver struct { 18 | Var string 19 | hidden int64 20 | } 21 | 22 | func (t *testReceiver) Ignore() { 23 | } 24 | 25 | func (t *testReceiver) Hello(p string) string { 26 | return "Hello " + p 27 | } 28 | 29 | func (t *testReceiver) Goodbye(p string) (string, string) { 30 | return "Goodbye", p 31 | } 32 | 33 | func (t *testReceiver) invalid() string { 34 | return "I'm afraid I can't let you do that, Dave" 35 | } 36 | 37 | func newTestReceiver(args []interface{}) interface{} { 38 | value := "Foo" 39 | 40 | if len(args) > 0 { 41 | switch v := args[0].(type) { 42 | case bool: 43 | return nil 44 | case string: 45 | value = v 46 | } 47 | } 48 | 49 | return &testReceiver{Var: value, hidden: 42} 50 | } 51 | 52 | var receiverDefineTests = []struct { 53 | script string 54 | expected string 55 | }{ 56 | { 57 | "$t = new TestReceiver; echo is_object($t);", 58 | "1", 59 | }, 60 | { 61 | `try { 62 | $t = new TestReceiver(false); 63 | } catch (Exception $e) { 64 | echo $e->getMessage(); 65 | }`, 66 | "Failed to instantiate method receiver", 67 | }, 68 | { 69 | "$t = new TestReceiver; echo $t->Var;", 70 | "Foo", 71 | }, 72 | { 73 | "$t = new TestReceiver; echo $t->hidden;", 74 | "", 75 | }, 76 | { 77 | "$t = new TestReceiver('wow'); echo $t->Var;", 78 | "wow", 79 | }, 80 | { 81 | "$t = new TestReceiver; $t->Var = 'Bar'; echo $t->Var;", 82 | "Bar", 83 | }, 84 | { 85 | "$t = new TestReceiver; $t->hello = 'wow'; echo $t->hello;", 86 | "", 87 | }, 88 | { 89 | "$t = new TestReceiver; echo $t->Ignore();", 90 | "", 91 | }, 92 | { 93 | "$t = new TestReceiver; echo $t->Hello('World');", 94 | "Hello World", 95 | }, 96 | { 97 | "$t = new TestReceiver; echo json_encode($t->Goodbye('Doge'));", 98 | `["Goodbye","Doge"]`, 99 | }, 100 | { 101 | "$t = new TestReceiver; echo $t->invalid();", 102 | "", 103 | }, 104 | { 105 | "$t = new TestReceiver; echo ($t->Var) ? 1 : 0;", 106 | "1", 107 | }, 108 | { 109 | "$t = new TestReceiver; echo isset($t->Var) ? 1 : 0;", 110 | "1", 111 | }, 112 | { 113 | "$t = new TestReceiver; echo empty($t->Var) ? 1 : 0;", 114 | "0", 115 | }, 116 | { 117 | "$t = new TestReceiver; echo isset($t->hidden) ? 1 : 0;", 118 | "0", 119 | }, 120 | } 121 | 122 | func TestReceiverDefine(t *testing.T) { 123 | var w bytes.Buffer 124 | 125 | c, _ := e.NewContext() 126 | c.Output = &w 127 | 128 | if err := e.Define("TestReceiver", newTestReceiver); err != nil { 129 | t.Fatalf("Engine.Define(): Failed to define method receiver: %s", err) 130 | } 131 | 132 | // Attempting to define a receiver twice should fail. 133 | if err := e.Define("TestReceiver", newTestReceiver); err == nil { 134 | t.Fatalf("Engine.Define(): Defining duplicate receiver should fail") 135 | } 136 | 137 | for _, tt := range receiverDefineTests { 138 | _, err := c.Eval(tt.script) 139 | if err != nil { 140 | t.Errorf("Context.Eval('%s'): %s", tt.script, err) 141 | continue 142 | } 143 | 144 | actual := w.String() 145 | w.Reset() 146 | 147 | if actual != tt.expected { 148 | t.Errorf("Context.Eval('%s'): Expected output '%s', actual '%s'", tt.script, tt.expected, actual) 149 | } 150 | } 151 | 152 | c.Destroy() 153 | } 154 | 155 | func TestReceiverDestroy(t *testing.T) { 156 | c, _ := e.NewContext() 157 | defer c.Destroy() 158 | 159 | r := e.receivers["TestReceiver"] 160 | if r == nil { 161 | t.Fatalf("Receiver.Destroy(): Could not find defined receiver") 162 | } 163 | 164 | r.Destroy() 165 | if r.create != nil || r.objects != nil { 166 | t.Errorf("Receiver.Destroy(): Did not set internal fields to `nil`") 167 | } 168 | 169 | // Attempting to destroy a receiver twice should be a no-op. 170 | r.Destroy() 171 | } 172 | 173 | func TestReceiverEnd(t *testing.T) { 174 | e.Destroy() 175 | t.SkipNow() 176 | } 177 | -------------------------------------------------------------------------------- /src/php5/_context.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | static void _context_bind(char *name, zval *value) { 6 | ZEND_SET_SYMBOL(EG(active_symbol_table), name, value); 7 | } 8 | 9 | static void _context_eval(zend_op_array *op, zval *ret) { 10 | zend_op_array *oparr = EG(active_op_array); 11 | zval *retval = NULL; 12 | zval **retvalptr = EG(return_value_ptr_ptr); 13 | zend_op **opline = EG(opline_ptr); 14 | int interact = CG(interactive); 15 | 16 | EG(return_value_ptr_ptr) = &retval; 17 | EG(active_op_array) = op; 18 | EG(no_extensions) = 1; 19 | 20 | if (!EG(active_symbol_table)) { 21 | zend_rebuild_symbol_table(); 22 | } 23 | 24 | CG(interactive) = 0; 25 | 26 | zend_try { 27 | zend_execute(op); 28 | } zend_catch { 29 | destroy_op_array(op); 30 | efree(op); 31 | zend_bailout(); 32 | } zend_end_try(); 33 | 34 | destroy_op_array(op); 35 | efree(op); 36 | 37 | CG(interactive) = interact; 38 | 39 | if (retval) { 40 | ZVAL_COPY_VALUE(ret, retval); 41 | zval_copy_ctor(ret); 42 | } else { 43 | ZVAL_NULL(ret); 44 | } 45 | 46 | EG(no_extensions) = 0; 47 | EG(opline_ptr) = opline; 48 | EG(active_op_array) = oparr; 49 | EG(return_value_ptr_ptr) = retvalptr; 50 | } 51 | -------------------------------------------------------------------------------- /src/php5/_engine.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | static int _engine_ub_write(const char *str, uint len) { 6 | return engine_ub_write(str, len); 7 | } 8 | -------------------------------------------------------------------------------- /src/php5/_receiver.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | static zval *_receiver_get(zval *object, zval *member, int type, const zend_literal *key) { 6 | zval *retval = NULL; 7 | MAKE_STD_ZVAL(retval); 8 | 9 | engine_value *result = receiver_get(object, member); 10 | if (result == NULL) { 11 | ZVAL_NULL(retval); 12 | return retval; 13 | } 14 | 15 | value_copy(retval, result->internal); 16 | _value_destroy(result); 17 | 18 | return retval; 19 | } 20 | 21 | static void _receiver_set(zval *object, zval *member, zval *value, const zend_literal *key) { 22 | receiver_set(object, member, value); 23 | } 24 | 25 | static int _receiver_exists(zval *object, zval *member, int check, const zend_literal *key) { 26 | return receiver_exists(object, member, check); 27 | } 28 | 29 | static int _receiver_method_call(const char *method, INTERNAL_FUNCTION_PARAMETERS) { 30 | return receiver_method_call((char *) method, INTERNAL_FUNCTION_PARAM_PASSTHRU); 31 | } 32 | 33 | static zend_function *_receiver_method_get(zval **object, char *name, int len, const zend_literal *key) { 34 | zend_object *obj = &(_receiver_this(*object)->obj); 35 | zend_internal_function *func = receiver_method_get(obj); 36 | 37 | func->function_name = estrndup(name, len); 38 | 39 | return (zend_function *) func; 40 | } 41 | 42 | static zend_function *_receiver_constructor_get(zval *object) { 43 | zend_object *obj = &(_receiver_this(object)->obj); 44 | zend_internal_function *func = receiver_constructor_get(obj); 45 | 46 | return (zend_function *) func; 47 | } 48 | 49 | // Free storage for allocated method receiver instance. 50 | static void _receiver_free(void *object) { 51 | engine_receiver *this = (engine_receiver *) object; 52 | zend_object_std_dtor(&(this->obj)); 53 | } 54 | 55 | // Initialize instance of method receiver object. The method receiver itself is 56 | // attached in the constructor function call. 57 | static zend_object_value _receiver_init(zend_class_entry *class_type) { 58 | engine_receiver *this = emalloc(sizeof(engine_receiver)); 59 | memset(this, 0, sizeof(engine_receiver)); 60 | 61 | zend_object_std_init(&this->obj, class_type); 62 | 63 | zend_object_value object; 64 | object.handle = zend_objects_store_put(this, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) _receiver_free, NULL); 65 | object.handlers = &receiver_handlers; 66 | 67 | return object; 68 | } 69 | 70 | static void _receiver_destroy(char *name) { 71 | zend_class_entry **class; 72 | 73 | if (zend_hash_find(CG(class_table), name, strlen(name), (void **) &class) == SUCCESS) { 74 | destroy_zend_class(class); 75 | zend_hash_del_key_or_index(CG(class_table), name, strlen(name), 0, HASH_DEL_KEY); 76 | } 77 | } 78 | 79 | static engine_receiver *_receiver_this(zval *object) { 80 | return (engine_receiver *) zend_object_store_get_object(object); 81 | } 82 | 83 | static void _receiver_handlers_set(zend_object_handlers *handlers) { 84 | zend_object_handlers *std = zend_get_std_object_handlers(); 85 | 86 | handlers->get_class_name = std->get_class_name; 87 | handlers->get_class_entry = std->get_class_entry; 88 | } 89 | 90 | // Return class name for method receiver. 91 | char *_receiver_get_name(engine_receiver *rcvr) { 92 | return (char *) rcvr->obj.ce->name; 93 | } 94 | -------------------------------------------------------------------------------- /src/php5/_value.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | zval *_value_init() { 6 | zval *tmp = NULL; 7 | 8 | MAKE_STD_ZVAL(tmp); 9 | ZVAL_NULL(tmp); 10 | 11 | return tmp; 12 | } 13 | 14 | // Destroy and free engine value. 15 | void _value_destroy(engine_value *val) { 16 | zval_dtor(val->internal); 17 | free(val); 18 | } 19 | 20 | int _value_truth(zval *val) { 21 | return (Z_TYPE_P(val) != IS_BOOL) ? -1 : ((Z_BVAL_P(val)) ? 1 : 0); 22 | } 23 | 24 | void _value_set_string(zval **val, char *str) { 25 | ZVAL_STRING(*val, str, 1); 26 | } 27 | 28 | static int _value_current_key_get(HashTable *ht, char **str_index, ulong *num_index) { 29 | return zend_hash_get_current_key(ht, str_index, num_index, 0); 30 | } 31 | 32 | static void _value_current_key_set(HashTable *ht, engine_value *val) { 33 | zval *tmp; 34 | 35 | MAKE_STD_ZVAL(tmp); 36 | zend_hash_get_current_key_zval(ht, tmp); 37 | add_next_index_zval(val->internal, tmp); 38 | } 39 | 40 | static void _value_array_next_get(HashTable *ht, engine_value *val) { 41 | zval **tmp = NULL; 42 | 43 | if (zend_hash_get_current_data(ht, (void **) &tmp) == SUCCESS) { 44 | value_set_zval(val, *tmp); 45 | zend_hash_move_forward(ht); 46 | } 47 | } 48 | 49 | static void _value_array_index_get(HashTable *ht, unsigned long index, engine_value *val) { 50 | zval **tmp = NULL; 51 | 52 | if (zend_hash_index_find(ht, index, (void **) &tmp) == SUCCESS) { 53 | value_set_zval(val, *tmp); 54 | } 55 | } 56 | 57 | static void _value_array_key_get(HashTable *ht, char *key, engine_value *val) { 58 | zval **tmp = NULL; 59 | 60 | if (zend_hash_find(ht, key, strlen(key) + 1, (void **) &tmp) == SUCCESS) { 61 | value_set_zval(val, *tmp); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/php7/_context.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | static void _context_bind(char *name, zval *value) { 6 | zend_hash_str_update(&EG(symbol_table), name, strlen(name), value); 7 | } 8 | 9 | static void _context_eval(zend_op_array *op, zval *ret) { 10 | EG(no_extensions) = 1; 11 | 12 | zend_try { 13 | ZVAL_NULL(ret); 14 | zend_execute(op, ret); 15 | } zend_catch { 16 | destroy_op_array(op); 17 | efree_size(op, sizeof(zend_op_array)); 18 | zend_bailout(); 19 | } zend_end_try(); 20 | 21 | destroy_op_array(op); 22 | efree_size(op, sizeof(zend_op_array)); 23 | 24 | EG(no_extensions) = 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/php7/_engine.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | static size_t _engine_ub_write(const char *str, size_t len) { 6 | return engine_ub_write(str, len); 7 | } 8 | -------------------------------------------------------------------------------- /src/php7/_receiver.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | static zval *_receiver_get(zval *object, zval *member, int type, void **cache_slot, zval *retval) { 6 | engine_value *result = receiver_get(object, member); 7 | if (result == NULL) { 8 | ZVAL_NULL(retval); 9 | return retval; 10 | } 11 | 12 | value_copy(retval, result->internal); 13 | _value_destroy(result); 14 | 15 | return retval; 16 | } 17 | 18 | static void _receiver_set(zval *object, zval *member, zval *value, void **cache_slot) { 19 | receiver_set(object, member, value); 20 | } 21 | 22 | static int _receiver_exists(zval *object, zval *member, int check, void **cache_slot) { 23 | return receiver_exists(object, member, check); 24 | } 25 | 26 | static int _receiver_method_call(zend_string *method, zend_object *object, INTERNAL_FUNCTION_PARAMETERS) { 27 | return receiver_method_call(method->val, INTERNAL_FUNCTION_PARAM_PASSTHRU); 28 | } 29 | 30 | static zend_function *_receiver_method_get(zend_object **object, zend_string *name, const zval *key) { 31 | zend_internal_function *func = receiver_method_get(*object); 32 | 33 | func->function_name = zend_string_copy(name); 34 | zend_set_function_arg_flags((zend_function *) func); 35 | 36 | return (zend_function *) func; 37 | } 38 | 39 | static zend_function *_receiver_constructor_get(zend_object *object) { 40 | zend_internal_function *func = receiver_constructor_get(object); 41 | zend_set_function_arg_flags((zend_function *) func); 42 | 43 | return (zend_function *) func; 44 | } 45 | 46 | // Free storage for allocated method receiver instance. 47 | static void _receiver_free(zend_object *object) { 48 | engine_receiver *this = (engine_receiver *) object; 49 | zend_object_std_dtor(&(this->obj)); 50 | } 51 | 52 | // Initialize instance of method receiver object. The method receiver itself is 53 | // attached in the constructor function call. 54 | static zend_object *_receiver_init(zend_class_entry *class_type) { 55 | engine_receiver *this = emalloc(sizeof(engine_receiver)); 56 | memset(this, 0, sizeof(engine_receiver)); 57 | 58 | zend_object_std_init(&(this->obj), class_type); 59 | object_properties_init(&(this->obj), class_type); 60 | this->obj.handlers = &receiver_handlers; 61 | 62 | return &(this->obj); 63 | } 64 | 65 | static void _receiver_destroy(char *name) { 66 | zval *class = zend_hash_str_find(CG(class_table), name, strlen(name)); 67 | 68 | if (class != NULL) { 69 | destroy_zend_class(class); 70 | zend_hash_str_del(CG(class_table), name, strlen(name)); 71 | } 72 | } 73 | 74 | static engine_receiver *_receiver_this(zval *object) { 75 | return (engine_receiver *) Z_OBJ_P(object); 76 | } 77 | 78 | static void _receiver_handlers_set(zend_object_handlers *handlers) { 79 | zend_object_handlers *std = zend_get_std_object_handlers(); 80 | 81 | handlers->get_class_name = std->get_class_name; 82 | handlers->free_obj = _receiver_free; 83 | } 84 | 85 | // Return class name for method receiver. 86 | char *_receiver_get_name(engine_receiver *rcvr) { 87 | return rcvr->obj.ce->name->val; 88 | } 89 | -------------------------------------------------------------------------------- /src/php7/_value.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | zval *_value_init() { 6 | zval *tmp = malloc(sizeof(zval)); 7 | ZVAL_NULL(tmp); 8 | 9 | return tmp; 10 | } 11 | 12 | // Destroy and free engine value. 13 | void _value_destroy(engine_value *val) { 14 | zval_dtor(val->internal); 15 | free(val->internal); 16 | free(val); 17 | } 18 | 19 | int _value_truth(zval *val) { 20 | return (Z_TYPE_P(val) == IS_TRUE) ? 1 : ((Z_TYPE_P(val) == IS_FALSE) ? 0 : -1); 21 | } 22 | 23 | void _value_set_string(zval **val, char *str) { 24 | ZVAL_STRING(*val, str); 25 | } 26 | 27 | static int _value_current_key_get(HashTable *ht, zend_string **str_index, zend_ulong *num_index) { 28 | return zend_hash_get_current_key(ht, str_index, num_index); 29 | } 30 | 31 | static void _value_current_key_set(HashTable *ht, engine_value *val) { 32 | zval tmp; 33 | 34 | zend_hash_get_current_key_zval(ht, &tmp); 35 | add_next_index_zval(val->internal, &tmp); 36 | } 37 | 38 | static void _value_array_next_get(HashTable *ht, engine_value *val) { 39 | zval *tmp = NULL; 40 | 41 | if ((tmp = zend_hash_get_current_data(ht)) != NULL) { 42 | value_set_zval(val, tmp); 43 | zend_hash_move_forward(ht); 44 | } 45 | } 46 | 47 | static void _value_array_index_get(HashTable *ht, unsigned long index, engine_value *val) { 48 | zval *tmp = NULL; 49 | 50 | if ((tmp = zend_hash_index_find(ht, index)) != NULL) { 51 | value_set_zval(val, tmp); 52 | } 53 | } 54 | 55 | static void _value_array_key_get(HashTable *ht, char *key, engine_value *val) { 56 | zval *tmp = NULL; 57 | zend_string *str = zend_string_init(key, strlen(key), 0); 58 | 59 | if ((tmp = zend_hash_find(ht, str)) != NULL) { 60 | value_set_zval(val, tmp); 61 | } 62 | 63 | zend_string_release(str); 64 | } 65 | -------------------------------------------------------------------------------- /value.c: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | #include 6 | #include 7 | #include
8 | 9 | #include "value.h" 10 | 11 | // Creates a new value and initializes type to null. 12 | engine_value *value_new() { 13 | engine_value *val = malloc(sizeof(engine_value)); 14 | if (val == NULL) { 15 | errno = 1; 16 | return NULL; 17 | } 18 | 19 | val->internal = _value_init(); 20 | val->kind = KIND_NULL; 21 | 22 | errno = 0; 23 | return val; 24 | } 25 | 26 | // Creates a complete copy of a zval. 27 | // The destination zval needs to be correctly initialized before use. 28 | void value_copy(zval *dst, zval *src) { 29 | ZVAL_COPY_VALUE(dst, src); 30 | zval_copy_ctor(dst); 31 | } 32 | 33 | // Returns engine value type. Usually compared against KIND_* constants, defined 34 | // in the `value.h` header file. 35 | int value_kind(engine_value *val) { 36 | return val->kind; 37 | } 38 | 39 | // Set type and value to null. 40 | void value_set_null(engine_value *val) { 41 | ZVAL_NULL(val->internal); 42 | val->kind = KIND_NULL; 43 | } 44 | 45 | // Set type and value to integer. 46 | void value_set_long(engine_value *val, long int num) { 47 | ZVAL_LONG(val->internal, num); 48 | val->kind = KIND_LONG; 49 | } 50 | 51 | // Set type and value to floating point. 52 | void value_set_double(engine_value *val, double num) { 53 | ZVAL_DOUBLE(val->internal, num); 54 | val->kind = KIND_DOUBLE; 55 | } 56 | 57 | // Set type and value to boolean. 58 | void value_set_bool(engine_value *val, bool status) { 59 | ZVAL_BOOL(val->internal, status); 60 | val->kind = KIND_BOOL; 61 | } 62 | 63 | // Set type and value to string. 64 | void value_set_string(engine_value *val, char *str) { 65 | _value_set_string(&val->internal, str); 66 | val->kind = KIND_STRING; 67 | } 68 | 69 | // Set type and value to array with a preset initial size. 70 | void value_set_array(engine_value *val, unsigned int size) { 71 | array_init_size(val->internal, size); 72 | val->kind = KIND_ARRAY; 73 | } 74 | 75 | // Set type and value to object. 76 | void value_set_object(engine_value *val) { 77 | object_init(val->internal); 78 | val->kind = KIND_OBJECT; 79 | } 80 | 81 | // Set type and value from zval. The source zval is copied and is otherwise not 82 | // affected. 83 | void value_set_zval(engine_value *val, zval *src) { 84 | int kind; 85 | 86 | // Determine concrete type from source zval. 87 | switch (Z_TYPE_P(src)) { 88 | case IS_NULL: 89 | kind = KIND_NULL; 90 | break; 91 | case IS_LONG: 92 | kind = KIND_LONG; 93 | break; 94 | case IS_DOUBLE: 95 | kind = KIND_DOUBLE; 96 | break; 97 | case IS_STRING: 98 | kind = KIND_STRING; 99 | break; 100 | case IS_OBJECT: 101 | kind = KIND_OBJECT; 102 | break; 103 | case IS_ARRAY: 104 | kind = KIND_ARRAY; 105 | HashTable *h = (Z_ARRVAL_P(src)); 106 | 107 | // Determine if array is associative or indexed. In the simplest case, a 108 | // associative array will have different values for the number of elements 109 | // and the index of the next free element. In cases where the number of 110 | // elements and the next free index is equal, we must iterate through 111 | // the hash table and check the keys themselves. 112 | if (h->nNumOfElements != h->nNextFreeElement) { 113 | kind = KIND_MAP; 114 | break; 115 | } 116 | 117 | unsigned long i = 0; 118 | 119 | for (zend_hash_internal_pointer_reset(h); i < h->nNumOfElements; i++) { 120 | unsigned long index; 121 | int type = _value_current_key_get(h, NULL, &index); 122 | 123 | if (type == HASH_KEY_IS_STRING || index != i) { 124 | kind = KIND_MAP; 125 | break; 126 | } 127 | 128 | zend_hash_move_forward(h); 129 | } 130 | 131 | break; 132 | default: 133 | // Booleans need special handling for different PHP versions. 134 | if (_value_truth(src) != -1) { 135 | kind = KIND_BOOL; 136 | break; 137 | } 138 | 139 | errno = 1; 140 | return; 141 | } 142 | 143 | value_copy(val->internal, src); 144 | val->kind = kind; 145 | 146 | errno = 0; 147 | } 148 | 149 | // Set next index of array or map value. 150 | void value_array_next_set(engine_value *arr, engine_value *val) { 151 | add_next_index_zval(arr->internal, val->internal); 152 | } 153 | 154 | void value_array_index_set(engine_value *arr, unsigned long idx, engine_value *val) { 155 | add_index_zval(arr->internal, idx, val->internal); 156 | arr->kind = KIND_MAP; 157 | } 158 | 159 | void value_array_key_set(engine_value *arr, const char *key, engine_value *val) { 160 | add_assoc_zval(arr->internal, key, val->internal); 161 | arr->kind = KIND_MAP; 162 | } 163 | 164 | void value_object_property_set(engine_value *obj, const char *key, engine_value *val) { 165 | add_property_zval(obj->internal, key, val->internal); 166 | } 167 | 168 | int value_get_long(engine_value *val) { 169 | zval tmp; 170 | 171 | // Return value directly if already in correct type. 172 | if (val->kind == KIND_LONG) { 173 | return Z_LVAL_P(val->internal); 174 | } 175 | 176 | value_copy(&tmp, val->internal); 177 | convert_to_long(&tmp); 178 | 179 | return Z_LVAL(tmp); 180 | } 181 | 182 | double value_get_double(engine_value *val) { 183 | zval tmp; 184 | 185 | // Return value directly if already in correct type. 186 | if (val->kind == KIND_DOUBLE) { 187 | return Z_DVAL_P(val->internal); 188 | } 189 | 190 | value_copy(&tmp, val->internal); 191 | convert_to_double(&tmp); 192 | 193 | return Z_DVAL(tmp); 194 | } 195 | 196 | bool value_get_bool(engine_value *val) { 197 | zval tmp; 198 | 199 | // Return value directly if already in correct type. 200 | if (val->kind == KIND_BOOL) { 201 | return _value_truth(val->internal); 202 | } 203 | 204 | value_copy(&tmp, val->internal); 205 | convert_to_boolean(&tmp); 206 | 207 | return _value_truth(&tmp); 208 | } 209 | 210 | char *value_get_string(engine_value *val) { 211 | zval tmp; 212 | int result; 213 | 214 | switch (val->kind) { 215 | case KIND_STRING: 216 | value_copy(&tmp, val->internal); 217 | break; 218 | case KIND_OBJECT: 219 | result = zend_std_cast_object_tostring(val->internal, &tmp, IS_STRING); 220 | if (result == FAILURE) { 221 | ZVAL_EMPTY_STRING(&tmp); 222 | } 223 | 224 | break; 225 | default: 226 | value_copy(&tmp, val->internal); 227 | convert_to_cstring(&tmp); 228 | } 229 | 230 | int len = Z_STRLEN(tmp) + 1; 231 | char *str = malloc(len); 232 | memcpy(str, Z_STRVAL(tmp), len); 233 | 234 | zval_dtor(&tmp); 235 | 236 | return str; 237 | } 238 | 239 | unsigned int value_array_size(engine_value *arr) { 240 | switch (arr->kind) { 241 | case KIND_ARRAY: 242 | case KIND_MAP: 243 | return Z_ARRVAL_P(arr->internal)->nNumOfElements; 244 | case KIND_OBJECT: 245 | // Object size is determined by the number of properties, regardless of 246 | // visibility. 247 | return Z_OBJPROP_P(arr->internal)->nNumOfElements; 248 | case KIND_NULL: 249 | // Null values are considered empty. 250 | return 0; 251 | } 252 | 253 | // Non-array or object values are considered to be single-value arrays. 254 | return 1; 255 | } 256 | 257 | engine_value *value_array_keys(engine_value *arr) { 258 | HashTable *h = NULL; 259 | engine_value *keys = value_new(); 260 | 261 | value_set_array(keys, value_array_size(arr)); 262 | 263 | switch (arr->kind) { 264 | case KIND_ARRAY: 265 | case KIND_MAP: 266 | case KIND_OBJECT: 267 | if (arr->kind == KIND_OBJECT) { 268 | h = Z_OBJPROP_P(arr->internal); 269 | } else { 270 | h = Z_ARRVAL_P(arr->internal); 271 | } 272 | 273 | unsigned long i = 0; 274 | 275 | for (zend_hash_internal_pointer_reset(h); i < h->nNumOfElements; i++) { 276 | _value_current_key_set(h, keys); 277 | zend_hash_move_forward(h); 278 | } 279 | 280 | break; 281 | case KIND_NULL: 282 | // Null values are considered empty. 283 | break; 284 | default: 285 | // Non-array or object values are considered to contain a single key, '0'. 286 | add_next_index_long(keys->internal, 0); 287 | } 288 | 289 | return keys; 290 | } 291 | 292 | void value_array_reset(engine_value *arr) { 293 | HashTable *h = NULL; 294 | 295 | switch (arr->kind) { 296 | case KIND_ARRAY: 297 | case KIND_MAP: 298 | h = Z_ARRVAL_P(arr->internal); 299 | break; 300 | case KIND_OBJECT: 301 | h = Z_OBJPROP_P(arr->internal); 302 | break; 303 | default: 304 | return; 305 | } 306 | 307 | zend_hash_internal_pointer_reset(h); 308 | } 309 | 310 | engine_value *value_array_next_get(engine_value *arr) { 311 | HashTable *ht = NULL; 312 | engine_value *val = value_new(); 313 | 314 | switch (arr->kind) { 315 | case KIND_ARRAY: 316 | case KIND_MAP: 317 | ht = Z_ARRVAL_P(arr->internal); 318 | break; 319 | case KIND_OBJECT: 320 | ht = Z_OBJPROP_P(arr->internal); 321 | break; 322 | default: 323 | // Attempting to return the next index of a non-array value will return 324 | // the value itself, allowing for implicit conversions of scalar values 325 | // to arrays. 326 | value_set_zval(val, arr->internal); 327 | return val; 328 | } 329 | 330 | _value_array_next_get(ht, val); 331 | return val; 332 | } 333 | 334 | engine_value *value_array_index_get(engine_value *arr, unsigned long idx) { 335 | HashTable *ht = NULL; 336 | engine_value *val = value_new(); 337 | 338 | switch (arr->kind) { 339 | case KIND_ARRAY: 340 | case KIND_MAP: 341 | ht = Z_ARRVAL_P(arr->internal); 342 | break; 343 | case KIND_OBJECT: 344 | ht = Z_OBJPROP_P(arr->internal); 345 | break; 346 | default: 347 | // Attempting to return the first index of a non-array value will return 348 | // the value itself, allowing for implicit conversions of scalar values 349 | // to arrays. 350 | if (idx == 0) { 351 | value_set_zval(val, arr->internal); 352 | return val; 353 | } 354 | 355 | return val; 356 | } 357 | 358 | _value_array_index_get(ht, idx, val); 359 | return val; 360 | } 361 | 362 | engine_value *value_array_key_get(engine_value *arr, char *key) { 363 | HashTable *ht = NULL; 364 | engine_value *val = value_new(); 365 | 366 | switch (arr->kind) { 367 | case KIND_ARRAY: 368 | case KIND_MAP: 369 | ht = Z_ARRVAL_P(arr->internal); 370 | break; 371 | case KIND_OBJECT: 372 | ht = Z_OBJPROP_P(arr->internal); 373 | break; 374 | default: 375 | return val; 376 | } 377 | 378 | _value_array_key_get(ht, key, val); 379 | return val; 380 | } 381 | 382 | #include "_value.c" 383 | -------------------------------------------------------------------------------- /value.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | package php 6 | 7 | // #cgo CFLAGS: -I/usr/include/php -I/usr/include/php/main -I/usr/include/php/TSRM 8 | // #cgo CFLAGS: -I/usr/include/php/Zend -Iinclude 9 | // 10 | // #include 11 | // #include 12 | // #include
13 | // #include "value.h" 14 | import "C" 15 | 16 | import ( 17 | "fmt" 18 | "reflect" 19 | "strconv" 20 | "unsafe" 21 | ) 22 | 23 | // ValueKind represents the specific kind of type represented in Value. 24 | type ValueKind int 25 | 26 | // PHP types representable in Go. 27 | const ( 28 | Null ValueKind = iota 29 | Long 30 | Double 31 | Bool 32 | String 33 | Array 34 | Map 35 | Object 36 | ) 37 | 38 | // Value represents a PHP value. 39 | type Value struct { 40 | value *C.struct__engine_value 41 | } 42 | 43 | // NewValue creates a PHP value representation of a Go value val. Available 44 | // bindings for Go to PHP types are: 45 | // 46 | // int -> integer 47 | // float64 -> double 48 | // bool -> boolean 49 | // string -> string 50 | // slice -> indexed array 51 | // map[int|string] -> associative array 52 | // struct -> object 53 | // 54 | // It is only possible to bind maps with integer or string keys. Only exported 55 | // struct fields are passed to the PHP context. Bindings for functions and method 56 | // receivers to PHP functions and classes are only available in the engine scope, 57 | // and must be predeclared before context execution. 58 | func NewValue(val interface{}) (*Value, error) { 59 | ptr, err := C.value_new() 60 | if err != nil { 61 | return nil, fmt.Errorf("Unable to instantiate PHP value") 62 | } 63 | 64 | v := reflect.ValueOf(val) 65 | 66 | // Determine interface value type and create PHP value from the concrete type. 67 | switch v.Kind() { 68 | // Bind integer to PHP int type. 69 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 70 | C.value_set_long(ptr, C.long(v.Int())) 71 | // Bind floating point number to PHP double type. 72 | case reflect.Float32, reflect.Float64: 73 | C.value_set_double(ptr, C.double(v.Float())) 74 | // Bind boolean to PHP bool type. 75 | case reflect.Bool: 76 | C.value_set_bool(ptr, C.bool(v.Bool())) 77 | // Bind string to PHP string type. 78 | case reflect.String: 79 | str := C.CString(v.String()) 80 | defer C.free(unsafe.Pointer(str)) 81 | 82 | C.value_set_string(ptr, str) 83 | // Bind slice to PHP indexed array type. 84 | case reflect.Slice: 85 | C.value_set_array(ptr, C.uint(v.Len())) 86 | 87 | for i := 0; i < v.Len(); i++ { 88 | vs, err := NewValue(v.Index(i).Interface()) 89 | if err != nil { 90 | C._value_destroy(ptr) 91 | return nil, err 92 | } 93 | 94 | C.value_array_next_set(ptr, vs.value) 95 | } 96 | // Bind map (with integer or string keys) to PHP associative array type. 97 | case reflect.Map: 98 | kt := v.Type().Key().Kind() 99 | 100 | if kt == reflect.Int || kt == reflect.String { 101 | C.value_set_array(ptr, C.uint(v.Len())) 102 | 103 | for _, key := range v.MapKeys() { 104 | kv, err := NewValue(v.MapIndex(key).Interface()) 105 | if err != nil { 106 | C._value_destroy(ptr) 107 | return nil, err 108 | } 109 | 110 | if kt == reflect.Int { 111 | C.value_array_index_set(ptr, C.ulong(key.Int()), kv.value) 112 | } else { 113 | str := C.CString(key.String()) 114 | defer C.free(unsafe.Pointer(str)) 115 | 116 | C.value_array_key_set(ptr, str, kv.value) 117 | } 118 | } 119 | } else { 120 | return nil, fmt.Errorf("Unable to create value of unknown type '%T'", val) 121 | } 122 | // Bind struct to PHP object (stdClass) type. 123 | case reflect.Struct: 124 | C.value_set_object(ptr) 125 | vt := v.Type() 126 | 127 | for i := 0; i < v.NumField(); i++ { 128 | // Skip unexported fields. 129 | if vt.Field(i).PkgPath != "" { 130 | continue 131 | } 132 | 133 | fv, err := NewValue(v.Field(i).Interface()) 134 | if err != nil { 135 | C._value_destroy(ptr) 136 | return nil, err 137 | } 138 | 139 | str := C.CString(vt.Field(i).Name) 140 | defer C.free(unsafe.Pointer(str)) 141 | 142 | C.value_object_property_set(ptr, str, fv.value) 143 | } 144 | case reflect.Invalid: 145 | C.value_set_null(ptr) 146 | default: 147 | C._value_destroy(ptr) 148 | return nil, fmt.Errorf("Unable to create value of unknown type '%T'", val) 149 | } 150 | 151 | return &Value{value: ptr}, nil 152 | } 153 | 154 | // NewValueFromPtr creates a Value type from an existing PHP value pointer. 155 | func NewValueFromPtr(val unsafe.Pointer) (*Value, error) { 156 | if val == nil { 157 | return nil, fmt.Errorf("Cannot create value from 'nil' pointer") 158 | } 159 | 160 | ptr, err := C.value_new() 161 | if err != nil { 162 | return nil, fmt.Errorf("Unable to create new PHP value") 163 | } 164 | 165 | if _, err := C.value_set_zval(ptr, (*C.zval)(val)); err != nil { 166 | return nil, fmt.Errorf("Unable to set PHP value from pointer") 167 | } 168 | 169 | return &Value{value: ptr}, nil 170 | } 171 | 172 | // Kind returns the Value's concrete kind of type. 173 | func (v *Value) Kind() ValueKind { 174 | return (ValueKind)(C.value_kind(v.value)) 175 | } 176 | 177 | // Interface returns the internal PHP value as it lies, with no conversion step. 178 | func (v *Value) Interface() interface{} { 179 | switch v.Kind() { 180 | case Long: 181 | return v.Int() 182 | case Double: 183 | return v.Float() 184 | case Bool: 185 | return v.Bool() 186 | case String: 187 | return v.String() 188 | case Array: 189 | return v.Slice() 190 | case Map, Object: 191 | return v.Map() 192 | } 193 | 194 | return nil 195 | } 196 | 197 | // Int returns the internal PHP value as an integer, converting if necessary. 198 | func (v *Value) Int() int64 { 199 | return (int64)(C.value_get_long(v.value)) 200 | } 201 | 202 | // Float returns the internal PHP value as a floating point number, converting 203 | // if necessary. 204 | func (v *Value) Float() float64 { 205 | return (float64)(C.value_get_double(v.value)) 206 | } 207 | 208 | // Bool returns the internal PHP value as a boolean, converting if necessary. 209 | func (v *Value) Bool() bool { 210 | return (bool)(C.value_get_bool(v.value)) 211 | } 212 | 213 | // String returns the internal PHP value as a string, converting if necessary. 214 | func (v *Value) String() string { 215 | str := C.value_get_string(v.value) 216 | defer C.free(unsafe.Pointer(str)) 217 | 218 | return C.GoString(str) 219 | } 220 | 221 | // Slice returns the internal PHP value as a slice of interface types. Non-array 222 | // values are implicitly converted to single-element slices. 223 | func (v *Value) Slice() []interface{} { 224 | size := (int)(C.value_array_size(v.value)) 225 | val := make([]interface{}, size) 226 | 227 | C.value_array_reset(v.value) 228 | 229 | for i := 0; i < size; i++ { 230 | t := &Value{value: C.value_array_next_get(v.value)} 231 | 232 | val[i] = t.Interface() 233 | t.Destroy() 234 | } 235 | 236 | return val 237 | } 238 | 239 | // Map returns the internal PHP value as a map of interface types, indexed by 240 | // string keys. Non-array values are implicitly converted to single-element maps 241 | // with a key of '0'. 242 | func (v *Value) Map() map[string]interface{} { 243 | val := make(map[string]interface{}) 244 | keys := &Value{value: C.value_array_keys(v.value)} 245 | 246 | for _, k := range keys.Slice() { 247 | switch key := k.(type) { 248 | case int64: 249 | t := &Value{value: C.value_array_index_get(v.value, C.ulong(key))} 250 | sk := strconv.Itoa((int)(key)) 251 | 252 | val[sk] = t.Interface() 253 | t.Destroy() 254 | case string: 255 | str := C.CString(key) 256 | t := &Value{value: C.value_array_key_get(v.value, str)} 257 | C.free(unsafe.Pointer(str)) 258 | 259 | val[key] = t.Interface() 260 | t.Destroy() 261 | } 262 | } 263 | 264 | keys.Destroy() 265 | return val 266 | } 267 | 268 | // Ptr returns a pointer to the internal PHP value, and is mostly used for 269 | // passing to C functions. 270 | func (v *Value) Ptr() unsafe.Pointer { 271 | return unsafe.Pointer(v.value) 272 | } 273 | 274 | // Destroy removes all active references to the internal PHP value and frees 275 | // any resources used. 276 | func (v *Value) Destroy() { 277 | if v.value == nil { 278 | return 279 | } 280 | 281 | C._value_destroy(v.value) 282 | v.value = nil 283 | } 284 | -------------------------------------------------------------------------------- /value_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexander Palaistras. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found in 3 | // the LICENSE file. 4 | 5 | package php 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestValueStart(t *testing.T) { 13 | e, _ = New() 14 | t.SkipNow() 15 | } 16 | 17 | var valueNewTests = []struct { 18 | value interface{} 19 | expected interface{} 20 | }{ 21 | { 22 | nil, 23 | nil, 24 | }, 25 | { 26 | 42, 27 | int64(42), 28 | }, 29 | { 30 | 3.14159, 31 | float64(3.14159), 32 | }, 33 | { 34 | true, 35 | true, 36 | }, 37 | { 38 | "Hello World", 39 | "Hello World", 40 | }, 41 | { 42 | []string{"Knick", "Knack"}, 43 | []interface{}{"Knick", "Knack"}, 44 | }, 45 | { 46 | [][]string{{"1", "2"}, {"3"}}, 47 | []interface{}{[]interface{}{"1", "2"}, []interface{}{"3"}}, 48 | }, 49 | { 50 | map[string]int{"biggs": 23, "wedge": 16}, 51 | map[string]interface{}{"biggs": int64(23), "wedge": int64(16)}, 52 | }, 53 | { 54 | map[int]string{10: "this", 20: "that"}, 55 | map[string]interface{}{"10": "this", "20": "that"}, 56 | }, 57 | { 58 | struct { 59 | I int 60 | S string 61 | B bool 62 | h string 63 | }{66, "wow", true, "hidden"}, 64 | map[string]interface{}{"I": int64(66), "S": "wow", "B": true}, 65 | }, 66 | } 67 | 68 | func TestValueNew(t *testing.T) { 69 | c, _ := e.NewContext() 70 | 71 | for _, tt := range valueNewTests { 72 | val, err := NewValue(tt.value) 73 | if err != nil { 74 | t.Errorf("NewValue('%v'): %s", tt.value, err) 75 | continue 76 | } 77 | 78 | if val == nil { 79 | t.Errorf("NewValue('%v'): No error returned but value is `nil`", tt.value) 80 | continue 81 | } 82 | 83 | actual := val.Interface() 84 | 85 | if reflect.DeepEqual(actual, tt.expected) == false { 86 | t.Errorf("NewValue('%v'): expected '%#v', actual '%#v'", tt.value, tt.expected, actual) 87 | } 88 | 89 | val.Destroy() 90 | } 91 | 92 | c.Destroy() 93 | } 94 | 95 | var valueNewInvalidTests = []interface{}{ 96 | uint(10), 97 | make(chan int), 98 | func() {}, 99 | []interface{}{uint(2)}, 100 | map[string]interface{}{"t": make(chan bool)}, 101 | map[bool]interface{}{false: true}, 102 | struct { 103 | T interface{} 104 | }{func() {}}, 105 | } 106 | 107 | func TestValueNewInvalid(t *testing.T) { 108 | c, _ := e.NewContext() 109 | 110 | for _, value := range valueNewInvalidTests { 111 | val, err := NewValue(value) 112 | if err == nil { 113 | val.Destroy() 114 | t.Errorf("NewValue('%v'): Value is invalid but no error occured", value) 115 | } 116 | } 117 | 118 | c.Destroy() 119 | } 120 | 121 | var valueKindTests = []struct { 122 | value interface{} 123 | expected ValueKind 124 | }{ 125 | { 126 | 42, 127 | Long, 128 | }, 129 | { 130 | 3.14159, 131 | Double, 132 | }, 133 | { 134 | true, 135 | Bool, 136 | }, 137 | { 138 | "Hello World", 139 | String, 140 | }, 141 | { 142 | []string{"Knick", "Knack"}, 143 | Array, 144 | }, 145 | { 146 | map[string]int{"t": 1, "c": 2}, 147 | Map, 148 | }, 149 | { 150 | struct { 151 | I int 152 | S string 153 | }{66, "wow"}, 154 | Object, 155 | }, 156 | } 157 | 158 | func TestValueKind(t *testing.T) { 159 | c, _ := e.NewContext() 160 | 161 | for _, tt := range valueKindTests { 162 | val, err := NewValue(tt.value) 163 | if err != nil { 164 | t.Errorf("NewValue('%v'): %s", tt.value, err) 165 | continue 166 | } 167 | 168 | actual := val.Kind() 169 | 170 | if actual != tt.expected { 171 | t.Errorf("Value.Kind('%v'): expected '%#v', actual '%#v'", tt.value, tt.expected, actual) 172 | } 173 | 174 | val.Destroy() 175 | } 176 | 177 | c.Destroy() 178 | } 179 | 180 | var valueIntTests = []struct { 181 | value interface{} 182 | expected int64 183 | }{ 184 | { 185 | 42, 186 | int64(42), 187 | }, 188 | { 189 | 3.14159, 190 | int64(3), 191 | }, 192 | { 193 | true, 194 | int64(1), 195 | }, 196 | { 197 | "Hello World", 198 | int64(0), 199 | }, 200 | { 201 | []string{"Knick", "Knack"}, 202 | int64(1), 203 | }, 204 | { 205 | map[string]int{"t": 1, "c": 2}, 206 | int64(1), 207 | }, 208 | { 209 | struct { 210 | I int 211 | S string 212 | }{66, "wow"}, 213 | int64(1), 214 | }, 215 | } 216 | 217 | func TestValueInt(t *testing.T) { 218 | c, _ := e.NewContext() 219 | 220 | for _, tt := range valueIntTests { 221 | val, err := NewValue(tt.value) 222 | if err != nil { 223 | t.Errorf("NewValue('%v'): %s", tt.value, err) 224 | continue 225 | } 226 | 227 | actual := val.Int() 228 | 229 | if reflect.DeepEqual(actual, tt.expected) == false { 230 | t.Errorf("Value.Int('%v'): expected '%#v', actual '%#v'", tt.value, tt.expected, actual) 231 | } 232 | 233 | val.Destroy() 234 | } 235 | 236 | c.Destroy() 237 | } 238 | 239 | var valueFloatTests = []struct { 240 | value interface{} 241 | expected float64 242 | }{ 243 | { 244 | 42, 245 | float64(42), 246 | }, 247 | { 248 | 3.14159, 249 | float64(3.14159), 250 | }, 251 | { 252 | true, 253 | float64(1), 254 | }, 255 | { 256 | "Hello World", 257 | float64(0), 258 | }, 259 | { 260 | []string{"Knick", "Knack"}, 261 | float64(1), 262 | }, 263 | { 264 | map[string]int{"t": 1, "c": 2}, 265 | float64(1), 266 | }, 267 | { 268 | struct { 269 | I int 270 | S string 271 | }{66, "wow"}, 272 | float64(1), 273 | }, 274 | } 275 | 276 | func TestValueFloat(t *testing.T) { 277 | c, _ := e.NewContext() 278 | 279 | for _, tt := range valueFloatTests { 280 | val, err := NewValue(tt.value) 281 | if err != nil { 282 | t.Errorf("NewValue('%v'): %s", tt.value, err) 283 | continue 284 | } 285 | 286 | actual := val.Float() 287 | 288 | if reflect.DeepEqual(actual, tt.expected) == false { 289 | t.Errorf("Value.Float('%v'): expected '%#v', actual '%#v'", tt.value, tt.expected, actual) 290 | } 291 | 292 | val.Destroy() 293 | } 294 | 295 | c.Destroy() 296 | } 297 | 298 | var valueBoolTests = []struct { 299 | value interface{} 300 | expected bool 301 | }{ 302 | { 303 | 42, 304 | true, 305 | }, 306 | { 307 | 3.14159, 308 | true, 309 | }, 310 | { 311 | true, 312 | true, 313 | }, 314 | { 315 | "Hello World", 316 | true, 317 | }, 318 | { 319 | []string{"Knick", "Knack"}, 320 | true, 321 | }, 322 | { 323 | map[string]int{"t": 1, "c": 2}, 324 | true, 325 | }, 326 | { 327 | struct { 328 | I int 329 | S string 330 | }{66, "wow"}, 331 | true, 332 | }, 333 | } 334 | 335 | func TestValueBool(t *testing.T) { 336 | c, _ := e.NewContext() 337 | 338 | for _, tt := range valueBoolTests { 339 | val, err := NewValue(tt.value) 340 | if err != nil { 341 | t.Errorf("NewValue('%v'): %s", tt.value, err) 342 | continue 343 | } 344 | 345 | actual := val.Bool() 346 | 347 | if reflect.DeepEqual(actual, tt.expected) == false { 348 | t.Errorf("Value.Bool('%v'): expected '%#v', actual '%#v'", tt.value, tt.expected, actual) 349 | } 350 | 351 | val.Destroy() 352 | } 353 | 354 | c.Destroy() 355 | } 356 | 357 | var valueStringTests = []struct { 358 | value interface{} 359 | expected string 360 | }{ 361 | { 362 | 42, 363 | "42", 364 | }, 365 | { 366 | 3.14159, 367 | "3.14159", 368 | }, 369 | { 370 | true, 371 | "1", 372 | }, 373 | { 374 | "Hello World", 375 | "Hello World", 376 | }, 377 | { 378 | []string{"Knick", "Knack"}, 379 | "Array", 380 | }, 381 | { 382 | map[string]int{"t": 1, "c": 2}, 383 | "Array", 384 | }, 385 | { 386 | struct { 387 | I int 388 | S string 389 | }{66, "wow"}, 390 | "", 391 | }, 392 | } 393 | 394 | func TestValueString(t *testing.T) { 395 | c, _ := e.NewContext() 396 | 397 | for _, tt := range valueStringTests { 398 | val, err := NewValue(tt.value) 399 | if err != nil { 400 | t.Errorf("NewValue('%v'): %s", tt.value, err) 401 | continue 402 | } 403 | 404 | actual := val.String() 405 | 406 | if reflect.DeepEqual(actual, tt.expected) == false { 407 | t.Errorf("Value.String('%v'): expected '%#v', actual '%#v'", tt.value, tt.expected, actual) 408 | } 409 | 410 | val.Destroy() 411 | } 412 | 413 | c.Destroy() 414 | } 415 | 416 | var valueSliceTests = []struct { 417 | value interface{} 418 | expected interface{} 419 | }{ 420 | { 421 | 42, 422 | []interface{}{int64(42)}, 423 | }, 424 | { 425 | 3.14159, 426 | []interface{}{float64(3.14159)}, 427 | }, 428 | { 429 | true, 430 | []interface{}{true}, 431 | }, 432 | { 433 | "Hello World", 434 | []interface{}{"Hello World"}, 435 | }, 436 | { 437 | []string{"Knick", "Knack"}, 438 | []interface{}{"Knick", "Knack"}, 439 | }, 440 | { 441 | map[string]int{"t": 1, "c": 2}, 442 | []interface{}{int64(1), int64(2)}, 443 | }, 444 | { 445 | struct { 446 | I int 447 | S string 448 | }{66, "wow"}, 449 | []interface{}{int64(66), "wow"}, 450 | }, 451 | } 452 | 453 | func TestValueSlice(t *testing.T) { 454 | c, _ := e.NewContext() 455 | 456 | for _, tt := range valueSliceTests { 457 | val, err := NewValue(tt.value) 458 | if err != nil { 459 | t.Errorf("NewValue('%v'): %s", tt.value, err) 460 | continue 461 | } 462 | 463 | actual := val.Slice() 464 | 465 | if reflect.DeepEqual(actual, tt.expected) == false { 466 | t.Errorf("Value.Slice('%v'): expected '%#v', actual '%#v'", tt.value, tt.expected, actual) 467 | } 468 | 469 | val.Destroy() 470 | } 471 | 472 | c.Destroy() 473 | } 474 | 475 | var valueMapTests = []struct { 476 | value interface{} 477 | expected map[string]interface{} 478 | }{ 479 | { 480 | 42, 481 | map[string]interface{}{"0": int64(42)}, 482 | }, 483 | { 484 | 3.14159, 485 | map[string]interface{}{"0": float64(3.14159)}, 486 | }, 487 | { 488 | true, 489 | map[string]interface{}{"0": true}, 490 | }, 491 | { 492 | "Hello World", 493 | map[string]interface{}{"0": "Hello World"}, 494 | }, 495 | { 496 | []string{"Knick", "Knack"}, 497 | map[string]interface{}{"0": "Knick", "1": "Knack"}, 498 | }, 499 | { 500 | map[string]int{"t": 1, "c": 2}, 501 | map[string]interface{}{"t": int64(1), "c": int64(2)}, 502 | }, 503 | { 504 | struct { 505 | I int 506 | S string 507 | }{66, "wow"}, 508 | map[string]interface{}{"I": int64(66), "S": "wow"}, 509 | }, 510 | } 511 | 512 | func TestValueMap(t *testing.T) { 513 | c, _ := e.NewContext() 514 | 515 | for _, tt := range valueMapTests { 516 | val, err := NewValue(tt.value) 517 | if err != nil { 518 | t.Errorf("NewValue('%v'): %s", tt.value, err) 519 | continue 520 | } 521 | 522 | actual := val.Map() 523 | 524 | if reflect.DeepEqual(actual, tt.expected) == false { 525 | t.Errorf("Value.Map('%v'): expected '%#v', actual '%#v'", tt.value, tt.expected, actual) 526 | } 527 | 528 | val.Destroy() 529 | } 530 | 531 | c.Destroy() 532 | } 533 | 534 | func TestValuePtr(t *testing.T) { 535 | c, _ := e.NewContext() 536 | defer c.Destroy() 537 | 538 | val, err := NewValue(42) 539 | if err != nil { 540 | t.Fatalf("NewValue('%v'): %s", 42, err) 541 | } 542 | 543 | if val.Ptr() == nil { 544 | t.Errorf("Value.Ptr('%v'): Unable to create pointer from value", 42) 545 | } 546 | } 547 | 548 | func TestValueDestroy(t *testing.T) { 549 | c, _ := e.NewContext() 550 | defer c.Destroy() 551 | 552 | val, err := NewValue(42) 553 | if err != nil { 554 | t.Fatalf("NewValue('%v'): %s", 42, err) 555 | } 556 | 557 | val.Destroy() 558 | 559 | if val.value != nil { 560 | t.Errorf("Value.Destroy(): Did not set internal fields to `nil`") 561 | } 562 | 563 | // Attempting to destroy a value twice should be a no-op. 564 | val.Destroy() 565 | } 566 | 567 | func TestValueEnd(t *testing.T) { 568 | e.Destroy() 569 | t.SkipNow() 570 | } 571 | --------------------------------------------------------------------------------