diff --git a/README.md b/README.md index 6c88717..6a5ce94 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,32 @@ class UserFeedItem extends FeedItem } ``` +#### Adding an array of elements + +In some cases, you need to place an array of elements with the same names. For example: + +```xml +https://via.placeholder.com/640x480.png/009966?text=beatae +https://via.placeholder.com/640x480.png/000011?text=deleniti +https://via.placeholder.com/640x480.png/009999?text=voluptates +``` + +To do this, add a symbol of `@` to the beginning of the key name: + +```php +use DragonCode\LaravelFeed\Feeds\Items\FeedItem; + +class UserFeedItem extends FeedItem +{ + public function toArray(): array + { + return [ + '@picture' => $this->model->images, + ]; + } +} +``` + #### Header information If it is necessary to change the file cap, override the `header` method in the feed class: diff --git a/src/Services/ConvertToXml.php b/src/Services/ConvertToXml.php index f1b82ef..75f6d9c 100644 --- a/src/Services/ConvertToXml.php +++ b/src/Services/ConvertToXml.php @@ -8,11 +8,14 @@ use DOMElement; use DragonCode\LaravelFeed\Feeds\Items\FeedItem; use Illuminate\Container\Attributes\Config; +use Illuminate\Support\Str; use function htmlspecialchars; use function is_array; +use function is_bool; use function preg_replace; use function str_replace; +use function str_starts_with; class ConvertToXml { @@ -54,6 +57,7 @@ protected function performItem(DOMElement $parent, array $items): void $this->isAttributes($key) => $this->setAttributes($parent, $value), $this->isCData($key) => $this->setCData($parent, $value), $this->isMixed($key) => $this->setMixed($parent, $value), + $this->isArray($key) => $this->setItemsArray($parent, $value, $key), default => $this->setItems($parent, $key, $value), }; } @@ -74,6 +78,11 @@ protected function isMixed(string $key): bool return $key === '@mixed'; } + protected function isArray(string $key): bool + { + return str_starts_with($key, '@'); + } + protected function createElement(string $name, bool|float|int|string|null $value = ''): DOMElement { return $this->document->createElement($name, $this->convertValue($value)); @@ -101,6 +110,15 @@ protected function setMixed(DOMElement $element, string $value): void $element->appendChild($fragment); } + protected function setItemsArray(DOMElement $parent, mixed $value, string $key): void + { + $key = Str::substr($key, 1); + + foreach ($value as $item) { + $this->setItems($parent, $key, $item); + } + } + protected function setItems(DOMElement $parent, string $key, mixed $value): void { $element = $this->createElement($key, is_array($value) ? '' : $this->convertValue($value)); @@ -117,13 +135,17 @@ protected function toXml(DOMElement $item): string return $this->document->saveXML($item); } - protected function convertKey(string $key): string + protected function convertKey(int|string $key): string { - return str_replace(' ', '_', $key); + return str_replace(' ', '_', (string) $key); } protected function convertValue(bool|float|int|string|null $value): string { + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + return $this->removeControlCharacters( htmlspecialchars((string) $value) ); diff --git a/tests/.pest/snapshots/Feature/YandexTest/export.snap b/tests/.pest/snapshots/Feature/YandexTest/export.snap index 58988d2..77983d6 100644 --- a/tests/.pest/snapshots/Feature/YandexTest/export.snap +++ b/tests/.pest/snapshots/Feature/YandexTest/export.snap @@ -24,6 +24,7 @@ 100 RUR The Best + https://via.placeholder.com/640x480.png/008877?text=repudiandae http://localhost/products/GD-PRDCT-2 @@ -34,6 +35,9 @@ 250 RUR The Best + https://via.placeholder.com/640x480.png/009966?text=beatae + https://via.placeholder.com/640x480.png/000011?text=deleniti + https://via.placeholder.com/640x480.png/009999?text=voluptates http://localhost/products/GD-PRDCT-3 @@ -44,6 +48,8 @@ 400 RUR The Best + https://via.placeholder.com/640x480.png/000044?text=asperiores + https://via.placeholder.com/640x480.png/0055ff?text=expedita diff --git a/workbench/app/Data/ProductFakeData.php b/workbench/app/Data/ProductFakeData.php index 5a3ba43..963c5d1 100644 --- a/workbench/app/Data/ProductFakeData.php +++ b/workbench/app/Data/ProductFakeData.php @@ -4,8 +4,6 @@ namespace Workbench\App\Data; -use function json_encode; - class ProductFakeData { public static function toArray(): array @@ -22,9 +20,9 @@ public static function toArray(): array 'brand' => 'The Best', - 'images' => json_encode([ - 'https://via.placeholder.com/640x480.png/00ff55?text=s1', - ]), + 'images' => [ + 'https://via.placeholder.com/640x480.png/008877?text=repudiandae', + ], 'created_at' => '2025-08-31 00:00:00', 'updated_at' => '2025-08-31 20:00:00', @@ -40,11 +38,11 @@ public static function toArray(): array 'brand' => 'The Best', - 'images' => json_encode([ - 'https://via.placeholder.com/640x480.png/00ff55?text=s20', - 'https://via.placeholder.com/640x480.png/00ff55?text=s21', - 'https://via.placeholder.com/640x480.png/00ff55?text=s22', - ]), + 'images' => [ + 'https://via.placeholder.com/640x480.png/009966?text=beatae', + 'https://via.placeholder.com/640x480.png/000011?text=deleniti', + 'https://via.placeholder.com/640x480.png/009999?text=voluptates', + ], 'created_at' => '2025-08-30 00:00:00', 'updated_at' => '2025-08-30 19:00:00', @@ -60,10 +58,10 @@ public static function toArray(): array 'brand' => 'The Best', - 'images' => json_encode([ - 'https://via.placeholder.com/640x480.png/00ff55?text=s30', - 'https://via.placeholder.com/640x480.png/00ff55?text=s31', - ]), + 'images' => [ + 'https://via.placeholder.com/640x480.png/000044?text=asperiores', + 'https://via.placeholder.com/640x480.png/0055ff?text=expedita', + ], 'created_at' => '2025-08-29 00:00:00', 'updated_at' => '2025-08-29 18:00:00', diff --git a/workbench/app/Feeds/Items/YandexFeedItem.php b/workbench/app/Feeds/Items/YandexFeedItem.php index 2bcc15e..54bcc81 100644 --- a/workbench/app/Feeds/Items/YandexFeedItem.php +++ b/workbench/app/Feeds/Items/YandexFeedItem.php @@ -19,7 +19,7 @@ public function attributes(): array return [ 'id' => $this->model->id, - 'available' => ! empty($this->model->quantity) ? 'true' : 'false', + 'available' => ! empty($this->model->quantity), 'type' => 'vendor.model', ]; @@ -40,7 +40,7 @@ public function toArray(): array 'currencyId' => 'RUR', 'vendor' => $this->model->brand, - // 'picture' => $this->model->images, + '@picture' => $this->model->images, ]; } } diff --git a/workbench/app/Models/Product.php b/workbench/app/Models/Product.php index b8bb4c2..b3aee87 100644 --- a/workbench/app/Models/Product.php +++ b/workbench/app/Models/Product.php @@ -27,4 +27,8 @@ class Product extends Model 'images', ]; + + protected $casts = [ + 'images' => 'array', + ]; } diff --git a/workbench/database/factories/ProductFactory.php b/workbench/database/factories/ProductFactory.php index 058714a..9015532 100644 --- a/workbench/database/factories/ProductFactory.php +++ b/workbench/database/factories/ProductFactory.php @@ -7,8 +7,6 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; -use function json_encode; - class ProductFactory extends Factory { public function definition(): array @@ -24,11 +22,11 @@ public function definition(): array 'quantity' => fake()->numberBetween(0, 10), 'currency' => fake()->currencyCode(), - 'images' => json_encode([ + 'images' => [ fake()->imageUrl(), fake()->imageUrl(), fake()->imageUrl(), - ]), + ], ]; } }