10000 feat: embed PHP thanks to FrankenPHP by dunglas · Pull Request #216 · symfony-cli/symfony-cli · GitHub
[go: up one dir, main page]

Skip to content

feat: embed PHP thanks to FrankenPHP #216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
8000
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 51 additions & 38 deletions commands/local_server_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"path/filepath"
"syscall"

"github.com/dunglas/frankenphp"
Copy link
Contributor
@shyim shyim Nov 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to have here a go build tag? Frankenphp uses a lot of cgo which would break the release ci pipeline as it's not possible to cross compile anymore 🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do that, but first I'll try to have to build a release pipeline supporting all current targets (they are supported by PHP, and we can use Docker to cross-compile).

8000

"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/soheilhy/cmux"
Expand Down Expand Up @@ -68,6 +69,7 @@ var localServerStartCmd = &console.Command{
&console.StringFlag{Name: "p12", Usage: "Name of the file containing the TLS certificate to use in p12 format"},
&console.BoolFlag{Name: "no-tls", Usage: "Use HTTP instead of HTTPS"},
&console.BoolFlag{Name: "use-gzip", Usage: "Use GZIP"},
&console.BoolFlag{Name: "frankenphp", Usage: "Use FrankenPHP (built-in) instead of PHP FPM"},
},
Action: func(c *console.Context) error {
ui := terminal.SymfonyStyle(terminal.Stdout, terminal.Stdin)
Expand Down Expand Up @@ -201,52 +203,58 @@ var localServerStartCmd = &console.Command{
return err
}

// We retrieve a reader on logs as soon as possible to be able to
// display error logs in case of startup errors. We can't do it
// later as the log file will already be deleted.
logs, err := phpPidFile.LogReader()
if err != nil {
return err
}
if phpPidFile == nil {
if err := phpStartCallback(); err != nil {
return err
}
} else {
// We retrieve a reader on logs as soon as possible to be able to
// display error logs in case of startup errors. We can't do it
// later as the log file will already be deleted.
logs, err := phpPidFile.LogReader()
if err != nil {
return err
}

if !reexec.IsChild() {
tailer.WatchAdditionalPidFile(phpPidFile)
}
if !reexec.IsChild() {
tailer.WatchAdditionalPidFile(phpPidFile)
}

// we run FPM in its own goroutine to allow it to run even when
// foreground is forced
go func() { errChan <- phpStartCallback() }()
// we run FPM in its own goroutine to allow it to run even when
// foreground is forced
go func() { errChan <- phpStartCallback() }()

// Give time to PHP to fail or to be ready
select {
case err := <-errChan:
terminal.Logger.Error().Msgf("Unable to start %s", phpPidFile.CustomName)
// Give time to PHP to fail or to be ready
select {
case err := <-errChan:
terminal.Logger.Error().Msgf("Unable to start %s", phpPidFile.CustomName)

humanizer := humanlog.NewHandler(&humanlog.Options{
SkipUnchanged: true,
WithSource: true,
})
humanizer := humanlog.NewHandler(&humanlog.Options{
SkipUnchanged: true,
WithSource: true,
})

buf := bytes.Buffer{}
fmt.Fprintf(&buf, "%s failed to start:\n", phpPidFile.CustomName)
buf := bytes.Buffer{}
fmt.Fprintf(&buf, "%s failed to start:\n", phpPidFile.CustomName)

scanner := bufio.NewScanner(logs)
for scanner.Scan() {
buf.Write(humanizer.Simplify(scanner.Bytes()))
buf.WriteRune('\n')
}
scanner := bufio.NewScanner(logs)
for scanner.Scan() {
buf.Write(humanizer.Simplify(scanner.Bytes()))
buf.WriteRune('\n')
}

ui.Error(buf.String())
ui.Error(buf.String())

if err != nil {
return err
}
return nil
case err := <-phpPidFile.WaitForPid():
// PHP started, we can close logs and go ahead
logs.Close()
if err != nil {
return err
if err != nil {
return err
}
return nil
case err := <-phpPidFile.WaitForPid():
// PHP started, we can close logs and go ahead
logs.Close()
if err != nil {
return err
}
}
}
}
Expand Down Expand Up @@ -343,6 +351,11 @@ var localServerStartCmd = &console.Command{
case <-shutdownCh:
terminal.Eprintln("")
terminal.Eprintln("Shutting down!")

if config.FrankenPHP {
frankenphp.Shutdown()
}

if err := cleanupWebServerFiles(projectDir, pidFile); err != nil {
return err
}
Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ 8000 -1,8 +1,11 @@
module github.com/symfony-cli/symfony-cli

replace github.com/symfony-cli/phpstore => github.com/dunglas/phpstore v1.0.6-0.20221112140329-97f1078dbfc1

require (
github.com/compose-spec/compose-go v1.6.0
github.com/docker/docker v20.10.21+incompatible
github.com/dunglas/frankenphp v0.0.0-20221112134810-6a6dda5ed924
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819
github.com/fabpot/local-php-security-checker/v2 v2.0.5
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
Expand All @@ -24,11 +27,17 @@ require (
github.com/symfony-cli/phpstore v1.0.5
github.com/symfony-cli/terminal v1.0.4
github.com/syncthing/notify v0.0.0-20210616190510-c6b7342338d2
go.uber.org/zap v1.23.0
golang.org/x/sync v0.1.0
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
gopkg.in/yaml.v2 v2.4.0
)

require (
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
)

require (
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/NYTimes/gziphandler v1.1.1
Expand Down
18 changes: 16 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2y
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
Expand All @@ -70,6 +71,12 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dunglas/frankenphp v0.0.0-20221110130350-f0b2eb74451c h1:MV2p5aj2WsUVKtPU5iWyQCqrTlPnc7h/Xcpd5Fp//Mo=
github.com/dunglas/frankenphp v0.0.0-20221110130350-f0b2eb74451c/go.mod h1:6b1QU694yYzvWx460qOVNYJGOwOLZtErhiUNLiVHpSI=
github.com/dunglas/frankenphp v0.0.0-20221112134810-6a6dda5ed924 h1:xE23E2Ch9txx7bSas/xeLa1j+GEfSSUXbwZ72LCjjnY=
github.com/dunglas/frankenphp v0.0.0-20221112134810-6a6dda5ed924/go.mod h1:6b1QU694yYzvWx460qOVNYJGOwOLZtErhiUNLiVHpSI=
github.com/dunglas/phpstore v1.0.6-0.20221112140329-97f1078dbfc1 h1:y124yxl6y4skiXW87SIisI76sWcsx0QKpyPvZwBEvLU=
github.com/dunglas/phpstore v1.0.6-0.20221112140329-97f1078dbfc1/go.mod h1:Pug4pGst4b5DcGUwYz2DB1LjltohPgvE4OusDe1z2Xg=
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
Expand Down Expand Up @@ -269,8 +276,6 @@ github.com/symfony-cli/cert v1.0.1 h1:ETYVBshgY+SaydBJmMkU0PaoDrWm6zsGNB/799ZQYC
github.com/symfony-cli/cert v1.0.1/go.mod h1:g4WrLT6EQsEPmA19xh5Jv9Jnpg5EvtFqArJf2AG2S+w=
github.com/symfony-cli/console v1.0.2 h1:u8KJm9jFbfzmN0y7fcfjjal3wWOLflNomu29PLgYQjQ=
github.com/symfony-cli/console v1.0.2/go.mod h1:z2dLSNdPW3rWdSxj8DlZocMtMYN5EF6OeIYjVioXVqE=
github.com/symfony-cli/phpstore v1.0.5 h1:e1J+FcztiSSAVuD4gwatPwMpeqQy3SGNdhd6Vtuncy8=
github.com/symfony-cli/phpstore v1.0.5/go.mod h1:Pug4pGst4b5DcGUwYz2DB1LjltohPgvE4OusDe1z2Xg=
github.com/symfony-cli/terminal v1.0.4 h1:jam7aN7g7WQ9uOwV9UC+iVGBLTlzK8kAC5EKcxq21Z8=
github.com/symfony-cli/terminal v1.0.4/go.mod h1:+OxxnU05wyRHKYyQkTzTaCSSxmmIBcvAHLcQ099odj4=
github.com/syncthing/notify v0.0.0-20210616190510-c6b7342338d2 h1:F4snRP//nIuTTW9LYEzVH4HVwDG9T3M4t8y/2nqMbiY=
Expand All @@ -292,6 +297,14 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
Expand Down Expand Up @@ -614,6 +627,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
62 changes: 53 additions & 9 deletions local/php/php_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httptest"
Expand All @@ -37,6 +38,7 @@ import (
"strings"
"time"

"github.com/dunglas/frankenphp"
"github.com/pkg/errors"
"github.com/rs/xid"
"github.com/rs/zerolog"
Expand All @@ -46,6 +48,7 @@ import (
"github.com/symfony-cli/symfony-cli/local/html"
"github.com/symfony-cli/symfony-cli/local/pid"
"github.com/symfony-cli/symfony-cli/local/process"
"go.uber.org/zap"
)

// Server represents a PHP server process (can be php-fpm, php-cgi, or php-cli)
Expand All @@ -63,16 +66,30 @@ type Server struct {
var addslashes = strings.NewReplacer("\\", "\\\\", "'", "\\'")

// NewServer creates a new PHP server backend
func NewServer(homeDir, projectDir, documentRoot, passthru string, logger zerolog.Logger) (*Server, error) {
logger.Debug().Str("source", "PHP").Msg("Reloading PHP versions")
phpStore := phpstore.New(homeDir, true, nil)
version, source, warning, err := phpStore.BestVersionForDir(projectDir)
if warning != "" {
logger.Warn().Str("source", "PHP").Msg(warning)
}
if err != nil {
return nil, err
func NewServer(homeDir, projectDir, documentRoot, passthru string, useFrankenPHP bool, logger zerolog.Logger) (*Server, error) {
var (
version *phpstore.Version
source string
)

if useFrankenPHP {
version = &phpstore.Version{Version: frankenphp.Version().Version, FrankenPHP: true}
source = "FrankenPHP"
} else {
logger.Debug().Str("source", "PHP").Msg("Reloading PHP versions")
phpStore := phpstore.New(homeDir, true, nil)
v, s, warning, err := phpStore.BestVersionForDir(projectDir)
if warning != "" {
logger.Warn().Str("source", "PHP").Msg(warning)
}
if err != nil {
return nil, err
}

version = v
source = s
}

logger.Debug().Str("source", "PHP").Msgf("Using PHP version %s (from %s)", version.Version, source)
return &Server{
Version: version,
Expand All @@ -86,6 +103,25 @@ func NewServer(homeDir, projectDir, documentRoot, passthru string, logger zerolo

// Start starts a PHP server
func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile, func() error, error) {
if p.Version.IsFrankenPHPServer() {
return nil, func() error {
log.Print("calllll")

// TODO: create an adapter between zerolog and zap
z, err := zap.NewProduction()
if err != nil {
return errors.Wrap(err, "unable to create FrankenPHP's logger")
}
if err = frankenphp.Init(frankenphp.WithLogger(z)); err != nil {
return errors.Wrap(err, "unable to start FrankenPHP's logger")
}

log.Print("started")

return nil
}, nil
}

var pathsToRemove []string
port, err := process.FindAvailablePort()
if err != nil {
Expand Down Expand Up @@ -197,6 +233,14 @@ func (p *Server) Serve(w http.ResponseWriter, r *http.Request, env map[string]st
for k, v := range p.generateEnv(r) {
env[k] = v
}
if p.Version.IsFrankenPHPServer() {
fr := frankenphp.NewRequestWithContext(r, p.d 10000 ocumentRoot, nil)
fc, _ := frankenphp.FromContext(fr.Context())
fc.Env = env
fc.Env["SCRIPT_FILENAME"] = p.documentRoot + string(os.PathSeparator) + p.passthru

return frankenphp.ServeHTTP(w, fr)
}
if p.Version.IsCLIServer() {
rid := xid.New().String()
r.Header.Add("__SYMFONY_LOCAL_REQUEST_ID__", rid)
Expand Down
5 changes: 4 additions & 1 deletion local/project/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type Config struct {
NoTLS bool `yaml:"no_tls"`
Daemon bool `yaml:"daemon"`
UseGzip bool `yaml:"use_gzip"`
FrankenPHP bool `yaml:"frankenphp"`
}

type FileConfig struct {
Expand Down Expand Up @@ -104,10 +105,12 @@ func NewConfigFromContext(c *console.Context, projectDir string) (*Config, *File
if c.IsSet("daemon") {
config.Daemon = c.Bool("daemon")
}

if c.IsSet("use-gzip") {
config.UseGzip = c.Bool("use-gzip")
}
if c.IsSet("frankenphp") {
config.FrankenPHP = c.Bool("frankenphp")
}

return config, fileConfig, nil
}
Expand Down
2 changes: 1 addition & 1 deletion local/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func New(c *Config) (*Project, error) {
return nil
}
} else {
p.PHPServer, err = php.NewServer(c.HomeDir, c.ProjectDir, documentRoot, passthru, c.Logger)
p.PHPServer, err = php.NewServer(c.HomeDir, c.ProjectDir, documentRoot, passthru, c.FrankenPHP, c.Logger)
if err != nil {
return nil, err
}
Expand Down
0