PHP
Dagger can be used to perform common CI tasks - testing, containerizing, publishing and more - for any PHP application, by encapsulating these CI tasks as Dagger Functions in a Dagger module.
How it works
To Daggerize a PHP project using a Dagger module, you must:
- Create a new Dagger module in your PHP project
- Implement your CI pipeline as one or more Dagger Function(s)
- Test your Dagger Function(s) locally and then transfer them to your CI environment
Prerequisites
None
Example
The following example demonstrates how CI tasks can be encapsulated as Dagger Functions in a Dagger module. It assumes:
- A PHP 8.2.x Web application with Composer for package management and PHPUnit for application testing. This example uses the Slim Framework skeleton application.
- Credentials to publish the containerized application image to a registry like Docker Hub.
Clone the application repository:
git clone https://github.com/slimphp/Slim-Skeleton.git
cd Slim-Skeleton
- Go
- Python
- TypeScript
Bootstrap a new module:
dagger init --name=my-module --sdk=go --source=./dagger
Update the generated dagger/main.go
file with the following code:
package main
import (
"context"
"fmt"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// return container image with application source code and dependencies
func (m *MyModule) Build(source *dagger.Directory) *dagger.Container {
return dag.Container().
From("php:8.2").
WithExec([]string{"apt-get", "update"}).
WithExec([]string{"apt-get", "install", "--yes", "git-core", "zip", "curl"}).
WithExec([]string{"sh", "-c", "curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer"}).
WithDirectory("/var/www", source.WithoutDirectory("dagger")).
WithWorkdir("/var/www").
WithExec([]string{"chmod", "-R", "775", "/var/www"}).
WithEnvVariable("PATH", "./vendor/bin:$PATH", dagger.ContainerWithEnvVariableOpts{
Expand: true,
}).
WithExec([]string{"composer", "install"})
}
// return result of unit tests
func (m *MyModule) Test(ctx context.Context, source *dagger.Directory) (string, error) {
return m.Build(source).
WithExec([]string{"phpunit"}).
Stdout(ctx)
}
// return address of published container image
func (m *MyModule) Publish(ctx context.Context, source *dagger.Directory, version string, registryAddress string, registryUsername string, registryPassword *dagger.Secret, imageName string) (string, error) {
return m.Build(source).
WithLabel("org.opencontainers.image.title", "PHP with Dagger").
WithLabel("org.opencontainers.image.version", version).
WithEntrypoint([]string{"php", "-S", "0.0.0.0:8080", "-t", "public"}).
WithExposedPort(8080).
WithRegistryAuth(registryAddress, registryUsername, registryPassword).
Publish(ctx, fmt.Sprintf("%s/%s/%s", registryAddress, registryUsername, imageName))
}
Bootstrap a new module:
dagger init --name=my-module --sdk=python --source=./dagger
Update the generated dagger/src/my_module/main.py
file with the following code:
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def build(self, source: dagger.Directory) -> dagger.Container:
"""Return container image with application source code and dependencies"""
return (
dag.container()
.from_("php:8.2")
.with_exec(["apt-get", "update"])
.with_exec(["apt-get", "install", "--yes", "git-core", "zip", "curl"])
.with_exec(
[
"sh",
"-c",
(
"curl -sS https://getcomposer.org/installer"
" | php -- --install-dir=/usr/local/bin --filename=composer"
),
]
)
.with_directory(
"/var/www",
source.without_directory("dagger"),
)
.with_workdir("/var/www")
.with_exec(["chmod", "-R", "775", "/var/www"])
.with_env_variable("PATH", "./vendor/bin:$PATH", expand=True)
.with_exec(["composer", "install"])
)
@function
async def test(self, source: dagger.Directory) -> str:
"""Return result of unit tests"""
return await self.build(source).with_exec(["phpunit"]).stdout()
@function
async def publish(
self,
source: dagger.Directory,
version: str,
registry_address: str,
registry_username: str,
registry_password: dagger.Secret,
image_name: str,
) -> str:
"""Return address of published container image"""
return await (
self.build(source)
.with_label("org.opencontainers.image.title", "PHP with Dagger")
.with_label("org.opencontainers.image.version", version)
.with_entrypoint(["php", "-S", "0.0.0.0:8080", "-t", "public"])
.with_exposed_port(8080)
.with_registry_auth(registry_address, registry_username, registry_password)
.publish(f"{registry_address}/{registry_username}/{image_name}")
)
Bootstrap a new module:
dagger init --name=my-module --sdk=typescript --source=./dagger
Update the generated dagger/src/index.ts
file with the following code:
import {
dag,
Container,
Directory,
Secret,
object,
func,
} from "@dagger.io/dagger"
@object()
class MyModule {
/*
* Return container image with application source code and dependencies
*/
@func()
build(source: Directory): Container {
return dag
.container()
.from("php:8.2")
.withExec(["apt-get", "update"])
.withExec(["apt-get", "install", "--yes", "git-core", "zip", "curl"])
.withDirectory("/var/www", source.withoutDirectory("dagger"))
.withWorkdir("/var/www")
.withExec(["chmod", "-R", "775", "/var/www"])
.withExec([
"sh",
"-c",
"curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer",
])
.withEnvVariable("PATH", "./vendor/bin:$PATH", { expand: true })
.withExec(["composer", "install"])
}
/*
* Return result of unit tests
*/
@func()
async test(source: Directory): Promise<string> {
return await this.build(source).withExec(["phpunit"]).stdout()
}
/*
* Return address of published container image
*/
@func()
async publish(
source: Directory,
version: string,
registryAddress: string,
registryUsername: string,
registryPassword: Secret,
imageName: string,
): Promise<string> {
const image = this.build(source)
.withLabel("org.opencontainers.image.title", "Laravel with Dagger")
.withLabel("org.opencontainers.image.version", version)
.withEntrypoint(["php", "-S", "0.0.0.0:8080", "-t", "public"])
.withExposedPort(8080)
const address = await image
.withRegistryAuth(registryAddress, registryUsername, registryPassword)
.publish(`${registryAddress}/${registryUsername}/${imageName}`)
return address
}
}
Here is an example of calling the Dagger Function to run the application's unit tests:
dagger call test --source=.
Here is an example of calling the Dagger Function to publish the application image to Docker Hub. Replace the DOCKER-HUB-USERNAME
and DOCKER-HUB-PASSWORD
placeholders with your Docker Hub credentials.
export REGISTRY_PASSWORD=DOCKER-HUB-PASSWORD
dagger call publish \
--source=. \
--version=0.1 \
--registry-address=docker.io \
--registry-username=DOCKER-HUB-USERNAME \
--registry-password=env:REGISTRY_PASSWORD \
--image-name=my-php-app
If you prefer to write your Dagger module in PHP instead of the above languages, an experimental PHP SDK is also available for PHP developers. This SDK is currently under development and therefore not yet documented. Interested early adopters can learn more by reviewing the source code or visiting our PHP channel.
Resources
If you have any questions about additional ways to use PHP with Dagger, join our Discord and ask your questions in our PHP channel.
About PHP
PHP is an interpreted, server-side scripting language.