Framework

Repository Author Commit message Committed SHA  
 
http by dg Response::setCookie() treats an explicitly empty domain or path as given

The path/domain defaulting used truthy checks, so an explicit '' (a host-only
cookie or an empty path) behaved as if the argument was omitted: the cookie
inherited cookiePath instead of widening to '/', or cookieDomain instead of
staying host-only. Only null now means "not given".

http by dg HttpExtension: default to trusting X-Forwarded-For only (BC break!)

Changes the configured default of `proxyHeaders` from "both" to "xForwarded",
so an application behind a proxy no longer trusts a client-supplied "Forwarded"
header by default (the common proxy sets X-Forwarded-For). The RequestFactory
PHP default is intentionally left at "both" for BC; this hardens only the
framework configuration.

BC: deployments whose proxy uses the "Forwarded" header must now set
`proxyHeaders: forwarded` (or `both`).

http by dg RequestFactory: configurable trust of forwarding headers

Adds a choice of which forwarding headers to trust from a trusted proxy:
setProxy() gains $forwarded and $xForwarded flags, and the DI extension
exposes it as `proxyHeaders: both|xForwarded|forwarded|none`, mapped to the
two flags. Lets a deployment ignore a header its proxy does not manage (e.g.
a client-supplied "Forwarded" while the proxy only sets "X-Forwarded-For").

Default stays "both" (Forwarded preferred) — no behaviour change.

http by dg RequestFactory: reworks trusted-proxy forwarding resolution

Resolves the real client address, scheme and host from forwarding headers
correctly and consistently for both "Forwarded" and "X-Forwarded-*":

- Parses "Forwarded" into RFC 7239 hops instead of flattening "," and ";"
together, so proto/host are taken from the same hop as the selected client
address (removes the crude single-value guard that discarded them on any
multi-hop header).
- Picks the client address by stripping trailing trusted proxies and taking
the rightmost remaining hop, instead of blindly trusting the leftmost,
client-spoofable "for" value.
- Validates the resolved address as an IP; an unknown/obfuscated identifier
or a broken chain now yields null instead of a non-IP string. The stripping
and validation is shared by both paths via findClientHop().

Note: as before, this trusts whatever forwarding header the request carries.
The trusted proxy must set the header it uses and strip any client-supplied
forwarding header it does not, otherwise a passed-through header (e.g. a
client "Forwarded" while the proxy only manages "X-Forwarded-For") is trusted.

BC: getRemoteAddress() now returns null (not a non-IP string) when the
innermost forwarded value is not a valid IP address.

http by dg added AGENTS.md & DOCS
http by dg CI: switched Coveralls upload to the official action

The php-coveralls phar is replaced by coverallsapp/github-action, which
resolves the token and the absolute paths in the Clover report on its
own, so tests/.coveralls.yml is no longer needed.

Coverage is now uploaded even when tests crash under phpdbg. Being
phpdbg-compatible is not a goal, but until now the failing step aborted
the job before the upload ever ran. The job-level continue-on-error is
gone, so genuine failures, including a rejected upload, are visible.

http by dg phpstan: fixes
http by dg improved phpDoc
http by dg only compiler-optimized functions in use-function imports
di by dg removed Statement compatibility shims from expression classes WIP
di by dg deprecated magic properties (BC break)
di by dg removed compatibility for old class names
di by dg removed Definition::generateMethod() (BC break)
di by dg removed deprecated stuff

- %parameters%
- three ... dots
- @inject & @var

di by dg opened 4.0-dev
di by dg ParametersExtension: nested dynamic parameters are addressed by key paths, dotted strings keep their flat meaning (BC)

setDynamicParameterNames() entries are now shape-driven: a string stands for itself
(a plain top-level name in which a dot has no special meaning, as before), a list of
keys addresses a nested value. DynamicValue markers resolve to key paths as well.

This removes the reinterpretation of user-supplied dotted names introduced with the
dotted-name feature, at both places where it happened: the marker literal injection
(getRef over exploded name) and the config default lookup in the generated fallback.
The runtime name of a path is its dot-joined form; the dot-in-key guard moved to the
single name<->path mapping point and newly covers explicit paths too.

Claude-Session: https://claude.ai/code/session_01HcUe1zbekTPjU5LuCkxgFT

di by dg ParametersExtension: dynamic parameters via DynamicValue marker and dotted names WIP

setDynamicParameterNames() now accepts dotted names (e.g. 'db.password'), marking a value
nested inside a parameter as dynamic; the marker literal is injected at that position and
the promoted top-level key regenerates its subtree at runtime, so sibling values stay
compiled in.

Adds Nette\DI\DynamicValue, an inline marker usable directly in the parameters config at
any depth; the extension derives its dotted name from its position. Its optional value is
the default used when no runtime value is supplied. The value is excluded from
serialization so a per-request value (e.g. from nette/bootstrap) cannot bust the cache.

di by dg Definitions: the previous-generation API is deprecated

Deprecated in favour of the canonical vocabulary (removal planned for 4.0):
- addDefinition() and add*Definition() -> add() with factory()/accessor()/locator()/imported()
- getDefinition()/getDefinitions()/hasDefinition()/removeDefinition() -> get()/getAll()/has()/remove()
- getByType() -> has(type:) or get(type:)->getName()
- getDefinitionByType() -> get(type:)
- findByType()/findByTag() -> find(type:)/find(tag:) with Definition::getTag()
- literal() -> Nette\DI\code()
- addExcludedClasses() -> excludeFromAutowiring()
- addTag()/setAutowired() -> tag()/autowired()
- setFactory()/getFactory() -> setCreator()/getCreator()
- addSetup() -> setup(); setSetup() -> clearSetup() and setup()
- getImplement() -> getType()

The canonical methods no longer route through the deprecated ones: the lookup
primitives moved to private lookupByName()/lookupByType()/findAllByType()/
hasName()/removeByName(). The remaining internal uses of the old accessors
(Resolver, ServicesExtension, DefinitionSchema) are the compilation machinery
these wrappers delegate to; they become internal in 4.0.

di by dg Definition::getName() returns string (BC break) [WIP]
di by dg FactoryDefinition API
di by dg Definitions: the canonical decade API

ContainerBuilder / Nette\DI\Definitions:
- add() takes an optional type: (mirrors NEON's create:/type: sibling keys) and
preserves the concrete definition class via @template; type: cannot be combined
with a ready-made definition
- has() addresses by tag: too; get()/find()/has()/remove() complete the addressing
matrix (get/remove take unique addresses, find multi ones, has any)
- a backslashed positional name in get()/has() throws an instructive error instead
of silently failing (a service name never contains a backslash)
- excludeFromAutowiring() supersedes addExcludedClasses()
- the registration primitive moved to private registerDefinition(); public
addDefinition() and the four add*Definition() helpers now delegate to it

Definitions:
- getName() returns non-nullable string (the name is an invariant of registration);
the compilation machinery distinguishes unregistered definitions (e.g. factory
result definitions) via the new @internal tryGetName()
- exported() replaces the never-released setExported()
- the canonical verbs own the bodies: tag(), autowired(), setup(); the old names
delegate to them
- FactoryDefinition gained setCreator()/setup()/setArguments() delegating to the
result definition (mirrors NEON, where create:/setup:/arguments: on a factory
configure the produced service, while tags:/autowired: belong to the factory)

Nette\DI functions:
- factory(), accessor(), locator() build the concrete generated-definition kinds
(typed, one word = one meaning); implement() remains as the shape-detecting
dispatcher for runtime-unknown interfaces

di by dg CompilerExtension: onStartup() adds DSL expressions / code to initialize()

onStartup(Expression|string $code, array $args = []) is the clean, first-class
way to add startup code to the container's initialize() method - a full
replacement for the verbose $this->initialization->addBody($builder->
formatPhp(...)) dance. It accepts a DSL expression (e.g.
service('session')->method('start')) or a PHP statement string with ?
placeholders whose args may themselves be DSL expressions; both are completed
and generated via ContainerBuilder::formatPhp(), which is un-deprecated as the
DI-aware statement formatter behind it. Built-in DIExtension and
ParametersExtension migrated to onStartup() as reference examples. This is the
CompilerExtension-level onStartup (for extensions), distinct from the deferred
Definitions-level one (for config closures).

di by dg Extensions: rewritten to the Definitions DSL (DIExtension, DecoratorExtension, InjectExtension, SearchExtension)
di by dg Compiler: journal stamps mutations with the current actor (from Schedule)

Schedule now tracks the currently-running actor; the Compiler sets it
around each hook (the extension's name) and around the config closures
('config'), and the ContainerBuilder's fan-out pulls it when recording a
mutation. Each journal entry thus carries who made the change, giving a
real biography ("made: created by 'maker', setup added by 'deco'"), with
Journal::getCreator() for who registered a service. CompilerExtension gets
getName(). Blanket enrichment of every exception message with the origin
was left out - it adds noise to the common case; the actor is available
programmatically for targeted use (e.g. a future Tracy panel).

di by dg added Nette\DI\Compiler\Journal: mutation events from definitions

The single notifier on Definition is generalized from "type/autowired
changed" into a mutation event carrying (definition, action, value), and
every mutation primitive reports through it (setType/setCreator/setArguments/
addSetup/setSetup/addTag/removeTag/setTags/setAutowired/lazy/clearSetup) -
so it catches the new DSL verbs, the legacy verbs and direct access alike.
ContainerBuilder fans the stream out to two consumers: the existing
needsResolve lock (reacts only to type/autowired) and the new Journal, an
append-only biography of each service (getJournal()/getBiography()).
Silent during resolve(), where mutations are internal autowiring, not "what
an extension did". add() now registers before setting the creator so the
creator is journaled. The actor stamp comes in the next commit.

di by dg Config: a PHP config file can return a closure operating on Definitions

The DSL becomes reachable from a real config file: return
function (Definitions $di) { ... } and use the full vocabulary bare
(namespace Nette\DI). PhpAdapter wraps a returned closure into an
internal @closures section (so it flows through the array-based Loader
contract untouched); the Compiler runs those closures first, at load-time,
so their add() is immediate and precedes the extensions, while hook()/
remove() reaching framework services defer into a phase (ADR 0006). The
@closures section is exempt from the unknown-section check; every other
unknown section still errors.

di by dg added Nette\DI\Definitions, the config-facing view of ContainerBuilder

The narrow interface a PHP config closure receives and an extension can
type against: the immediate vocabulary (add/remove/get/find/has/getAll)
plus hook(), the only time machine (ADR 0006). It lives in one namespace
with the element functions so config files use everything bare, and it
deliberately hides the internal compilation machinery (resolve/complete/
generateCode) and the legacy names. ContainerBuilder implements it; the
of: generics carry through, so typing via Definitions matches the builder.
parameters()/onStartup() are omitted for now and add additively later.

di by dg ContainerBuilder: remove() (immediate) + teaching timeline errors

remove(name/type) drops an existing service immediately (ADR 0006: the
whole vocabulary is immediate, no hidden deferral), so the intuitive
if (has()) { remove(); add(); } just works. It reuses get(of: Definition)
for addressing and the missing-service error. To drop a service an
extension registers, use hook(Phase::Discover, ..., after: '*').

get()/remove() now teach the compilation timeline: while services may
still be registered (before the Discover phase completes, known via the
schedule), a "service not found" error gains a hint pointing at
hook(Phase::Modify, ...). A standalone builder without a schedule keeps
the plain error.

di by dg added Nette\DI\Compiler\Schedule; ContainerBuilder::hook() defers into a phase

Introduces the Nette\DI\Compiler namespace (Latte-style; new engine classes
are born there, existing ones move in 4.0) with its first inhabitant,
Schedule: the compilation schedule that owns the hook registry, their
before/after ordering and the phase state (which phase drains, which have
completed), and fails loudly on hooks that would never run. The Compiler
owns a Schedule and shares it with the ContainerBuilder (builder -> Schedule
<- Compiler, no builder<->Compiler cycle); Compiler::addHook() and the
running-phase / Setup-constraint guards move onto it, plus a new
completed-phase guard (scheduling into a phase that already ran throws).
The Register double drain keeps working: it is marked completed only after
the second drain.

ContainerBuilder::hook(phase, fn, before, after) is the DSL time machine
(ADR 0006): the only way to reach services registered later. It accepts
only Register/Discover/Modify (Setup is extension-level, Compile leaks the
generated ClassType); the callback receives the builder. A standalone
builder with no Compiler throws an instructive error.

di by dg ServiceDefinition: $lazy property replaced by lazy()/isLazy() (BC break)

The public $lazy property is now private; set it via lazy() and read it
via the new isLazy(): ?bool. Built-in extensions (DI, Services) updated
accordingly. BC break: code writing $def->lazy = ... or reading $def->lazy
must switch to $def->lazy(...) / $def->isLazy().

di by dg ContainerBuilder: add() registers a service from an expression or definition

add(name, creator) is the DSL registration verb: a ready-made definition
(from implement()/imported()) is registered as-is, anything else becomes a
ServiceDefinition whose creator is built via the same create() dispatch
(bare class -> new, callable -> factory method, expression -> passthrough).
It returns the real definition for fluent configuration and errors on a
duplicate name. Adds the implement() (factory/accessor/locator by interface
shape) and imported() element functions.

di by dg ContainerBuilder: retrieval API get(), find(), has(), getAll()

Short, consistent config-facing accessors addressing services by name or
by type:/tag:, mirroring add/remove. get() returns one definition
(throwing on 0/many for a type), find() returns definitions (find(tag:)
gives definitions, not tag values like findByTag()), has() is a bool,
getAll() aliases getDefinitions().

get()/find() take an of: parameter (ADR 0005): get() asserts the result
is of that definition class (ServiceDefinition by default, the common
case) so the 99% path is friction-free and the rest gets an instructive
error; find(of:) filters the result set. Conditional return types keep
static analysis exact without a PHPStan extension.

Last synchronization: 2026-09-26 23:02:48