func (c *Config) SetPHPBoolSetting(hostname, setting string, value bool) error {
	for i, s := range c.Sites {
		if s.Hostname == hostname {
			switch setting {
			case "display_errors":
				c.Sites[i].PHP.DisplayErrors = value
				return nil
			case "opcache_enable":
				c.Sites[i].PHP.OpcacheEnable = value
				return nil
			case "opcache_validate_timestamps":
				c.Sites[i].PHP.OpcacheValidateTimestamps = value
				return nil
			default:
				return fmt.Errorf("unknown php setting %s", setting)
			}
		}
	}
	return fmt.Errorf("unable to find the site: %s", hostname)
}
SetPHPBoolSetting is used to set php settings that are bool. It will look
for the site by its hostname and change the setting. If it cannot find the
site or setting it will return an error.
package config
import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"github.com/craftcms/nitro/pkg/helpers"
	"gopkg.in/yaml.v3"
)
var (
	DirectoryName = ".nitro"
	ErrNoConfigFile = fmt.Errorf("there is no config file for the environment")
	ErrEmptyfile = fmt.Errorf("the config file appears to be empty")
	FileName = "nitro.yaml"
	DefaultEnvs = map[string]string{
		"PHP_DISPLAY_ERRORS":              "on",
		"PHP_MEMORY_LIMIT":                "512M",
		"PHP_MAX_EXECUTION_TIME":          "5000",
		"PHP_UPLOAD_MAX_FILESIZE":         "512M",
		"PHP_MAX_INPUT_VARS":              "5000",
		"PHP_POST_MAX_SIZE":               "512M",
		"PHP_OPCACHE_ENABLE":              "0",
		"PHP_OPCACHE_REVALIDATE_FREQ":     "0",
		"PHP_OPCACHE_VALIDATE_TIMESTAMPS": "0",
		"XDEBUG_MODE":                     "off",
		"XDEBUG_SESSION":                  "PHPSTORM",
		"XDEBUG_CONFIG":                   "",
		"BLACKFIRE_SERVER_ID":             "",
		"BLACKFIRE_SERVER_TOKEN":          "",
	}
)
type Config struct {
	Containers []Container `json:"containers,omitempty" yaml:"containers,omitempty"`
	Blackfire  Blackfire   `json:"blackfire,omitempty" yaml:"blackfire,omitempty"`
	Databases  []Database  `json:"databases,omitempty" yaml:"databases,omitempty"`
	Services   Services    `json:"services" yaml:"services"`
	Sites      []Site      `json:"sites,omitempty" yaml:"sites,omitempty"`
	File       string      `json:"-" yaml:"-"`
}
func (c *Config) AllSitesWithHostnames(site Site, addr string) map[string][]string {
	hostnames := make(map[string][]string)
	for _, s := range c.Sites {
		if site.Hostname == s.Hostname {
			continue
		}
		hostnames[addr] = append(s.Aliases, s.Hostname)
	}
	return hostnames
}
func (c *Config) FindContainerByName(name string) (*Container, error) {
	for _, c := range c.Containers {
		if c.Name == name {
			return &c, nil
		}
	}
	return nil, fmt.Errorf("unable to find container with name %s", name)
}
func (c *Config) FindSiteByHostName(hostname string) (*Site, error) {
	for _, s := range c.Sites {
		if s.Hostname == hostname {
			return &s, nil
		}
	}
	return nil, fmt.Errorf("unable to find site with hostname %s", hostname)
}
func (c *Config) ListOfSitesByDirectory(home, wd string) []Site {
	var found []Site
	for _, s := range c.Sites {
		p, _ := s.GetAbsContainerPath(home)
		if strings.Contains(p, wd) {
			found = append(found, s)
		}
	}
	if len(found) > 0 {
		var exactMatches []Site
		var bestMatches = 0
		var b Site
		var m = 0
		for _, s := range found {
			containerPath := s.GetContainerPath()
			p, _ := s.GetAbsContainerPath(home)
			segments := strings.Split(containerPath, "/")
			var matchingSegments = len(segments)
			if wd == p {
				exactMatches = append(exactMatches, s)
			} else if matchingSegments > 1 && matchingSegments > m {
				bestMatches += 1
				m = matchingSegments
				b = s
			}
		}
		if len(exactMatches) > 0 {
			return exactMatches
		}
		if bestMatches == 1 {
			return []Site{b}
		}
		return found
	}
	return c.Sites
}
type Blackfire struct {
	ServerID    string `json:"server_id,omitempty" yaml:"server_id,omitempty"`
	ServerToken string `json:"server_token,omitempty" yaml:"server_token,omitempty"`
}
type Container struct {
	Name string `json:"name" yaml:"name"`
	Image string `json:"image" yaml:"image"`
	Tag string `json:"tag" yaml:"tag"`
	Ports []string `json:"ports,omitempty" yaml:"ports,omitempty"`
	Volumes []string `json:"volumes,omitempty" yaml:"volumes,omitempty"`
	WebGui  int    `json:"web_gui,omitempty" yaml:"web_gui,omitempty"`
	EnvFile string `json:"env_file,omitempty" yaml:"env_file,omitempty"`
}
func (c *Config) AddContainer(container Container) error {
	for _, e := range c.Containers {
		if e.Name == container.Name {
			return fmt.Errorf("an existing container %q already exists", e.Name)
		}
	}
	c.Containers = append(c.Containers, container)
	sort.SliceStable(c.Containers, func(i, j int) bool {
		return c.Containers[i].Name < c.Containers[j].Name
	})
	return nil
}
type Database struct {
	Engine  string `json:"engine" yaml:"engine"`
	Version string `json:"version" yaml:"version"`
	Port    string `json:"port" yaml:"port"`
}
func (d *Database) GetHostname() (string, error) {
	if d.Engine == "" || d.Version == "" || d.Port == "" {
		return "", fmt.Errorf("the engine, version, and port must be defined for the database")
	}
	return fmt.Sprintf("%s-%s-%s.database.nitro", d.Engine, d.Version, d.Port), nil
}
type Services struct {
	DynamoDB bool `json:"dynamodb"`
	Mailhog  bool `json:"mailhog"`
	Minio    bool `json:"minio"`
	Redis    bool `json:"redis"`
}
type Site struct {
	Hostname   string   `json:"hostname" yaml:"hostname"`
	Aliases    []string `json:"aliases,omitempty" yaml:"aliases,omitempty"`
	Path       string   `json:"path" yaml:"path"`
	Version    string   `json:"version" yaml:"version"`
	PHP        PHP      `json:"php,omitempty" yaml:"php,omitempty"`
	Extensions []string `json:"extensions,omitempty" yaml:"extensions,omitempty"`
	Webroot    string   `json:"webroot" yaml:"webroot"`
	Xdebug     bool     `json:"xdebug" yaml:"xdebug"`
	Blackfire  bool     `json:"blackfire" yaml:"blackfire"`
}
func (s *Site) GetAbsPath(home string) (string, error) {
	return cleanPath(home, s.Path)
}
func (s *Site) GetAbsContainerPath(home string) (string, error) {
	return cleanPath(home, s.Path+string(os.PathSeparator)+s.GetContainerPath())
}
func (s *Site) GetContainerPath() string {
	webroot := strings.TrimRight(s.Webroot, "/")
	if strings.Contains(webroot, "/") {
		parts := strings.Split(webroot, "/")
		if len(parts) >= 2 {
			return strings.Join(parts[:len(parts)-1], "/")
		}
	}
	return ""
}
func (s *Site) AsEnvs(addr string) []string {
	var envs []string
	envs = append(envs, phpVars(s.PHP, s.Version)...)
	return append(envs, xdebugVars(s.PHP, s.Xdebug, s.Version, s.Hostname, addr)...)
}