The Fragment Directives component

Available since version 7.6.0

The Component

This specialized fragment component contains a list of Directives that can be used by user-agent to further improve UX when navigating to or inside a website. As of time of this writing, only the Text Directive is defined by the URL Fragment Text Directives but nothing preclude the addition of other directives in the future.

The component on itself includes the same public API as the generic Fragment class and, in addition, it provides methods to handle directives.

Instantiation

use League\Uri\Components\FragmentDirectives;
use League\Uri\Components\FragmentDirectives\GenericDirective;
use League\Uri\Components\FragmentDirectives\TextDirective;

$fragment = new FragmentDirectives(
    new TextDirective(start: 'attributes', end: 'attribute', prefix: 'Deprecated'),
    GenericDirective::fromString('foo=bar'),
    'unknownDirective'
));

echo $fragment->toString();
//returns ":~:text=Deprecated-,attributes,attribute&foo=bar&unknownDirective"
echo $fragment->getUriComponent();
// returns "#:~:text=Deprecated-,attributes,attribute&foo=bar&unknownDirective"

As you can see with the example the FragmentDirectives acts as a container for distinct directives.

Directives can be submitted as specialized Directive class or as simple encoded directive string. For ease of usage you can also create a new instance from a submitted URI:

use League\Uri\Components\FragmentDirectives;

$fragment = FragmentDirectives::fromUri('https://example.com#:~:text=Deprecated-,attributes,attribute&foo=bar&unknownDirective');
count($fragment); //returns 3; the number of parsed directives.

Component Accessor Methods

You can use the following methods to navigate around the Directives container:

FragmentDirectives::isEmpty(): bool;
FragmentDirectives::count(): int;
FragmentDirectives::first(): ?Directive;
FragmentDirectives::last(): ?Directive;
FragmentDirectives::nth(int $offset): ?Directive;
FragmentDirectives::has(int ...$offset): bool;
FragmentDirectives::contains(FragmentDirective|Stringable|string $directive): bool;
FragmentDirectives::indexOf(FragmentDirective|Stringable|string $directive): ?int;

Apart from implementing the Countable interface, the FragmentDirectives class implements the IteratorAggregate interface to allow iterating over all the Directives, if needed.

$fragment = new FragmentDirectives(
    new TextDirective(start: 'attributes', end: 'attribute', prefix: 'Deprecated'),
    GenericDirective::fromString('foo=bar').
));

foreach ($fragment as $directive) {
    echo $directive->toString();
}

Component Manipulation Methods

The FragmentDirectives allows you to manipulate its content using the following methods:

FragmentDirectives::append(FragmentDirective|Stringable|string ...$directives): self;
FragmentDirectives::prepend(FragmentDirective|Stringable|string ...$directives): self;
FragmentDirectives::replace(int $offset, FragmentDirective|Stringable|string $directive): self;
FragmentDirectives::remove(int ...$offset): self;
FragmentDirectives::slice(int $offset, ?int $length = null): self;
FragmentDirectives::filter(callabck $callback): self;

All the modifying methods return a new instance to make the class immutable.

$fragment = new FragmentDirectives();
$newFragment = $fragment
    ->append(GenericDirective::fromString('foo=bar'))
    ->prepend(new TextDirective(start: 'attributes', end: 'attribute', prefix: 'Deprecated'))
));

$fragment->equals($newFragment); //returns false

The supported Directives

While the FragmentDirectives class allows submitting Directives as string, it is highly recommended to use a dedicated class to do so, as the grammar around building or parsing directives may be complex in regard to encoding characters and/or delimiters usage.

The FragmentDirectives class supports the TextDirective and the GenericDirective classes. Both classes implement the following FragmentDirective interface.

FragmentDirective::name(): string
FragmentDirective::value(): ?string
FragmentDirective::equals(mixed $value): ?string
FragmentDirective::toString(): string
FragmentDirective::__toString(): string

A directive is composed of two parts separated by the = separator. The name is required as it defines the directive syntax, while its value MAY be optional. When the value is not defined, the separator is omitted. The name() and value() methods return the decoded value of the directive part whereas the toString() method returns its encoded string representation. The __toString() method is an alias of the toString() method.

Text Directive

The text directive is used in browsers to highlight page fragments. It is represented by the TextDirective class.

use League\Uri\Components\FragmentDirectives\TextDirective;

$directive = new TextDirective(
    start: 'attributes',
    end: 'attribute',
    prefix: 'Deprecated',
    suffix: 'instead'
);
echo $directive->name();  //display "text"
echo $directive->value(); //display "Deprecated-,attributes,attribute,-instead"
echo $directive;          //display "text=Deprecated-,attributes,attribute,-instead"

the -, & and , characters are special and must be encoded if found in the text to avoid parsing errors.

when added in a fragment directive and applied on a webpage the text range which starts with attributes and ends with attribute and which is preceded by Deprecated and followed by instead will be highlighted. Depending on the user agent, the browser may scroll up to the highlighted text when the page loads.

The class follows the specification of the URL Fragment Text Directives. Apart from the start argument all the other arguments are optionals.

Once you have a TextDirective instance you can change any of its properties using the following wither- methods.

TextDirective::startsWith(string $text): self;  //change the starting text
TextDirective::endsWith(?string $text): self;   //change the optional ending text
TextDirective::precededBy(?string $text): self; //change the optional prefix context
TextDirective::followedBy(?string $text): self; //change the optional suffix context

All the methods return a new instance making the class immutable.

$directive = new TextDirective('foo')
        ->startsWith('y&lo')
        ->endsWith('bar')
        ->precededBy('john')
        ->followedBy('doe');

$directive->name();     // returns 'text';
$directive->value();    // returns "john-,y&lo,bar,-doe" the decoded value
$directive->toString(); // returns "text=john-,y%26lo,bar,-doe"

Generic Directive

This directive is considered generic because it only meets the minimal syntax requirements of a Directive. Unlike the TextDirective class, it does not perform any additional parsing or validation around the directive value. As a result, it can only be instantiated from a directive’s string representation.

use League\Uri\Components\FragmentDirectives\GenericDirective;

$directive = GenericDirective::fromString('fo%26o=bar');
$directive->value(); //returns "bar"
$directive->name();  //returns "fo&o"

It’s use case is to handle all the other Directives as long as they don’t have their own specific syntax.

Directive equality

The equals() method compares two directives based on their string representations. It returns true if both representations are identical, and false otherwise.

use League\Uri\Components\FragmentDirectives\GenericDirective;
use League\Uri\Components\FragmentDirectives\TextDirective;

$directive1 = GenericDirective::fromString('fo%26o=bar');
$directive2 = new TextDirective('foo')
        ->startsWith('y&lo')
        ->endsWith('bar')
        ->precededBy('john')
        ->followedBy('doe');

$directive3 = GenericDirective::fromString($directive2);

$directive1->equals($directive2);  // returns false
$directive1->equals($directive3);  // returns true
$directive1->equals('fo%26o=bar'); // returns true