Open thekid opened 6 years ago
package main
import (
"fmt"
php "github.com/deuill/go-php"
)
func main() {
engine, _ := php.New()
context, _ := engine.NewContext()
var str string = "Hello"
context.Bind("var", str)
val, _ := context.Eval("return $var.' World';")
fmt.Printf("%s", val.Interface())
// Prints 'Hello World' back to the user.
engine.Destroy()
}
friebe@LWKA-BWYWZF2:/.../go/src/github.com/xp-runners/evolution$ ./eval
Hello World
To make this work, I needed to install both php-embed
and php-dev
; and to create a file php7-ubuntu.go with:
package php
// #cgo CFLAGS: -I/usr/include/php/20170718 -Iinclude/php7 -Isrc/php7
// #cgo CFLAGS: -I/usr/include/php/20170718/main -I/usr/include/php/20170718/Zend
// #cgo CFLAGS: -I/usr/include/php/20170718/TSRM
// #cgo LDFLAGS: -lphp7.2
import "C"
Needs a small patch to class-main.php
:
--- /usr/bin/class-main.php 2018-09-06 20:08:05.000000000 +0200
+++ class-main.php 2018-10-01 23:57:14.613855000 +0200
@@ -71,7 +71,7 @@
$home= getenv('HOME');
$cwd= '.';
-if ('cgi' === PHP_SAPI || 'cgi-fcgi' === PHP_SAPI) {
+if ('cgi' === PHP_SAPI || 'cgi-fcgi' === PHP_SAPI || 'gophp-engine' === PHP_SAPI) {
ini_set('html_errors', 0);
define('STDIN', fopen('php://stdin', 'rb'));
define('STDOUT', fopen('php://stdout', 'wb'));
OK, here's a first running version:
package main
import (
"fmt"
"os"
"io"
php "github.com/deuill/go-php"
)
type Log struct {
Writer io.Writer
}
func (l Log) Write(p []byte) (n int, err error) {
os.Stderr.WriteString("> ")
n, err = l.Writer.Write(p)
os.Stderr.WriteString("\n")
return
}
func main() {
engine, _ := php.New()
context, _ := engine.NewContext()
context.Bind("argv", []string { "", "xp.runtime.Version" })
context.Output = os.Stdout
context.Log = &Log{os.Stderr}
val, err := context.Eval(`
set_include_path('.:/mnt/c/Tools/cygwin/home/friebe/devel/xp/core::.');
ini_set('date.timezone', 'Europe/Berlin');
try {
include 'class-main.php';
} catch (\Throwable $e) {
echo $e;
}
`)
if err != nil {
fmt.Printf("Could not execute entry point: %v", err)
os.Exit(1)
}
fmt.Printf("%+v", val.Interface())
var exit int
switch val.Kind() {
case php.Long:
exit = int(val.Int())
case php.String:
fmt.Println(val.String())
}
engine.Destroy()
os.Exit(exit)
}
Wow, an exit()
call from PHP shuts down the go binary with it! After another patch to the entry point script, this goes away; but it really needs to be handled inside Go code:
--- class-main.php 2018-10-01 21:32:43.574316300 +0200
+++ /usr/bin/class-main.php 2018-09-06 20:08:05.000000000 +0200
@@ -371,8 +371,8 @@
}
try {
- return $class::main(array_slice($argv, 1));
+ exit($class::main(array_slice($argv, 1)));
} catch (\lang\SystemExit $e) {
if ($message= $e->getMessage()) echo $message, "\n";
- return $e->getCode();
+ exit($e->getCode());
}
Wow, an exit() call from PHP shuts down the go binary with it!
Addressed with https://github.com/deuill/go-php/pull/59 - now giving us:
package main
import (
"fmt"
"os"
"io"
php "github.com/deuill/go-php"
)
type Log struct {
Writer io.Writer
}
func (l Log) Write(p []byte) (n int, err error) {
os.Stderr.WriteString("> ")
n, err = l.Writer.Write(p)
os.Stderr.WriteString("\n")
return
}
func main() {
engine, err := php.New()
if err != nil {
fmt.Printf("Could not create a new engine: %v", err)
os.Exit(1)
}
defer engine.Destroy()
context, err := engine.NewContext()
if err != nil {
fmt.Printf("Could not create a new context: %v", err)
os.Exit(1)
}
defer context.Destroy()
context.Bind("argv", os.Args)
context.Output = os.Stdout
context.Log = &Log{os.Stderr}
_, err = context.Eval(`
set_include_path('.:/mnt/c/Tools/cygwin/home/friebe/devel/xp/core::.');
ini_set('date.timezone', 'Europe/Berlin');
try {
include 'class-main.php';
} catch (\Throwable $e) {
echo $e;
exit(255);
}
`)
if err != nil {
if exit, ok := err.(*php.ExitError); ok {
os.Exit(exit.Status)
}
fmt.Printf("Could not execute entry point: %v", err)
os.Exit(1)
}
}
With https://github.com/deuill/go-php/pull/61, we can also move the set_include_path and ini_set calls out of the eval'ed code and inside go setters:
context.Ini("include_path", ".:/mnt/c/Tools/cygwin/home/friebe/devel/xp/core::.")
context.Ini("date.timezone", "Europe/Berlin")
Now the only thing missing would be the installation of an uncaught exception handler for errors from class-main.php
(see https://github.com/deuill/go-php/issues/31)
One of the things we could do is expose functionality such as https://github.com/fsnotify/fsnotify to PHP by the following:
type Watcher struct {
backing *notify.Watcher
}
func NewWatcher(args []interface{}) interface{} {
backing, err := notify.NewWatcher()
if err != nil {
return nil
}
return &Watcher{backing}
}
func (w *Watcher) Add(path string) error {
// Add directory
if err := w.backing.Add(path); err != nil {
return err
}
// Add subdirectories
return filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
name := info.Name()
if name != "." && name != ".." {
if err := w.backing.Add(path); err != nil {
return err
}
}
}
return nil
})
}
func (w *Watcher) Select() (interface{}, string) {
select {
case event, ok := <-w.backing.Events:
if !ok {
return nil, "Closed"
}
return event.Op.String(), event.Name
case err := <-w.backing.Errors:
return nil, err.Error()
}
}
func (w *Watcher) Close() {
w.backing.Close()
}
// Later on;
engine.Define("Watcher", NewWatcher)
$ go build run.go && ./run xp.runtime.Evaluate \
'$w= new Watcher(); $w->Add("."); var_dump($w, $w->Select()); $w->Close();'
object(Watcher)#19 (0) {
}
array(2) {
[0]=>
string(5) "WRITE"
[1]=>
string(8) "./xp.ini"
}
Instead of using
exec
, use https://github.com/deuill/go-php/cc @mikey179