Framework

Repository Author Commit message Committed SHA  
 
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.

di by dg Definitions: fluent verbs setup(), tag(), autowired(), lazy() (old names stay)

Adds the definition-handle side of the two-object split from
dsl-proposal.md. Definition base gets tag()/removeTag()/autowired();
ServiceDefinition gets setup() (the '$prop'/'$prop[]'/method mini-language,
or an expression step), clearSetup() and lazy(). The old verbs (addSetup,
addTag, setAutowired) stay untouched; deprecation is a 4.0 concern. call()
lives only on expressions and setup only on definitions, so the split is
structurally enforced.

di by dg added element functions (service, wire, create, param, ...)

First wave of the DSL value builders from dsl-proposal.md: pure,
context-free factories for Expression nodes, living in one namespace with
the Definitions type so config files can use them bare. service()/wire()/
self() build references, param()/expand() build parameter expansions,
create()/call() dispatch by shape (bare class -> new, callable -> factory
method), services()/cast()/not()/code() wrap the remaining nodes. No
string decoding: @service and %param% stay literal.

di by dg ArgumentPlaceholder::Single marks an open argument position for autowiring

In an Instantiation or Call, an ArgumentPlaceholder::Single argument now
means "autowire this position" (as if the argument was omitted); it is
stripped before autowiring so later positional arguments keep their
place, mirroring the NEON `_` behaviour. In a PartialCall it stays a
runtime placeholder as before. This is what the DSL wire() without
arguments will produce.

di by dg Expressions: fluent method(), property() and constant() for chaining

Adds the expression side of the two-object split from dsl-proposal.md as
the Chaining trait, used only by the nodes whose value is an object:
Reference, Instantiation and Call. Deliberately NOT on the Expression
base class - value expressions (casts, parameter expansions, service
collections) have nothing to chain on and must not offer it; the base
carries only the compilation lifecycle. PartialCall stays out as well:
chaining after a first-class callable is a NEON-only edge case. Each
method returns the matching node, so chains like
service('a')->method('getUrl')->method('getHost') build the AST directly.

di by dg PartialCall supports argument placeholders (PHP 8.6 partial application)
di by dg added expression classes (Call, Instantiation, ConstantFetch, Expansion, PropertyAccess, ServiceCollection, SpecialFunction, PhpCode)
di by dg added PartialCall, expression representing PHP first-class callable syntax
di by dg ServiceDefinition: creator can be any Expression

enables first-class callable in 'create:'

di by dg Expression::transformValues() used by Helpers to traverse expressions

Last synchronization: 2026-08-27 05:02:41