master

laravel/framework

Last updated at: 29/12/2023 09:26

ComponentAttributeBag.php

TLDR

This file, ComponentAttributeBag.php, is a class file that defines a class called ComponentAttributeBag. This class is used to manage and manipulate component attributes. It provides methods for getting, setting, filtering, merging, and manipulating attributes. The class implements several interfaces, including ArrayAccess and JsonSerializable, and uses traits like Conditionable and Macroable to add additional functionality.

Methods

__construct

The __construct method is the constructor of the ComponentAttributeBag class. It initializes the class by accepting an optional array of attributes and setting them as the $attributes property.

first

The first method returns the value of the first attribute in the bag. It accepts an optional default value, which is returned if there are no attributes in the bag.

get

The get method returns the value of a given attribute from the attribute array. It accepts a key and an optional default value, which is returned if the attribute does not exist in the bag.

has

The has method determines if a given attribute exists in the attribute array. It accepts a key or an array of keys and returns true if all the keys exist in the bag.

hasAny

The hasAny method determines if any of the given keys exist in the attribute array. It accepts a key or an array of keys and returns true if any of the keys exist in the bag.

missing

The missing method determines if a given attribute is missing from the attribute array. It accepts a key and returns true if the attribute does not exist in the bag.

only

The only method creates a new ComponentAttributeBag instance that includes only the given attribute keys from the attribute array. It accepts a key or an array of keys and returns a new instance of ComponentAttributeBag.

except

The except method creates a new ComponentAttributeBag instance that excludes the given attribute keys from the attribute array. It accepts a key or an array of keys and returns a new instance of ComponentAttributeBag.

filter

The filter method creates a new ComponentAttributeBag instance that includes only the attributes that pass a given callback filter. It accepts a callable callback and returns a new instance of ComponentAttributeBag.

whereStartsWith

The whereStartsWith method creates a new ComponentAttributeBag instance that includes only the attributes with keys that start with the given value or pattern. It accepts a string or an array of strings and returns a new instance of ComponentAttributeBag.

whereDoesntStartWith

The whereDoesntStartWith method creates a new ComponentAttributeBag instance that excludes the attributes with keys that start with the given value or pattern. It accepts a string or an array of strings and returns a new instance of ComponentAttributeBag.

thatStartWith

The thatStartWith method is an alias for the whereStartsWith method.

onlyProps

The onlyProps method creates a new ComponentAttributeBag instance that includes only the attributes with keys that match the given prop names. It accepts a key or an array of keys and returns a new instance of ComponentAttributeBag.

exceptProps

The exceptProps method creates a new ComponentAttributeBag instance that excludes the attributes with keys that match the given prop names. It accepts a key or an array of keys and returns a new instance of ComponentAttributeBag.

class

The class method applies conditional merging of classes into the attribute bag. It accepts a class list and returns a new instance of ComponentAttributeBag with the merged classes.

style

The style method applies conditional merging of styles into the attribute bag. It accepts a style list and returns a new instance of ComponentAttributeBag with the merged styles.

merge

The merge method merges additional attributes and values into the attribute bag. It accepts an array of attribute defaults and an optional escape flag. It returns a new instance of ComponentAttributeBag with the merged attributes.

shouldEscapeAttributeValue

The shouldEscapeAttributeValue method determines if a specific attribute value should be escaped. It accepts a boolean escape flag and a value and returns true if the value should be escaped.

prepends

The prepends method creates a new appendable attribute value. It accepts a value and returns a new instance of AppendableAttributeValue.

resolveAppendableAttributeDefault

The resolveAppendableAttributeDefault method resolves an appendable attribute value's default value. It accepts an array of attribute defaults, a key, and an escape flag. It returns the resolved value.

isEmpty

The isEmpty method determines if the attribute bag is empty. It returns true if the attribute bag contains no attributes.

isNotEmpty

The isNotEmpty method determines if the attribute bag is not empty. It returns true if the attribute bag contains attributes.

getAttributes

The getAttributes method returns all of the raw attributes as an array.

setAttributes

The setAttributes method sets the underlying attributes. It accepts an array of attributes and handles the merging of attribute bags if one of the attributes is an instance of ComponentAttributeBag.

toHtml

The toHtml method converts the attribute bag into a string of HTML. It returns the string representation of the attribute bag.

__invoke

The __invoke method is an alternative way to merge additional attributes/values into the attribute bag. It accepts an array of attribute defaults and returns a new instance of HtmlString.

offsetExists

The offsetExists method determines if the given offset exists. It accepts a string offset and returns true if the offset exists in the attribute array.

offsetGet

The offsetGet method gets the value at the given offset. It accepts a string offset and returns the value of the attribute.

offsetSet

The offsetSet method set the value at a given offset. It accepts a string offset and a value and sets the value in the attribute array.

offsetUnset

The offsetUnset method removes the value at the given offset. It accepts a string offset and removes the value from the attribute array.

getIterator

The getIterator method returns an iterator for the attribute array. It returns an ArrayIterator instance.

jsonSerialize

The jsonSerialize method converts the object into a JSON serializable form. It returns the attributes as they are.

__toString

The __toString method converts the attributes into a single HTML-ready string. It overrides the default __toString method and returns the string representation of the attributes.

END

<?php

namespace Illuminate\View;

use ArrayAccess;
use ArrayIterator;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use IteratorAggregate;
use JsonSerializable;
use Stringable;
use Traversable;

class ComponentAttributeBag implements ArrayAccess, Htmlable, JsonSerializable, IteratorAggregate, Stringable
{
    use Conditionable, Macroable;

    /**
     * The raw array of attributes.
     *
     * @var array
     */
    protected $attributes = [];

    /**
     * Create a new component attribute bag instance.
     *
     * @param  array  $attributes
     * @return void
     */
    public function __construct(array $attributes = [])
    {
        $this->attributes = $attributes;
    }

    /**
     * Get the first attribute's value.
     *
     * @param  mixed  $default
     * @return mixed
     */
    public function first($default = null)
    {
        return $this->getIterator()->current() ?? value($default);
    }

    /**
     * Get a given attribute from the attribute array.
     *
     * @param  string  $key
     * @param  mixed  $default
     * @return mixed
     */
    public function get($key, $default = null)
    {
        return $this->attributes[$key] ?? value($default);
    }

    /**
     * Determine if a given attribute exists in the attribute array.
     *
     * @param  array|string  $key
     * @return bool
     */
    public function has($key)
    {
        $keys = is_array($key) ? $key : func_get_args();

        foreach ($keys as $value) {
            if (! array_key_exists($value, $this->attributes)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Determine if any of the keys exist in the attribute array.
     *
     * @param  array|string  $key
     * @return bool
     */
    public function hasAny($key)
    {
        if (! count($this->attributes)) {
            return false;
        }

        $keys = is_array($key) ? $key : func_get_args();

        foreach ($keys as $value) {
            if ($this->has($value)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Determine if a given attribute is missing from the attribute array.
     *
     * @param  string  $key
     * @return bool
     */
    public function missing($key)
    {
        return ! $this->has($key);
    }

    /**
     * Only include the given attribute from the attribute array.
     *
     * @param  mixed  $keys
     * @return static
     */
    public function only($keys)
    {
        if (is_null($keys)) {
            $values = $this->attributes;
        } else {
            $keys = Arr::wrap($keys);

            $values = Arr::only($this->attributes, $keys);
        }

        return new static($values);
    }

    /**
     * Exclude the given attribute from the attribute array.
     *
     * @param  mixed|array  $keys
     * @return static
     */
    public function except($keys)
    {
        if (is_null($keys)) {
            $values = $this->attributes;
        } else {
            $keys = Arr::wrap($keys);

            $values = Arr::except($this->attributes, $keys);
        }

        return new static($values);
    }

    /**
     * Filter the attributes, returning a bag of attributes that pass the filter.
     *
     * @param  callable  $callback
     * @return static
     */
    public function filter($callback)
    {
        return new static(collect($this->attributes)->filter($callback)->all());
    }

    /**
     * Return a bag of attributes that have keys starting with the given value / pattern.
     *
     * @param  string|string[]  $needles
     * @return static
     */
    public function whereStartsWith($needles)
    {
        return $this->filter(function ($value, $key) use ($needles) {
            return Str::startsWith($key, $needles);
        });
    }

    /**
     * Return a bag of attributes with keys that do not start with the given value / pattern.
     *
     * @param  string|string[]  $needles
     * @return static
     */
    public function whereDoesntStartWith($needles)
    {
        return $this->filter(function ($value, $key) use ($needles) {
            return ! Str::startsWith($key, $needles);
        });
    }

    /**
     * Return a bag of attributes that have keys starting with the given value / pattern.
     *
     * @param  string|string[]  $needles
     * @return static
     */
    public function thatStartWith($needles)
    {
        return $this->whereStartsWith($needles);
    }

    /**
     * Only include the given attribute from the attribute array.
     *
     * @param  mixed|array  $keys
     * @return static
     */
    public function onlyProps($keys)
    {
        return $this->only($this->extractPropNames($keys));
    }

    /**
     * Exclude the given attribute from the attribute array.
     *
     * @param  mixed|array  $keys
     * @return static
     */
    public function exceptProps($keys)
    {
        return $this->except($this->extractPropNames($keys));
    }

    /**
     * Extract prop names from given keys.
     *
     * @param  mixed|array  $keys
     * @return array
     */
    protected function extractPropNames($keys)
    {
        $props = [];

        foreach ($keys as $key => $defaultValue) {
            $key = is_numeric($key) ? $defaultValue : $key;

            $props[] = $key;
            $props[] = Str::kebab($key);
        }

        return $props;
    }

    /**
     * Conditionally merge classes into the attribute bag.
     *
     * @param  mixed|array  $classList
     * @return static
     */
    public function class($classList)
    {
        $classList = Arr::wrap($classList);

        return $this->merge(['class' => Arr::toCssClasses($classList)]);
    }

    /**
     * Conditionally merge styles into the attribute bag.
     *
     * @param  mixed|array  $styleList
     * @return static
     */
    public function style($styleList)
    {
        $styleList = Arr::wrap($styleList);

        return $this->merge(['style' => Arr::toCssStyles($styleList)]);
    }

    /**
     * Merge additional attributes / values into the attribute bag.
     *
     * @param  array  $attributeDefaults
     * @param  bool  $escape
     * @return static
     */
    public function merge(array $attributeDefaults = [], $escape = true)
    {
        $attributeDefaults = array_map(function ($value) use ($escape) {
            return $this->shouldEscapeAttributeValue($escape, $value)
                        ? e($value)
                        : $value;
        }, $attributeDefaults);

        [$appendableAttributes, $nonAppendableAttributes] = collect($this->attributes)
                    ->partition(function ($value, $key) use ($attributeDefaults) {
                        return $key === 'class' || $key === 'style' || (
                            isset($attributeDefaults[$key]) &&
                            $attributeDefaults[$key] instanceof AppendableAttributeValue
                        );
                    });

        $attributes = $appendableAttributes->mapWithKeys(function ($value, $key) use ($attributeDefaults, $escape) {
            $defaultsValue = isset($attributeDefaults[$key]) && $attributeDefaults[$key] instanceof AppendableAttributeValue
                        ? $this->resolveAppendableAttributeDefault($attributeDefaults, $key, $escape)
                        : ($attributeDefaults[$key] ?? '');

            if ($key === 'style') {
                $value = Str::finish($value, ';');
            }

            return [$key => implode(' ', array_unique(array_filter([$defaultsValue, $value])))];
        })->merge($nonAppendableAttributes)->all();

        return new static(array_merge($attributeDefaults, $attributes));
    }

    /**
     * Determine if the specific attribute value should be escaped.
     *
     * @param  bool  $escape
     * @param  mixed  $value
     * @return bool
     */
    protected function shouldEscapeAttributeValue($escape, $value)
    {
        if (! $escape) {
            return false;
        }

        return ! is_object($value) &&
               ! is_null($value) &&
               ! is_bool($value);
    }

    /**
     * Create a new appendable attribute value.
     *
     * @param  mixed  $value
     * @return \Illuminate\View\AppendableAttributeValue
     */
    public function prepends($value)
    {
        return new AppendableAttributeValue($value);
    }

    /**
     * Resolve an appendable attribute value default value.
     *
     * @param  array  $attributeDefaults
     * @param  string  $key
     * @param  bool  $escape
     * @return mixed
     */
    protected function resolveAppendableAttributeDefault($attributeDefaults, $key, $escape)
    {
        if ($this->shouldEscapeAttributeValue($escape, $value = $attributeDefaults[$key]->value)) {
            $value = e($value);
        }

        return $value;
    }

    /**
     * Determine if the attribute bag is empty.
     *
     * @return bool
     */
    public function isEmpty()
    {
        return trim((string) $this) === '';
    }

    /**
     * Determine if the attribute bag is not empty.
     *
     * @return bool
     */
    public function isNotEmpty()
    {
        return ! $this->isEmpty();
    }

    /**
     * Get all of the raw attributes.
     *
     * @return array
     */
    public function getAttributes()
    {
        return $this->attributes;
    }

    /**
     * Set the underlying attributes.
     *
     * @param  array  $attributes
     * @return void
     */
    public function setAttributes(array $attributes)
    {
        if (isset($attributes['attributes']) &&
            $attributes['attributes'] instanceof self) {
            $parentBag = $attributes['attributes'];

            unset($attributes['attributes']);

            $attributes = $parentBag->merge($attributes, $escape = false)->getAttributes();
        }

        $this->attributes = $attributes;
    }

    /**
     * Get content as a string of HTML.
     *
     * @return string
     */
    public function toHtml()
    {
        return (string) $this;
    }

    /**
     * Merge additional attributes / values into the attribute bag.
     *
     * @param  array  $attributeDefaults
     * @return \Illuminate\Support\HtmlString
     */
    public function __invoke(array $attributeDefaults = [])
    {
        return new HtmlString((string) $this->merge($attributeDefaults));
    }

    /**
     * Determine if the given offset exists.
     *
     * @param  string  $offset
     * @return bool
     */
    public function offsetExists($offset): bool
    {
        return isset($this->attributes[$offset]);
    }

    /**
     * Get the value at the given offset.
     *
     * @param  string  $offset
     * @return mixed
     */
    public function offsetGet($offset): mixed
    {
        return $this->get($offset);
    }

    /**
     * Set the value at a given offset.
     *
     * @param  string  $offset
     * @param  mixed  $value
     * @return void
     */
    public function offsetSet($offset, $value): void
    {
        $this->attributes[$offset] = $value;
    }

    /**
     * Remove the value at the given offset.
     *
     * @param  string  $offset
     * @return void
     */
    public function offsetUnset($offset): void
    {
        unset($this->attributes[$offset]);
    }

    /**
     * Get an iterator for the items.
     *
     * @return \ArrayIterator
     */
    public function getIterator(): Traversable
    {
        return new ArrayIterator($this->attributes);
    }

    /**
     * Convert the object into a JSON serializable form.
     *
     * @return mixed
     */
    public function jsonSerialize(): mixed
    {
        return $this->attributes;
    }

    /**
     * Implode the attributes into a single HTML ready string.
     *
     * @return string
     */
    public function __toString()
    {
        $string = '';

        foreach ($this->attributes as $key => $value) {
            if ($value === false || is_null($value)) {
                continue;
            }

            if ($value === true) {
                // Exception for Alpine...
                $value = $key === 'x-data' ? '' : $key;
            }

            $string .= ' '.$key.'="'.str_replace('"', '\\"', trim($value)).'"';
        }

        return trim($string);
    }
}