Skip to content

Commit 95d6542

Browse files
[12.x] Add InteractsWithData trait to ComponentAttributeBag (#56452)
* Add `InteractsWithData` trait to `ComponentAttributeBag` * Add test attribute retrieval using dot notation
1 parent ad13858 commit 95d6542

File tree

2 files changed

+279
-59
lines changed

2 files changed

+279
-59
lines changed

src/Illuminate/View/ComponentAttributeBag.php

Lines changed: 17 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Illuminate\Support\HtmlString;
1212
use Illuminate\Support\Str;
1313
use Illuminate\Support\Traits\Conditionable;
14+
use Illuminate\Support\Traits\InteractsWithData;
1415
use Illuminate\Support\Traits\Macroable;
1516
use IteratorAggregate;
1617
use JsonSerializable;
@@ -19,7 +20,7 @@
1920

2021
class ComponentAttributeBag implements Arrayable, ArrayAccess, IteratorAggregate, JsonSerializable, Htmlable, Stringable
2122
{
22-
use Conditionable, Macroable;
23+
use Conditionable, InteractsWithData, Macroable;
2324

2425
/**
2526
* The raw array of attributes.
@@ -41,11 +42,16 @@ public function __construct(array $attributes = [])
4142
/**
4243
* Get all the attribute values.
4344
*
45+
* @param array|mixed|null $keys
4446
* @return array
4547
*/
46-
public function all()
48+
public function all($keys = null)
4749
{
48-
return $this->attributes;
50+
if (is_null($keys)) {
51+
return $this->attributes;
52+
}
53+
54+
return $this->only($keys)->toArray();
4955
}
5056

5157
/**
@@ -72,56 +78,19 @@ public function get($key, $default = null)
7278
}
7379

7480
/**
75-
* Determine if a given attribute exists in the attribute array.
76-
*
77-
* @param array|string $key
78-
* @return bool
79-
*/
80-
public function has($key)
81-
{
82-
$keys = is_array($key) ? $key : func_get_args();
83-
84-
foreach ($keys as $value) {
85-
if (! array_key_exists($value, $this->attributes)) {
86-
return false;
87-
}
88-
}
89-
90-
return true;
91-
}
92-
93-
/**
94-
* Determine if any of the keys exist in the attribute array.
81+
* Retrieve data from the instance.
9582
*
96-
* @param array|string $key
97-
* @return bool
83+
* @param string|null $key
84+
* @param mixed $default
85+
* @return mixed
9886
*/
99-
public function hasAny($key)
87+
protected function data($key = null, $default = null)
10088
{
101-
if (! count($this->attributes)) {
102-
return false;
103-
}
104-
105-
$keys = is_array($key) ? $key : func_get_args();
106-
107-
foreach ($keys as $value) {
108-
if ($this->has($value)) {
109-
return true;
110-
}
89+
if (is_null($key)) {
90+
return $this->attributes;
11191
}
11292

113-
return false;
114-
}
115-
116-
/**
117-
* Determine if a given attribute is missing from the attribute array.
118-
*
119-
* @param string $key
120-
* @return bool
121-
*/
122-
public function missing($key)
123-
{
124-
return ! $this->has($key);
93+
return $this->get($key, $default);
12594
}
12695

12796
/**

tests/View/ViewComponentAttributeBagTest.php

Lines changed: 262 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,47 @@ public function testAttributeRetrieval()
101101
'test-extract-1',
102102
'test-extract-2' => 'defaultValue',
103103
]));
104+
105+
// Test only() method
106+
$bag = new ComponentAttributeBag(['class' => 'font-bold', 'name' => 'test', 'id' => 'my-id']);
107+
$this->assertInstanceOf(ComponentAttributeBag::class, $bag->only('class'));
108+
$this->assertSame('class="font-bold"', (string) $bag->only('class'));
109+
$this->assertSame('class="font-bold" name="test"', (string) $bag->only(['class', 'name']));
110+
$this->assertSame('', (string) $bag->only('missing'));
111+
$this->assertSame('name="test"', (string) $bag->only(['name', 'missing']));
112+
113+
// Test except() method
114+
$this->assertInstanceOf(ComponentAttributeBag::class, $bag->except('class'));
115+
$this->assertSame('name="test" id="my-id"', (string) $bag->except('class'));
116+
$this->assertSame('id="my-id"', (string) $bag->except(['class', 'name']));
117+
$this->assertSame('class="font-bold" name="test" id="my-id"', (string) $bag->except('missing'));
118+
$this->assertSame('class="font-bold" id="my-id"', (string) $bag->except(['name', 'missing']));
119+
}
120+
121+
public function testAttributeRetrievalUsingDotNotation()
122+
{
123+
$bag = new ComponentAttributeBag([
124+
'data.config' => 'value1',
125+
'x-on:click.prevent' => 'handler',
126+
'wire:model.lazy' => 'username',
127+
'@submit.prevent' => 'submitForm',
128+
'wire:model.debounce.500ms' => 'search',
129+
]);
130+
131+
$this->assertFalse($bag->has('data'));
132+
$this->assertFalse($bag->has('wire:model.debounce'));
133+
134+
$this->assertTrue($bag->has('data.config'));
135+
$this->assertTrue($bag->has('x-on:click.prevent'));
136+
$this->assertTrue($bag->has('wire:model.lazy'));
137+
$this->assertTrue($bag->has('@submit.prevent'));
138+
$this->assertTrue($bag->has('wire:model.debounce.500ms'));
139+
140+
$this->assertSame('value1', $bag->get('data.config'));
141+
$this->assertSame('handler', $bag->get('x-on:click.prevent'));
142+
$this->assertSame('username', $bag->get('wire:model.lazy'));
143+
$this->assertSame('submitForm', $bag->get('@submit.prevent'));
144+
$this->assertSame('search', $bag->get('wire:model.debounce.500ms'));
104145
}
105146

106147
public function testItMakesAnExceptionForAlpineXdata()
@@ -126,18 +167,20 @@ public function testItMakesAnExceptionForLivewireWireAttributes()
126167

127168
public function testAttributeExistence()
128169
{
129-
$bag = new ComponentAttributeBag(['name' => 'test']);
170+
$bag = new ComponentAttributeBag(['name' => 'test', 'href' => '', 'src' => null]);
130171

131-
$this->assertTrue((bool) $bag->has('name'));
132-
$this->assertTrue((bool) $bag->has(['name']));
133-
$this->assertTrue((bool) $bag->hasAny(['class', 'name']));
134-
$this->assertTrue((bool) $bag->hasAny('class', 'name'));
135-
$this->assertFalse((bool) $bag->missing('name'));
136-
$this->assertFalse((bool) $bag->has('class'));
137-
$this->assertFalse((bool) $bag->has(['class']));
138-
$this->assertFalse((bool) $bag->has(['name', 'class']));
139-
$this->assertFalse((bool) $bag->has('name', 'class'));
140-
$this->assertTrue((bool) $bag->missing('class'));
172+
$this->assertTrue($bag->has('src'));
173+
$this->assertTrue($bag->has('href'));
174+
$this->assertTrue($bag->has('name'));
175+
$this->assertTrue($bag->has(['name']));
176+
$this->assertTrue($bag->hasAny(['class', 'name']));
177+
$this->assertTrue($bag->hasAny('class', 'name'));
178+
$this->assertFalse($bag->missing('name'));
179+
$this->assertFalse($bag->has('class'));
180+
$this->assertFalse($bag->has(['class']));
181+
$this->assertFalse($bag->has(['name', 'class']));
182+
$this->assertFalse($bag->has('name', 'class'));
183+
$this->assertTrue($bag->missing('class'));
141184
}
142185

143186
public function testAttributeIsEmpty()
@@ -164,4 +207,212 @@ public function testAttributeIsArray()
164207
$this->assertIsArray($bag->toArray());
165208
$this->assertEquals(['name' => 'test', 'class' => 'font-bold'], $bag->toArray());
166209
}
210+
211+
public function testFilled()
212+
{
213+
$bag = new ComponentAttributeBag([
214+
'name' => 'test',
215+
'class' => 'font-bold',
216+
'empty' => '',
217+
'whitespace' => ' ',
218+
'zero' => '0',
219+
'false' => false,
220+
'null' => null,
221+
]);
222+
223+
$this->assertTrue($bag->filled('name'));
224+
$this->assertTrue($bag->filled('class'));
225+
$this->assertTrue($bag->filled('zero'));
226+
$this->assertTrue($bag->filled('false'));
227+
$this->assertFalse($bag->filled('null'));
228+
$this->assertFalse($bag->filled('empty'));
229+
$this->assertFalse($bag->filled('whitespace'));
230+
$this->assertFalse($bag->filled('nonexistent'));
231+
232+
// Multiple keys
233+
$this->assertTrue($bag->filled(['name', 'class']));
234+
$this->assertFalse($bag->filled(['name', 'empty']));
235+
$this->assertTrue($bag->filled('name', 'class'));
236+
$this->assertFalse($bag->filled('name', 'empty'));
237+
}
238+
239+
public function testIsNotFilled()
240+
{
241+
$bag = new ComponentAttributeBag([
242+
'name' => 'test',
243+
'empty' => '',
244+
'whitespace' => ' ',
245+
]);
246+
247+
$this->assertFalse($bag->isNotFilled('name'));
248+
$this->assertTrue($bag->isNotFilled('empty'));
249+
$this->assertTrue($bag->isNotFilled('whitespace'));
250+
$this->assertTrue($bag->isNotFilled('nonexistent'));
251+
252+
// Multiple keys - all must be empty
253+
$this->assertTrue($bag->isNotFilled(['empty', 'whitespace']));
254+
$this->assertFalse($bag->isNotFilled(['name', 'empty']));
255+
}
256+
257+
public function testAnyFilled()
258+
{
259+
$bag = new ComponentAttributeBag([
260+
'name' => 'test',
261+
'empty' => '',
262+
'whitespace' => ' ',
263+
]);
264+
265+
$this->assertTrue($bag->anyFilled(['name', 'empty']));
266+
$this->assertTrue($bag->anyFilled(['empty', 'name']));
267+
$this->assertFalse($bag->anyFilled(['empty', 'whitespace']));
268+
$this->assertTrue($bag->anyFilled('name', 'empty'));
269+
$this->assertFalse($bag->anyFilled('empty', 'whitespace'));
270+
}
271+
272+
public function testWhenFilled()
273+
{
274+
$bag = new ComponentAttributeBag([
275+
'name' => 'test',
276+
'empty' => '',
277+
]);
278+
279+
$result = $bag->whenFilled('name', function ($value) {
280+
return 'callback-'.$value;
281+
});
282+
$this->assertEquals('callback-test', $result);
283+
284+
$result = $bag->whenFilled('empty', function ($value) {
285+
return 'callback-'.$value;
286+
});
287+
$this->assertSame($bag, $result);
288+
289+
$result = $bag->whenFilled('empty', function ($value) {
290+
return 'callback-'.$value;
291+
}, function () {
292+
return 'default-callback';
293+
});
294+
$this->assertEquals('default-callback', $result);
295+
}
296+
297+
public function testWhenHas()
298+
{
299+
$bag = new ComponentAttributeBag(['name' => 'test']);
300+
301+
$result = $bag->whenHas('name', function ($value) {
302+
return 'callback-'.$value;
303+
});
304+
$this->assertEquals('callback-test', $result);
305+
306+
$result = $bag->whenHas('missing', function ($value) {
307+
return 'callback-'.$value;
308+
});
309+
$this->assertSame($bag, $result);
310+
311+
$result = $bag->whenHas('missing', function ($value) {
312+
return 'callback-'.$value;
313+
}, function () {
314+
return 'default-callback';
315+
});
316+
$this->assertEquals('default-callback', $result);
317+
}
318+
319+
public function testWhenMissing()
320+
{
321+
$bag = new ComponentAttributeBag(['name' => 'test']);
322+
323+
$result = $bag->whenMissing('name', function () {
324+
return 'callback';
325+
});
326+
$this->assertSame($bag, $result);
327+
328+
$result = $bag->whenMissing('missing', function () {
329+
return 'callback';
330+
});
331+
$this->assertEquals('callback', $result);
332+
333+
$result = $bag->whenMissing('name', function () {
334+
return 'callback';
335+
}, function () {
336+
return 'default-callback';
337+
});
338+
$this->assertEquals('default-callback', $result);
339+
}
340+
341+
public function testString()
342+
{
343+
$bag = new ComponentAttributeBag([
344+
'name' => 'test',
345+
'empty' => '',
346+
'number' => 123,
347+
]);
348+
349+
$this->assertInstanceOf(\Illuminate\Support\Stringable::class, $bag->string('name'));
350+
$this->assertEquals('test', (string) $bag->string('name'));
351+
$this->assertEquals('', (string) $bag->string('empty'));
352+
$this->assertEquals('123', (string) $bag->string('number'));
353+
$this->assertEquals('default', (string) $bag->string('missing', 'default'));
354+
}
355+
356+
public function testBoolean()
357+
{
358+
$bag = new ComponentAttributeBag([
359+
'true_string' => 'true',
360+
'false_string' => 'false',
361+
'one' => '1',
362+
'zero' => '0',
363+
'yes' => 'yes',
364+
'no' => 'no',
365+
'on' => 'on',
366+
'off' => 'off',
367+
]);
368+
369+
$this->assertTrue($bag->boolean('true_string'));
370+
$this->assertFalse($bag->boolean('false_string'));
371+
$this->assertTrue($bag->boolean('one'));
372+
$this->assertFalse($bag->boolean('zero'));
373+
$this->assertTrue($bag->boolean('yes'));
374+
$this->assertFalse($bag->boolean('no'));
375+
$this->assertTrue($bag->boolean('on'));
376+
$this->assertFalse($bag->boolean('off'));
377+
$this->assertTrue($bag->boolean('missing', true));
378+
$this->assertFalse($bag->boolean('missing', false));
379+
}
380+
381+
public function testInteger()
382+
{
383+
$bag = new ComponentAttributeBag([
384+
'number' => '123',
385+
'float' => '123.45',
386+
'string' => 'abc',
387+
]);
388+
389+
$this->assertSame(123, $bag->integer('number'));
390+
$this->assertSame(123, $bag->integer('float'));
391+
$this->assertSame(0, $bag->integer('string'));
392+
$this->assertSame(42, $bag->integer('missing', 42));
393+
}
394+
395+
public function testFloat()
396+
{
397+
$bag = new ComponentAttributeBag([
398+
'number' => '123',
399+
'float' => '123.45',
400+
'string' => 'abc',
401+
]);
402+
403+
$this->assertSame(123.0, $bag->float('number'));
404+
$this->assertSame(123.45, $bag->float('float'));
405+
$this->assertSame(0.0, $bag->float('string'));
406+
$this->assertSame(42.5, $bag->float('missing', 42.5));
407+
}
408+
409+
public function testExists()
410+
{
411+
$bag = new ComponentAttributeBag(['name' => 'test']);
412+
413+
$this->assertTrue($bag->exists('name'));
414+
$this->assertFalse($bag->exists('missing'));
415+
$this->assertTrue($bag->exists(['name']));
416+
$this->assertFalse($bag->exists(['missing']));
417+
}
167418
}

0 commit comments

Comments
 (0)