setName($name)->setValue($value);
}
/**
* Create a new HTML attribute from the given name and value
*
* @param string $name The name of the attribute
* @param string|bool|array|null $value The value of the attribute
*
* @return static
*
* @throws InvalidArgumentException If the name of the attribute contains special characters
*/
public static function create($name, $value)
{
return new static($name, $value);
}
/**
* Create a new empty HTML attribute from the given name
*
* The value of the attribute will be null after construction.
*
* @param string $name The name of the attribute
*
* @return static
*
* @throws InvalidArgumentException If the name of the attribute contains special characters
*/
public static function createEmpty($name)
{
return new static($name, null);
}
/**
* Escape the name of an attribute
*
* Makes sure that the name of an attribute really is a string.
*
* @param string $name
*
* @return string
*/
public static function escapeName($name)
{
return (string) $name;
}
/**
* Escape the value of an attribute
*
* If the value is an array, returns the string representation
* of all array elements joined with the specified glue string.
*
* Values are escaped according to the HTML5 double-quoted attribute value syntax:
* {@link https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 }.
*
* @param string|array $value
* @param string $glue Glue string to join elements if value is an array
*
* @return string
*/
public static function escapeValue($value, $glue = ' ')
{
if (is_array($value)) {
$value = implode($glue, $value);
}
// We force double-quoted attribute value syntax so let's start by escaping double quotes
$value = str_replace('"', '"', $value);
// In addition, values must not contain ambiguous ampersands
$value = preg_replace_callback(
'/&[0-9A-Z]+;/i',
function ($match) {
$subject = $match[0];
if (htmlspecialchars_decode($subject, ENT_COMPAT | ENT_HTML5) === $subject) {
// Ambiguous ampersand
return str_replace('&', '&', $subject);
}
return $subject;
},
$value
);
return $value;
}
/**
* Get the name of the attribute
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set the name of the attribute
*
* @param string $name
*
* @return $this
*
* @throws InvalidArgumentException If the name contains special characters
*/
protected function setName($name)
{
if (! preg_match('/^[a-z][a-z0-9:-]*$/i', $name)) {
throw new InvalidArgumentException(sprintf(
'Attribute names with special characters are not yet allowed: %s',
$name
));
}
$this->name = $name;
return $this;
}
/**
* Get the value of the attribute
*
* @return string|bool|array|null
*/
public function getValue()
{
return $this->value;
}
/**
* Set the value of the attribute
*
* @param string|bool|array|null $value
*
* @return $this
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Add the given value(s) to the attribute
*
* @param string|array $value The value(s) to add
*
* @return $this
*/
public function addValue($value)
{
$this->value = array_merge((array) $this->value, (array) $value);
return $this;
}
/**
* Remove the given value(s) from the attribute
*
* The current value is set to null if it matches the value to remove
* or is in the array of values to remove.
*
* If the current value is an array, all elements are removed which
* match the value(s) to remove.
*
* Does nothing if there is no such value to remove.
*
* @param string|array $value The value(s) to remove
*
* @return $this
*/
public function removeValue($value)
{
$value = (array) $value;
$current = $this->getValue();
if (is_array($current)) {
$this->setValue(array_diff($current, $value));
} elseif (in_array($current, $value, true)) {
$this->setValue(null);
}
return $this;
}
/**
* Test and return true if the attribute is boolean, false otherwise
*
* @return bool
*/
public function isBoolean()
{
return is_bool($this->value);
}
/**
* Test and return true if the attribute is empty, false otherwise
*
* Null and the empty array will be considered empty.
*
* @return bool
*/
public function isEmpty()
{
return $this->value === null || $this->value === [];
}
/**
* Render the attribute to HTML
*
* If the value of the attribute is of type boolean, it will be rendered as
* {@link http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes boolean attribute}.
* Note that in this case if the value of the attribute is false, the empty string will be returned.
*
* If the value of the attribute is null or an empty array,
* the empty string will be returned as well.
*
* Escaping of the attribute's value takes place automatically using {@link Attribute::escapeValue()}.
*
* @return string
*/
public function render()
{
if ($this->isEmpty()) {
return '';
}
if ($this->isBoolean()) {
if ($this->value) {
return $this->renderName();
}
return '';
} else {
return sprintf(
'%s="%s"',
$this->renderName(),
$this->renderValue()
);
}
}
/**
* Render the name of the attribute to HTML
*
* @return string
*/
public function renderName()
{
return static::escapeName($this->name);
}
/**
* Render the value of the attribute to HTML
*
* @return string
*/
public function renderValue()
{
return static::escapeValue($this->value);
}
}