Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractColor
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
10 / 10
13
100.00% covered (success)
100.00%
1 / 1
 render
n/a
0 / 0
n/a
0 / 0
0
 __toString
n/a
0 / 0
n/a
0 / 0
0
 toCss
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 channels
n/a
0 / 0
n/a
0 / 0
0
 __set
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __unset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetGet
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 offsetSet
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 offsetUnset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 channelsForError
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Color\Color;
16
17/**
18 * Abstract image color class
19 *
20 * @category   Pop
21 * @package    Pop\Color
22 * @author     Nick Sagona, III <nick@popphp.org>
23 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
24 * @license    https://www.popphp.org/license     New BSD License
25 * @version    2.0.0
26 */
27abstract class AbstractColor implements ColorInterface, \ArrayAccess
28{
29
30    /**
31     * String formats
32     */
33    const COMMA   = 'COMMA';
34    const CSS     = 'CSS';
35    const PERCENT = 'PERCENT';
36
37    /**
38     * Convert to readable string
39     *
40     * @param  ?string $format
41     * @return string
42     */
43    abstract public function render(?string $format = null): string;
44
45    /**
46     * Method to print the color object
47     *
48     * @return string
49     */
50    abstract public function __toString();
51
52    /**
53     * Convert to a CSS-formatted string, regardless of the class's default __toString() format
54     *
55     * @return string
56     */
57    public function toCss(): string
58    {
59        return $this->render(self::CSS);
60    }
61
62    /**
63     * Get the valid channel names for this color type, e.g. ['r', 'g', 'b', 'a']
64     *
65     * Every channel name here must have matching get{Name}()/set{Name}() methods
66     * (e.g. 'gray' => getGray()/setGray()) - that convention is what offsetGet()/offsetSet()
67     * dispatch on.
68     *
69     * @return array
70     */
71    abstract protected function channels(): array;
72
73    /**
74     * Magic method to set the color value
75     *
76     * @param  string $name
77     * @param  mixed  $value
78     * @throws Exception
79     * @return void
80     */
81    public function __set(string $name, mixed $value): void
82    {
83        $this->offsetSet($name, $value);
84    }
85
86    /**
87     * Magic method to return the color value
88     *
89     * @param  string $name
90     * @throws Exception
91     * @return mixed
92     */
93    public function __get(string $name): mixed
94    {
95        return $this->offsetGet($name);
96    }
97
98    /**
99     * Magic method to return whether the color value exists
100     *
101     * @param  string $name
102     * @return bool
103     */
104    public function __isset(string $name): bool
105    {
106        return $this->offsetExists($name);
107    }
108
109    /**
110     * Magic method to unset color value
111     *
112     * @param  string $name
113     * @throws Exception
114     * @return void
115     */
116    public function __unset(string $name): void
117    {
118        $this->offsetUnset($name);
119    }
120
121    /**
122     * ArrayAccess offsetExists
123     *
124     * @param  mixed $offset
125     * @return bool
126     */
127    public function offsetExists(mixed $offset): bool
128    {
129        return in_array((string)$offset, $this->channels(), true);
130    }
131
132    /**
133     * ArrayAccess offsetGet
134     *
135     * @param  mixed $offset
136     * @throws Exception
137     * @return mixed
138     */
139    public function offsetGet(mixed $offset): mixed
140    {
141        if (!$this->offsetExists($offset)) {
142            throw new Exception('Error: You can only use ' . $this->channelsForError() . '.');
143        }
144        return $this->{'get' . ucfirst((string)$offset)}();
145    }
146
147    /**
148     * ArrayAccess offsetSet
149     *
150     * @param  mixed $offset
151     * @param  mixed $value
152     * @throws Exception
153     * @return void
154     */
155    public function offsetSet(mixed $offset, mixed $value): void
156    {
157        if (!$this->offsetExists($offset)) {
158            throw new Exception('Error: You can only use ' . $this->channelsForError() . '.');
159        }
160        $this->{'set' . ucfirst((string)$offset)}($value);
161    }
162
163    /**
164     * ArrayAccess offsetUnset
165     *
166     * @param  mixed $offset
167     * @throws Exception
168     * @return void
169     */
170    public function offsetUnset(mixed $offset): void
171    {
172        throw new Exception('You cannot unset the properties of this color object.');
173    }
174
175    /**
176     * Format the valid channel names for an exception message, e.g. "'r', 'g', 'b' or 'a'"
177     *
178     * @return string
179     */
180    protected function channelsForError(): string
181    {
182        $quoted = array_map(fn($channel) => "'" . $channel . "'", $this->channels());
183        $last   = array_pop($quoted);
184
185        return (empty($quoted)) ? $last : implode(', ', $quoted) . ' or ' . $last;
186    }
187
188}