| 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. | | | |