Skip to content

Instantly share code, notes, and snippets.

@DmitrySkibitsky
Last active March 9, 2026 11:04
Show Gist options
  • Select an option

  • Save DmitrySkibitsky/4359ff051f6c6a04f908184bd484ffc8 to your computer and use it in GitHub Desktop.

Select an option

Save DmitrySkibitsky/4359ff051f6c6a04f908184bd484ffc8 to your computer and use it in GitHub Desktop.
mago (Symfony)
FROM alpine:3.21
ARG MAGO_VERSION=latest
ARG TARGETARCH
RUN apk add --no-cache ca-certificates curl
# Resolve version and download the mago binary for the target architecture.
# Asset naming convention: mago-{VERSION}-{ARCH}.tar.gz
RUN set -eux; \
if [ "$MAGO_VERSION" = "latest" ]; then \
MAGO_VERSION="$(curl -fsSL https://api.github.com/repos/carthage-software/mago/releases/latest \
| grep -oP '"tag_name":\s*"\K[^"]+')"; \
fi; \
echo "Resolved mago version: ${MAGO_VERSION}"; \
case "$TARGETARCH" in \
amd64) ARCH="x86_64-unknown-linux-musl" ;; \
arm64) ARCH="aarch64-unknown-linux-musl" ;; \
*) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; \
esac; \
mkdir -p /tmp/mago-extract; \
curl -fsSL \
"https://github.com/carthage-software/mago/releases/download/${MAGO_VERSION}/mago-${MAGO_VERSION}-${ARCH}.tar.gz" \
| tar -xz -C /tmp/mago-extract; \
echo "Archive contents:"; \
find /tmp/mago-extract -print; \
MAGO_BIN="$(find /tmp/mago-extract -type f -name 'mago' | head -1)"; \
if [ -z "$MAGO_BIN" ]; then \
echo "ERROR: mago binary not found in archive"; \
find /tmp/mago-extract -print; \
exit 1; \
fi; \
mv "$MAGO_BIN" /usr/local/bin/mago; \
rm -rf /tmp/mago-extract; \
chmod +x /usr/local/bin/mago; \
mago --version
WORKDIR /app
ENTRYPOINT ["mago"]
# ─────────────────────────────────────────────────────────────────────────────
MAGO_IMAGE := mago:latest
MAGO_DOCKERFILE := docker/mago/Dockerfile
_SERVICE_ABS := $(abspath $(_SERVICE_DIR))
_FILE_ARG := $(if $(FILE),$(patsubst $(_SERVICE_ABS)/%,%,$(FILE)),)
_MAGO_ARGS := $(if $(_FILE_ARG),$(CMD) $(_FILE_ARG),$(CMD))
# Mago targets
# ─────────────────────────────────────────────────────────────────────────────
mago-build: ## Build the mago Docker image (run once, or after version bump)
docker build \
--build-arg TARGETARCH="$$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')" \
-t $(MAGO_IMAGE) \
-f $(MAGO_DOCKERFILE) \
docker/mago
mago: ## Run mago. SERVICE=auth or projects, CMD=lint/fmt/analyze/check, FILE=<abs path>
@if ! docker image inspect $(MAGO_IMAGE) > /dev/null 2>&1; then \
echo " Image $(MAGO_IMAGE) not found -- building..."; \
$(MAKE) mago-build; \
fi
docker run --rm \
-v "$(_SERVICE_ABS):/app" \
-w /app \
$(MAGO_IMAGE) $(_MAGO_ARGS)
# ─────────────────────────────────────────────────────────────────────────────
# Mago configuration for the Auth service (PHP 8.4 / Symfony 8)
# Strict typing enforcement is intentional — keep it that way.
# ─────────────────────────────────────────────────────────────────────────────
php-version = "8.4"
[source]
# Analyse application code and tests; vendor is context-only (includes).
paths = ["src", "tests"]
includes = ["vendor"]
excludes = ["var/**", "var/cache/**"]
# ─────────────────────────────────────────────────────────────────────────────
# Linter
# ─────────────────────────────────────────────────────────────────────────────
[linter]
# Symfony integration enables framework-aware rules (service naming, etc.)
integrations = ["symfony"]
baseline = "lint-baseline.toml"
[linter.rules]
# ── Strict types ──────────────────────────────────────────────────────────────
# Every PHP file must declare strict_types=1.
strict-types = { level = "error" }
# ── Type hints consistency ────────────────────────────────────────────────────
# Type hints must use lowercase built-in names (int, string, bool, etc.)
lowercase-type-hint = { level = "error" }
# Nullable params must use explicit ?Type syntax (PHP 8.0+ style)
explicit-nullable-param = { level = "error" }
# ── Code quality ──────────────────────────────────────────────────────────────
no-nested-ternary = { level = "error" }
no-closing-tag = { level = "error" }
no-goto = { level = "error" }
no-eval = { level = "error" }
# Shorthand ternary (?:) can hide bugs — use explicit conditions.
no-shorthand-ternary = { level = "warning" }
# No error suppression with @
no-error-control-operator = { level = "error" }
# No global state
no-global = { level = "error" }
# Avoid empty catch blocks — at minimum log or rethrow.
no-empty-catch-clause = { level = "warning" }
# Empty loop bodies are usually bugs.
no-empty-loop = { level = "warning" }
# Avoid useless else after return / throw / continue.
no-else-clause = { level = "warning" }
# Inline variable return is redundant ($x = ...; return $x;).
inline-variable-return = { level = "warning" }
# Prefer first-class callable syntax (PHP 8.1+)
prefer-first-class-callable = { level = "warning" }
# Prefer arrow functions for short closures (reduces boilerplate)
prefer-arrow-function = { level = "warning", exclude = ["tests/"] }
# Static closures when $this is not used
prefer-static-closure = { level = "warning", exclude = ["tests/"] }
# ── Complexity ────────────────────────────────────────────────────────────────
cyclomatic-complexity = { threshold = 10, level = "warning" }
# ── Redundancy detection ──────────────────────────────────────────────────────
no-redundant-nullsafe = { level = "warning" }
no-redundant-parentheses = { level = "warning" }
no-redundant-string-concat = { level = "warning" }
no-redundant-use = { level = "warning" }
no-redundant-final = { level = "warning" }
no-redundant-readonly = { level = "warning" }
no-redundant-block = { level = "warning" }
no-redundant-continue = { level = "warning" }
no-redundant-literal-return = { level = "warning" }
no-redundant-isset = { level = "warning" }
no-redundant-math = { level = "warning" }
# ── Debug / security ─────────────────────────────────────────────────────────
no-debug-symbols = { level = "error" }
no-literal-password = { level = "error" }
# Strict comparison (=== / !==) everywhere
identity-comparison = { level = "error" }
# ─────────────────────────────────────────────────────────────────────────────
# Formatter (PSR-12 base, Symfony-style overrides)
# ─────────────────────────────────────────────────────────────────────────────
[formatter]
preset = "psr-12"
print-width = 120
tab-width = 4
use-tabs = false
single-quote = true
trailing-comma = true
# Break each named argument in attributes onto its own line
always-break-attribute-named-argument-lists = true
preserve-breaking-attribute-list = true
always-break-named-arguments-list = true
# Symfony / PSR-12 brace style
control-brace-style = "same-line"
function-brace-style = "next-line"
method-brace-style = "next-line"
classlike-brace-style = "next-line"
# Blank lines around namespace and use blocks
empty-line-after-opening-tag = true
empty-line-after-namespace = true
empty-line-after-use = true
# Keep `use` statements sorted and split by type (class vs function vs const)
sort-uses = true
separate-use-types = true
expand-use-groups = true
# ─────────────────────────────────────────────────────────────────────────────
# Analyzer (maximum strictness)
# ─────────────────────────────────────────────────────────────────────────────
[analyzer]
baseline = "analysis-baseline.toml"
# Unused code detection
find-unused-expressions = true
find-unused-definitions = true
analyze-dead-code = true
find-unused-parameters = true
# Exception handling verification
check-throws = true
# Enforce `override` attribute where applicable (PHP 8.3+)
check-missing-override = true
# ── Type hint enforcement (core goal) ─────────────────────────────────────────
# Report every missing type hint: params, return types, properties.
check-missing-type-hints = true
check-closure-missing-type-hints = true
check-arrow-function-missing-type-hints = true
# Enforce `final` on classes that are not explicitly designed for extension.
# Disable per-class with a `@api` or `@internal` docblock if needed.
enforce-class-finality = true
# Require @api or @internal phpdoc on public API surface (enable when stable)
require-api-or-internal = false
# ── Strict runtime-safety checks ─────────────────────────────────────────────
strict-list-index-checks = true
no-boolean-literal-comparison = true
allow-possibly-undefined-array-keys = false
trust-existence-checks = false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment