From 9e37d1b0b0caa665e17dcc52be7f8f1e5a2eeca8 Mon Sep 17 00:00:00 2001 From: Andreas Sacher Date: Thu, 2 Oct 2025 16:42:37 +0200 Subject: [PATCH 1/8] TASK: optimize container build process --- Dockerfile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5c0b66e..76ad79e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,20 +3,22 @@ FROM golang:alpine AS build-env ENV GOPATH=/gopath ENV PATH=$GOPATH/bin:$PATH -ADD . /gopath/src/github.com/dfeyer/flow-debugproxy +WORKDIR /app RUN apk update && \ apk upgrade && \ apk add git -RUN cd /gopath/src/github.com/dfeyer/flow-debugproxy \ - && go get \ - && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o flow-debugproxy +ADD go.mod . +ADD go.sum . +RUN go mod download +ADD . . +RUN CGO_ENABLED=0 go build -o flow-debugproxy # # Build step FROM alpine WORKDIR /app -COPY --from=build-env /gopath/src/github.com/dfeyer/flow-debugproxy/flow-debugproxy /app/ +COPY --from=build-env /app/flow-debugproxy /app/ ENV ADDITIONAL_ARGS "" From 034129083b6c7c67b27f25ee77595212534c8442 Mon Sep 17 00:00:00 2001 From: Andreas Sacher Date: Thu, 2 Oct 2025 16:45:49 +0200 Subject: [PATCH 2/8] TASK: prevent cache misses from crashing the whole application --- dummypathmapper/dummypathmapper.go | 8 +++---- errorhandler/errorhanlder.go | 18 --------------- flowpathmapper/flowpathmapper.go | 22 ++++++++++-------- main.go | 36 ++++++++++++++++++++++-------- xdebugproxy/xdebugproxy.go | 28 ++++++++++++++++------- 5 files changed, 64 insertions(+), 48 deletions(-) delete mode 100644 errorhandler/errorhanlder.go diff --git a/dummypathmapper/dummypathmapper.go b/dummypathmapper/dummypathmapper.go index 0b4ecdd..908e97f 100644 --- a/dummypathmapper/dummypathmapper.go +++ b/dummypathmapper/dummypathmapper.go @@ -33,11 +33,11 @@ func (p *PathMapper) Initialize(c *config.Config, l *logger.Logger, m *pathmappi } // ApplyMappingToTextProtocol change file path in xDebug text protocol -func (p *PathMapper) ApplyMappingToTextProtocol(message []byte) []byte { - return message +func (p *PathMapper) ApplyMappingToTextProtocol(message []byte) ([]byte, error) { + return message, nil } // ApplyMappingToXML change file path in xDebug XML protocol -func (p *PathMapper) ApplyMappingToXML(message []byte) []byte { - return message +func (p *PathMapper) ApplyMappingToXML(message []byte) ([]byte, error) { + return message, nil } diff --git a/errorhandler/errorhanlder.go b/errorhandler/errorhanlder.go deleted file mode 100644 index 5d52f70..0000000 --- a/errorhandler/errorhanlder.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2015 Dominique Feyer . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package errorhandler - -import ( - "github.com/dfeyer/flow-debugproxy/logger" - "os" -) - -// PanicHandling handle error and output log message -func PanicHandling(err error, logger *logger.Logger) { - if err != nil { - logger.Warn(err.Error()) - os.Exit(1) - } -} diff --git a/flowpathmapper/flowpathmapper.go b/flowpathmapper/flowpathmapper.go index 9754040..80b2325 100644 --- a/flowpathmapper/flowpathmapper.go +++ b/flowpathmapper/flowpathmapper.go @@ -6,7 +6,6 @@ package flowpathmapper import ( "github.com/dfeyer/flow-debugproxy/config" - "github.com/dfeyer/flow-debugproxy/errorhandler" "github.com/dfeyer/flow-debugproxy/logger" "github.com/dfeyer/flow-debugproxy/pathmapperfactory" "github.com/dfeyer/flow-debugproxy/pathmapping" @@ -16,9 +15,9 @@ import ( "io/ioutil" "os" "regexp" + "runtime" "strconv" "strings" - "runtime" ) const ( @@ -63,24 +62,26 @@ func (p *PathMapper) Initialize(c *config.Config, l *logger.Logger, m *pathmappi } // ApplyMappingToTextProtocol change file path in xDebug text protocol -func (p *PathMapper) ApplyMappingToTextProtocol(message []byte) []byte { - return p.doTextPathMapping(message) +func (p *PathMapper) ApplyMappingToTextProtocol(message []byte) ([]byte, error) { + return p.doTextPathMapping(message), nil } // ApplyMappingToXML change file path in xDebug XML protocol -func (p *PathMapper) ApplyMappingToXML(message []byte) []byte { +func (p *PathMapper) ApplyMappingToXML(message []byte) ([]byte, error) { message = p.doXMLPathMapping(message) // update xml length count s := strings.Split(string(message), "\x00") i, err := strconv.Atoi(s[0]) - errorhandler.PanicHandling(err, p.logger) + if err != nil { + return nil, err + } l := len(s[1]) if i != l { message = bytes.Replace(message, []byte(strconv.Itoa(i)), []byte(strconv.Itoa(l)), 1) } - return message + return message, nil } func (p *PathMapper) doTextPathMapping(message []byte) []byte { @@ -88,7 +89,7 @@ func (p *PathMapper) doTextPathMapping(message []byte) []byte { for _, match := range regexpPhpFile.FindAllStringSubmatch(string(message), -1) { originalPath := match[1] if runtime.GOOS == "windows" { - originalPath = strings.Replace(originalPath,"//","", 1) + originalPath = strings.Replace(originalPath, "//", "", 1) } path := p.mapPath(originalPath) p.logger.Debug("doTextPathMapping %s >>> %s", path, originalPath) @@ -183,7 +184,10 @@ func (p *PathMapper) readOriginalPathFromCache(path, basePath string) string { } p.logger.Debug("readOriginalPathFromCache %s", localPath) dat, err := ioutil.ReadFile(localPath) - errorhandler.PanicHandling(err, p.logger) + if err != nil { + p.logger.Warn(err.Error()) + return localPath + } match := regexpPathAndFilename.FindStringSubmatch(string(dat)) if len(match) == 2 { originalPath := match[1] diff --git a/main.go b/main.go index f6c0035..a2e20ac 100644 --- a/main.go +++ b/main.go @@ -5,9 +5,10 @@ package main import ( + golog "log" + "github.com/dfeyer/flow-debugproxy/config" - "github.com/dfeyer/flow-debugproxy/errorhandler" "github.com/dfeyer/flow-debugproxy/logger" "github.com/dfeyer/flow-debugproxy/pathmapperfactory" "github.com/dfeyer/flow-debugproxy/pathmapping" @@ -84,13 +85,20 @@ func main() { Config: c, } - laddr, raddr, listener := setupNetworkConnection(cli.String("xdebug"), cli.String("ide"), log) + laddr, raddr, listener, err := setupNetworkConnection(cli.String("xdebug"), cli.String("ide")) + if err != nil { + log.Warn(err.Error()) + os.Exit(1) + } log.Info("Debugger from %v\nIDE from %v\n", laddr, raddr) pathMapping := &pathmapping.PathMapping{} pathMapper, err := pathmapperfactory.Create(c, pathMapping, log) - errorhandler.PanicHandling(err, log) + if err != nil { + log.Warn(err.Error()) + os.Exit(1) + } for { conn, err := listener.AcceptTCP() @@ -109,18 +117,28 @@ func main() { } } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + golog.Fatal(err) + return + } } -func setupNetworkConnection(xdebugAddr string, ideAddr string, log *logger.Logger) (*net.TCPAddr, *net.TCPAddr, *net.TCPListener) { +func setupNetworkConnection(xdebugAddr string, ideAddr string) (*net.TCPAddr, *net.TCPAddr, *net.TCPListener, error) { laddr, err := net.ResolveTCPAddr("tcp", xdebugAddr) - errorhandler.PanicHandling(err, log) + if err != nil { + return nil, nil, nil, err + } raddr, err := net.ResolveTCPAddr("tcp", ideAddr) - errorhandler.PanicHandling(err, log) + if err != nil { + return nil, nil, nil, err + } listener, err := net.ListenTCP("tcp", laddr) - errorhandler.PanicHandling(err, log) + if err != nil { + return nil, nil, nil, err + } - return laddr, raddr, listener + return laddr, raddr, listener, nil } diff --git a/xdebugproxy/xdebugproxy.go b/xdebugproxy/xdebugproxy.go index 2560219..0412ccc 100644 --- a/xdebugproxy/xdebugproxy.go +++ b/xdebugproxy/xdebugproxy.go @@ -9,10 +9,10 @@ import ( "github.com/dfeyer/flow-debugproxy/logger" "github.com/dfeyer/flow-debugproxy/pathmapping" + "bytes" "fmt" "io" "net" - "bytes" "strconv" ) @@ -21,8 +21,8 @@ const h = "%s" // XDebugProcessorPlugin process message in xDebug protocol type XDebugProcessorPlugin interface { Initialize(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) - ApplyMappingToTextProtocol(message []byte) []byte - ApplyMappingToXML(message []byte) []byte + ApplyMappingToTextProtocol(message []byte) ([]byte, error) + ApplyMappingToXML(message []byte) ([]byte, error) } // Proxy represents a pair of connections and their state @@ -135,18 +135,30 @@ func (p *Proxy) pipe(src, dst *net.TCPConn) { } // extract command name if isFromDebugger { - b = p.PathMapper.ApplyMappingToXML(b) + b, err = p.PathMapper.ApplyMappingToXML(b) + if p.handleError(err, dst) { + return + } // post processors for _, d := range p.postProcessors { processor = d - b = processor.ApplyMappingToXML(b) + b, err = processor.ApplyMappingToXML(b) + if p.handleError(err, dst) { + return + } } } else { - b = p.PathMapper.ApplyMappingToTextProtocol(b) + b, err = p.PathMapper.ApplyMappingToTextProtocol(b) + if p.handleError(err, dst) { + return + } // post processors for _, d := range p.postProcessors { processor = d - b = processor.ApplyMappingToTextProtocol(b) + b, err = processor.ApplyMappingToTextProtocol(b) + if p.handleError(err, dst) { + return + } } } @@ -184,4 +196,4 @@ func (p *Proxy) handleError(err error, ch *net.TCPConn) bool { } return false -} \ No newline at end of file +} From 41b91ff23b17cee9314452e7ee2ead87b4c4c5bf Mon Sep 17 00:00:00 2001 From: Andreas Sacher Date: Sun, 5 Oct 2025 00:32:20 +0200 Subject: [PATCH 3/8] TASK: add more logging --- flowpathmapper/flowpathmapper.go | 2 ++ main.go | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/flowpathmapper/flowpathmapper.go b/flowpathmapper/flowpathmapper.go index 80b2325..44fbebb 100644 --- a/flowpathmapper/flowpathmapper.go +++ b/flowpathmapper/flowpathmapper.go @@ -159,6 +159,8 @@ func (p *PathMapper) mapPath(originalPath string) string { } if err == nil { return p.setPathMapping(realPath, originalPath) + } else if p.config.Debug { + p.logger.Debug("Path %s not found on disk, return original path", realPath) } } diff --git a/main.go b/main.go index a2e20ac..4bd4307 100644 --- a/main.go +++ b/main.go @@ -91,7 +91,16 @@ func main() { os.Exit(1) } - log.Info("Debugger from %v\nIDE from %v\n", laddr, raddr) + log.Info("Debugger from %v", laddr) + log.Info("IDE from %v", raddr) + if c.Verbose { + log.Info("Context %v", c.Context) + log.Info("Framework %v", c.Framework) + log.Info("Local Root %v", c.LocalRoot) + log.Info("Verbose %v", c.Verbose) + log.Info("Very Verbose %v", c.VeryVerbose) + log.Info("Debug %v", c.Debug) + } pathMapping := &pathmapping.PathMapping{} pathMapper, err := pathmapperfactory.Create(c, pathMapping, log) From 68ccca294ae1dd017a41d4cdcfa5b6c440d8fd57 Mon Sep 17 00:00:00 2001 From: Andreas Sacher Date: Sun, 5 Oct 2025 03:04:05 +0200 Subject: [PATCH 4/8] TASK: listen to multiple ports and associate a context with each port listened to --- Dockerfile | 10 ++-- main.go | 111 +++++++++++++++++++++++++------------ xdebugproxy/xdebugproxy.go | 2 +- 3 files changed, 82 insertions(+), 41 deletions(-) diff --git a/Dockerfile b/Dockerfile index 76ad79e..14a14e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,11 +22,13 @@ COPY --from=build-env /app/flow-debugproxy /app/ ENV ADDITIONAL_ARGS "" -ENV XDEBUG_PORT 9010 +ENV XDEBUG 9003:Development -ENV IDE_IP 127.0.0.1 -ENV IDE_PORT 9000 +ENV IDE_IP host.docker.internal +ENV IDE_PORT 9010 ENV FRAMEWORK "flow" -ENTRYPOINT ["sh", "-c", "./flow-debugproxy --xdebug 0.0.0.0:${XDEBUG_PORT} --framework ${FRAMEWORK} --ide ${IDE_IP}:${IDE_PORT} ${ADDITIONAL_ARGS}"] +ENV LOCAL_ROOT "" + +ENTRYPOINT ["sh", "-c", "./flow-debugproxy --xdebug ${XDEBUG} --framework ${FRAMEWORK} --ide ${IDE_IP}:${IDE_PORT} --localroot \"${LOCAL_ROOT}\" ${ADDITIONAL_ARGS}"] diff --git a/main.go b/main.go index 4bd4307..bc432ea 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ package main import ( + "fmt" golog "log" "github.com/dfeyer/flow-debugproxy/config" @@ -34,7 +35,7 @@ func main() { app.Flags = []cli.Flag{ &cli.StringFlag{ Name: "xdebug, l", - Value: "127.0.0.1:9000", + Value: "Development:9003", Usage: "Listen address IP and port number", }, &cli.StringFlag{ @@ -42,11 +43,6 @@ func main() { Value: "127.0.0.1:9010", Usage: "Bind address IP and port number", }, - &cli.StringFlag{ - Name: "context, c", - Value: "Development", - Usage: "The context to run as", - }, &cli.StringFlag{ Name: "localroot, r", Value: "", @@ -73,7 +69,7 @@ func main() { app.Action = func(cli *cli.Context) error { c := &config.Config{ - Context: cli.String("context"), + Context: "", Framework: cli.String("framework"), LocalRoot: strings.TrimRight(cli.String("localroot"), "/"), Verbose: cli.Bool("verbose") || cli.Bool("vv"), @@ -85,16 +81,17 @@ func main() { Config: c, } - laddr, raddr, listener, err := setupNetworkConnection(cli.String("xdebug"), cli.String("ide")) + listener, raddr, err := setupNetworkConnection(strings.Split(cli.String("xdebug"), ","), cli.String("ide")) if err != nil { log.Warn(err.Error()) os.Exit(1) } - log.Info("Debugger from %v", laddr) + for _, listenerWithContext := range listener { + log.Info("Debugger from %v for context %v", listenerWithContext.addr, listenerWithContext.context) + } log.Info("IDE from %v", raddr) if c.Verbose { - log.Info("Context %v", c.Context) log.Info("Framework %v", c.Framework) log.Info("Local Root %v", c.LocalRoot) log.Info("Verbose %v", c.Verbose) @@ -102,26 +99,40 @@ func main() { log.Info("Debug %v", c.Debug) } - pathMapping := &pathmapping.PathMapping{} - pathMapper, err := pathmapperfactory.Create(c, pathMapping, log) - if err != nil { - log.Warn(err.Error()) - os.Exit(1) - } + connections := make(chan *xdebugproxy.Proxy) + for _, listenerWithContext := range listener { + listenerWithContext := listenerWithContext + originalConfig := *c + proxyConfig := originalConfig // copy config + proxyConfig.Context = listenerWithContext.context - for { - conn, err := listener.AcceptTCP() + pathMapping := &pathmapping.PathMapping{} + pathMapper, err := pathmapperfactory.Create(&proxyConfig, pathMapping, log) if err != nil { - log.Warn("Failed to accept connection '%s'\n", err) - continue + log.Warn(err.Error()) + os.Exit(1) } - proxy := &xdebugproxy.Proxy{ - Lconn: conn, - Raddr: raddr, - PathMapper: pathMapper, - Config: c, - } + go func() { + for { + conn, err := listenerWithContext.listener.AcceptTCP() + if err != nil { + log.Warn("Failed to accept connection '%s'\n", err) + continue + } + + connections <- &xdebugproxy.Proxy{ + Lconn: conn, + Raddr: raddr, + PathMapper: pathMapper, + Config: &proxyConfig, + } + } + }() + } + + for { + proxy := <-connections go proxy.Start() } } @@ -133,21 +144,49 @@ func main() { } } -func setupNetworkConnection(xdebugAddr string, ideAddr string) (*net.TCPAddr, *net.TCPAddr, *net.TCPListener, error) { - laddr, err := net.ResolveTCPAddr("tcp", xdebugAddr) - if err != nil { - return nil, nil, nil, err +type listenerWithContext struct { + addr *net.TCPAddr + listener *net.TCPListener + context string +} + +func splitContextAndPort(contextWithPort string) (string, string, error) { + contextAndPort := strings.Split(contextWithPort, ":") + if len(contextAndPort) != 2 { + return "", "", fmt.Errorf("could not parse port and context information '%s'", contextWithPort) } + return contextAndPort[0], contextAndPort[1], nil +} - raddr, err := net.ResolveTCPAddr("tcp", ideAddr) - if err != nil { - return nil, nil, nil, err +func setupNetworkConnection(xdebugAddr []string, ideAddr string) ([]*listenerWithContext, *net.TCPAddr, error) { + listener := make([]*listenerWithContext, 0, len(xdebugAddr)) + for _, portWithContext := range xdebugAddr { + context, portStr, err := splitContextAndPort(portWithContext) + if err != nil { + return nil, nil, err + } + + addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:"+portStr) + if err != nil { + return nil, nil, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return nil, nil, err + } + + listener = append(listener, &listenerWithContext{ + addr: addr, + listener: l, + context: context, + }) } - listener, err := net.ListenTCP("tcp", laddr) + raddr, err := net.ResolveTCPAddr("tcp", ideAddr) if err != nil { - return nil, nil, nil, err + return nil, nil, err } - return laddr, raddr, listener, nil + return listener, raddr, nil } diff --git a/xdebugproxy/xdebugproxy.go b/xdebugproxy/xdebugproxy.go index 0412ccc..c6714b8 100644 --- a/xdebugproxy/xdebugproxy.go +++ b/xdebugproxy/xdebugproxy.go @@ -60,7 +60,7 @@ func (p *Proxy) Start() { defer close(p.pipeErrors) // display both ends - p.log("Opened %s >>> %s", p.Lconn.RemoteAddr().String(), p.rconn.RemoteAddr().String()) + p.log("Opened %s >>> %s -- context %s", p.Lconn.RemoteAddr().String(), p.rconn.RemoteAddr().String(), p.Config.Context) // bidirectional copy go p.pipe(p.Lconn, p.rconn) go p.pipe(p.rconn, p.Lconn) From 6fc1c630bb13e9100bda86df1a7e5c7f9438b5e3 Mon Sep 17 00:00:00 2001 From: Andreas Sacher Date: Tue, 7 Oct 2025 15:49:56 +0200 Subject: [PATCH 5/8] TASK: map classes in paths with scheme [...]/DistributionPackages//Classes/.php --- flowpathmapper/flowpathmapper.go | 45 +++++++++++++-------------- flowpathmapper/flowpathmapper_test.go | 9 ++++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/flowpathmapper/flowpathmapper.go b/flowpathmapper/flowpathmapper.go index 44fbebb..90afd63 100644 --- a/flowpathmapper/flowpathmapper.go +++ b/flowpathmapper/flowpathmapper.go @@ -27,12 +27,13 @@ const ( ) var ( - regexpPhpFile = regexp.MustCompile(`(?://)?(/[^ ]*\.php)`) - regexpFilename__Win = regexp.MustCompile(`filename=["]?file:///(\S+)/Data/Temporary/.+?/Cache/Code/Flow_Object_Classes/([^"]*)\.php`) - regexpFilename__Unix = regexp.MustCompile(`filename=["]?file://(\S+)/Data/Temporary/.+?/Cache/Code/Flow_Object_Classes/([^"]*)\.php`) - regexpPathAndFilename = regexp.MustCompile(`(?m)^# PathAndFilename: (.*)$`) - regexpPackageClass = regexp.MustCompile(`(.*?)/Packages/[^/]*/(.*?)/Classes/(.*).php`) - regexpDot = regexp.MustCompile(`[\./]`) + regexpPhpFile = regexp.MustCompile(`(?://)?(/[^ ]*\.php)`) + regexpFilename__Win = regexp.MustCompile(`filename=["]?file:///(\S+)/Data/Temporary/.+?/Cache/Code/Flow_Object_Classes/([^"]*)\.php`) + regexpFilename__Unix = regexp.MustCompile(`filename=["]?file://(\S+)/Data/Temporary/.+?/Cache/Code/Flow_Object_Classes/([^"]*)\.php`) + regexpPathAndFilename = regexp.MustCompile(`(?m)^# PathAndFilename: (.*)$`) + regexpPackageClass = regexp.MustCompile(`(.*?)/Packages/[^/]*/(.*?)/Classes/(.*).php`) + regexpDistributionPackageClass = regexp.MustCompile(`(.*?)/DistributionPackages/(.*?)/Classes/(.*).php`) + regexpDot = regexp.MustCompile(`[\./]`) ) func init() { @@ -149,11 +150,16 @@ func (p *PathMapper) getRealFilename(path string) string { } func (p *PathMapper) mapPath(originalPath string) string { - if strings.Contains(originalPath, "/Packages/") { + if strings.Contains(originalPath, "/Packages/") || + strings.Contains(originalPath, "/DistributionPackages/") { p.logger.Debug("Path %s is a Flow Package file", originalPath) - cachePath := p.getCachePath(p.buildClassNameFromPath(originalPath)) + basePath, className, err := pathToClassPath(originalPath) + if err != nil { + p.logger.Warn(err.Error()) + return originalPath + } + cachePath := p.getCachePath(basePath, className) realPath := p.getRealFilename(cachePath) - var err error if len(p.config.LocalRoot) == 0 { _, err = os.Stat(realPath) } @@ -206,23 +212,16 @@ func (p *PathMapper) readOriginalPathFromCache(path, basePath string) string { return path } -func (p *PathMapper) buildClassNameFromPath(path string) (string, string) { - basePath, className := pathToClassPath(path) - if className == "" { - // Other (vendor) packages, todo add support for vendor package with Flow proxy class - p.logger.Warn(h, "Vendor package detected") - p.logger.Warn("Class mapping not supported currently for path: %s, \n", path) - } - return basePath, className -} - // Convert absolute path to class path (internal use only) -func pathToClassPath(path string) (string, string) { +func pathToClassPath(path string) (string, string, error) { var ( basePath string classPath string ) match := regexpPackageClass.FindStringSubmatch(path) + if len(match) != 4 { + match = regexpDistributionPackageClass.FindStringSubmatch(path) + } if len(match) == 4 { // Flow standard packages packagePath := regexpDot.ReplaceAllString(match[2], "/") @@ -234,9 +233,7 @@ func pathToClassPath(path string) (string, string) { basePath = match[1] classPath = regexpDot.ReplaceAllString(classPath, "_") } else { - // Other (vendor) packages, todo add support for vendor package with Flow proxy class - basePath = path - classPath = "" + return "", "", fmt.Errorf("path %s does not match known class path patterns, may be an (unsupported) vendor package", path) } - return basePath, classPath + return basePath, classPath, nil } diff --git a/flowpathmapper/flowpathmapper_test.go b/flowpathmapper/flowpathmapper_test.go index 334c069..00a0fae 100644 --- a/flowpathmapper/flowpathmapper_test.go +++ b/flowpathmapper/flowpathmapper_test.go @@ -1,18 +1,21 @@ package flowpathmapper import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestBuildClassNameFromPathSupportPSR2(t *testing.T) { - basePath, className := pathToClassPath("/your/path/sites/dev/master-dev.neos-workplace.dev/Packages/Application/Ttree.FlowDebugProxyHelper/Classes/Ttree/FlowDebugProxyHelper/ProxyClassMapperComponent.php") + basePath, className, err := pathToClassPath("/your/path/sites/dev/master-dev.neos-workplace.dev/Packages/Application/Ttree.FlowDebugProxyHelper/Classes/Ttree/FlowDebugProxyHelper/ProxyClassMapperComponent.php") assert.Equal(t, "/your/path/sites/dev/master-dev.neos-workplace.dev", basePath, "they should be equal") assert.Equal(t, "Ttree_FlowDebugProxyHelper_ProxyClassMapperComponent", className, "they should be equal") + assert.Equal(t, nil, err, "there should not be an error") } func TestBuildClassNameFromPathSupportPSR4(t *testing.T) { - basePath, className := pathToClassPath("/your/path/sites/dev/master-dev.neos-workplace.dev/Packages/Application/Ttree.FlowDebugProxyHelper/Classes/ProxyClassMapperComponent.php") + basePath, className, err := pathToClassPath("/your/path/sites/dev/master-dev.neos-workplace.dev/Packages/Application/Ttree.FlowDebugProxyHelper/Classes/ProxyClassMapperComponent.php") assert.Equal(t, "/your/path/sites/dev/master-dev.neos-workplace.dev", basePath, "they should be equal") assert.Equal(t, "Ttree_FlowDebugProxyHelper_ProxyClassMapperComponent", className, "they should be equal") + assert.Equal(t, nil, err, "there should not be an error") } From c3d660b10b9739f84754558621fe11c1fb4fea97 Mon Sep 17 00:00:00 2001 From: Andreas Sacher Date: Thu, 16 Oct 2025 14:57:49 +0200 Subject: [PATCH 6/8] BUGFIX: Fix using multiple contexts --- dummypathmapper/dummypathmapper.go | 20 ++++++++++++-------- flowpathmapper/flowpathmapper.go | 24 +++++++++++++++--------- pathmapperfactory/pathmapperfactory.go | 11 +++++------ xdebugproxy/xdebugproxy.go | 5 ++++- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/dummypathmapper/dummypathmapper.go b/dummypathmapper/dummypathmapper.go index 908e97f..5ba1dea 100644 --- a/dummypathmapper/dummypathmapper.go +++ b/dummypathmapper/dummypathmapper.go @@ -9,15 +9,26 @@ import ( "github.com/dfeyer/flow-debugproxy/logger" "github.com/dfeyer/flow-debugproxy/pathmapperfactory" "github.com/dfeyer/flow-debugproxy/pathmapping" + "github.com/dfeyer/flow-debugproxy/xdebugproxy" ) const framework = "dummy" func init() { - p := &PathMapper{} + p := &PathMapperFactory{} pathmapperfactory.Register(framework, p) } +type PathMapperFactory struct{} + +func (p *PathMapperFactory) Create(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) xdebugproxy.XDebugProcessorPlugin { + return &PathMapper{ + config: c, + logger: l, + pathMapping: m, + } +} + // PathMapper handle the mapping between real code and proxy type PathMapper struct { config *config.Config @@ -25,13 +36,6 @@ type PathMapper struct { pathMapping *pathmapping.PathMapping } -// Initialize the path mapper dependencies -func (p *PathMapper) Initialize(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) { - p.config = c - p.logger = l - p.pathMapping = m -} - // ApplyMappingToTextProtocol change file path in xDebug text protocol func (p *PathMapper) ApplyMappingToTextProtocol(message []byte) ([]byte, error) { return message, nil diff --git a/flowpathmapper/flowpathmapper.go b/flowpathmapper/flowpathmapper.go index 90afd63..6bcf8ff 100644 --- a/flowpathmapper/flowpathmapper.go +++ b/flowpathmapper/flowpathmapper.go @@ -9,6 +9,7 @@ import ( "github.com/dfeyer/flow-debugproxy/logger" "github.com/dfeyer/flow-debugproxy/pathmapperfactory" "github.com/dfeyer/flow-debugproxy/pathmapping" + "github.com/dfeyer/flow-debugproxy/xdebugproxy" "bytes" "fmt" @@ -37,7 +38,7 @@ var ( ) func init() { - p := &PathMapper{} + p := &PathMapperFactory{} pathmapperfactory.Register(framework, p) } @@ -48,6 +49,16 @@ func regexpFilename() *regexp.Regexp { return regexpFilename__Unix } +type PathMapperFactory struct{} + +func (p *PathMapperFactory) Create(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) xdebugproxy.XDebugProcessorPlugin { + return &PathMapper{ + config: c, + logger: l, + pathMapping: m, + } +} + // PathMapper handle the mapping between real code and proxy type PathMapper struct { config *config.Config @@ -55,13 +66,6 @@ type PathMapper struct { pathMapping *pathmapping.PathMapping } -// Initialize the path mapper dependencies -func (p *PathMapper) Initialize(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) { - p.config = c - p.logger = l - p.pathMapping = m -} - // ApplyMappingToTextProtocol change file path in xDebug text protocol func (p *PathMapper) ApplyMappingToTextProtocol(message []byte) ([]byte, error) { return p.doTextPathMapping(message), nil @@ -109,7 +113,9 @@ func (p *PathMapper) getCachePath(base, filename string) string { context := p.config.Context if strings.Contains(context, "/") { contextParts := strings.Split(context, "/") - contextParts[1] = "SubContext" + contextParts[1] + for i := 1; i < len(contextParts); i++ { + contextParts[i] = "SubContext" + contextParts[i] + } context = strings.Join(contextParts, "/") } cachePath = strings.Replace(cachePath, "@context@", context, 1) diff --git a/pathmapperfactory/pathmapperfactory.go b/pathmapperfactory/pathmapperfactory.go index a504c3a..64b3820 100644 --- a/pathmapperfactory/pathmapperfactory.go +++ b/pathmapperfactory/pathmapperfactory.go @@ -13,20 +13,19 @@ import ( "errors" ) -var pathMapperRegistry = map[string]xdebugproxy.XDebugProcessorPlugin{} +var pathMapperRegistry = map[string]xdebugproxy.XDebugProcessorPluginFactory{} // Register a path mapper -func Register(f string, p xdebugproxy.XDebugProcessorPlugin) { +func Register(f string, p xdebugproxy.XDebugProcessorPluginFactory) { pathMapperRegistry[f] = p } // Create return a pathmapper for the given framework func Create(c *config.Config, p *pathmapping.PathMapping, l *logger.Logger) (xdebugproxy.XDebugProcessorPlugin, error) { if _, exist := pathMapperRegistry[c.Framework]; exist { - pathmapper := pathMapperRegistry[c.Framework] - pathmapper.Initialize(c, l, p) - return pathmapper, nil + pathmapperFactory := pathMapperRegistry[c.Framework] + return pathmapperFactory.Create(c, l, p), nil } - return nil, errors.New("Unsupported framework") + return nil, errors.New("unsupported framework") } diff --git a/xdebugproxy/xdebugproxy.go b/xdebugproxy/xdebugproxy.go index c6714b8..ceddbaa 100644 --- a/xdebugproxy/xdebugproxy.go +++ b/xdebugproxy/xdebugproxy.go @@ -18,9 +18,12 @@ import ( const h = "%s" +type XDebugProcessorPluginFactory interface { + Create(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) XDebugProcessorPlugin +} + // XDebugProcessorPlugin process message in xDebug protocol type XDebugProcessorPlugin interface { - Initialize(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) ApplyMappingToTextProtocol(message []byte) ([]byte, error) ApplyMappingToXML(message []byte) ([]byte, error) } From 1fe6671ebca7c81fc93e8a0b8e25a173ac987156 Mon Sep 17 00:00:00 2001 From: Andreas Sacher Date: Thu, 16 Oct 2025 14:58:17 +0200 Subject: [PATCH 7/8] TASK: Update README.md --- README.md | 63 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index ed5cd6e..0b2a380 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ between your PHP file and the proxy class. Build your own -------------- +When you just want to use this inside Docker you probably want to scroll further down and skip until you reach the **Use with Docker** part. No need to build it on your host machine yourself. + # Get the dependecies go get # Build @@ -43,57 +45,62 @@ Show help Use with Docker --------------- -Use the [official docker image](https://hub.docker.com/r/dfeyer/flow-debugproxy/) and follow the instruction for the configuration. - -##### PHP configuration +Make sure xdebug is installed in your dev container. You can use drydock for this or add the following to your development Dockerfile ``` -[Xdebug] -zend_extension=/.../xdebug.so -xdebug.remote_enable=1 -xdebug.idekey=PHPSTORM -; The IP or name of the proxy container -xdebug.remote_host=debugproxy -; The proxy port (9010 by default, to not have issue is you use PHP FPM, already on port 9000) -xdebug.remote_port=9010 -;xdebug.remote_log=/tmp/xdebug.log +# install xdebug +RUN yes | pecl install xdebug +RUN echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini ``` +#### Docker Compose -You can use the `xdebug.remote_log` to debug the protocol between your container and the proxy, it's useful to catch network issues. - -##### Docker Compose - -This is an incomplete Docker Compose configuration: +This is an incomplete Docker Compose configuration showing all relevant settings for the debugproxy: ``` services: debugproxy: - image: dfeyer/flow-debugproxy:latest + image: ghcr.io/sachera/flow-debugproxy:latest volumes: - - .:/data + - neos_data_tempory:/neos/Data/Temporary:ro environment: # This MUST be the IP address of the IDE (your computer) - - "IDE_IP=192.168.1.130" - # This is the default value, need to match the xdebug.remote_port on your php.ini - - "XDEBUG_PORT=9010" + IDE_PORT: 9003 + # This is the default value, need to match the xdebug.client_port and FLOW_CONTEXT environment variable + XDEBUG: Docker/Testing:9001,Docker/Development:9002 + # must match the path used in the volume + LOCAL_ROOT: /neos # Use this to enable verbose debugging on the proxy - # - "ADDITIONAL_ARGS=-vv --debug" + #ADDITIONAL_ARGS: "-vv --debug" networks: - backend # This is your application containers, you need to link it to the proxy app: # The proxy need an access to the project files, to be able to do the path mapping + # make sure the path is the one actually used by neos and exists in the container + # if it is created by this command neos won't be able to write any temporary cache data due to right issues! volumes: - - .:/data - links: - - debugproxy + neos_data_tempory:/app/Data/Temporary:rw,cached + +volumes: + neos_data_temporary: {} +``` + +When running the debugger make sure to include interpreter options for xdebug to tell it about your debug proxy. For example using the above compose file when debugging a test in the Docker/Testing context you would use: + +``` +-dxdebug.client_host=debugproxy -dxdebug.client_port=9001 ``` +**Depending on your IDE set this setting as late as possible!** + +IntelliJ/PHPStorm for example set these command line options on their own, overriding them if you set them in your xdebug.ini. You will need to add them to the configuration of each actual test to make sure these settings have precedence! + **Options summary:** -* `IDE_IP` The primary local W-/LAN IP of your machine where your IDE runs on +* `IDE_IP` The primary local W-/LAN IP of your machine where your IDE runs on, defaults to docker.host.internal * `IDE_PORT` The Port your IDE is listening for incoming xdebug connections. (The port the debug proxy will try to connect to) -* `XDEBUG_PORT` The port on which xdebug will try to establish a connection (to this container) +* `XDEBUG` The flow context to port mapping. Based on the port xdebug connects to the context will be determined by this container +* `LOCAL_ROOT` The path where the Data/Temporary directory will be mounted to. Used to determine the path mapping * `FRAMEWORK` Currently supported values: `flow` and `dummy` * `ADDITIONAL_ARGS` For any additional argument like verbosity flags (`-vv`) or debug mode (`--debug`) (or both) From 8a2bb7518ae93200feeb8a064c38285b2cc16bf0 Mon Sep 17 00:00:00 2001 From: Andreas Sacher Date: Thu, 16 Oct 2025 15:30:00 +0200 Subject: [PATCH 8/8] TASK: Add instruction to create .../Data/Temporary in Dockerfile in README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 0b2a380..4ee81e0 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,9 @@ Make sure xdebug is installed in your dev container. You can use drydock for thi # install xdebug RUN yes | pecl install xdebug RUN echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini + +# ensure /app/Data/Temporary exists (change this if neos is not in /app) +RUN mkdir -p /app/Data/Temporary ``` #### Docker Compose