diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 47caa7eb..b8b66777 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,6 +7,7 @@ docs/ @JoshuaEstes /src/SonsOfPHP/Bard @JoshuaEstes # Each component/contract needs a Team +/src/SonsOfPHP/**/Attribute @JoshuaEstes /src/SonsOfPHP/**/Cache @JoshuaEstes /src/SonsOfPHP/**/Clock @JoshuaEstes /src/SonsOfPHP/**/Container @JoshuaEstes diff --git a/.github/labeler.yml b/.github/labeler.yml index e0ece348..e2de3d60 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -2,6 +2,11 @@ Bard: - docs/bard/* - src/SonsOfPHP/Bard/* +Attribute: + - docs/components/attribute/* + - docs/contracts/attribute/* + - src/SonsOfPHP/**/Attribute/* + Cache: - docs/components/cache/* - src/SonsOfPHP/**/Cache/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c140b4c..1f358888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ To get the diff between two versions, go to https://github.com/SonsOfPHP/sonsofp ## [Unreleased] +* [PR #210](https://github.com/SonsOfPHP/sonsofphp/pull/210) Added new Attribute component and contact * [PR #51](https://github.com/SonsOfPHP/sonsofphp/pull/51) Added new Filesystem component * [PR #59](https://github.com/SonsOfPHP/sonsofphp/pull/59) Added new HttpMessage component * [PR #59](https://github.com/SonsOfPHP/sonsofphp/pull/59) Added new HttpFactory component diff --git a/bard.json b/bard.json index d70c02bc..32ab67e3 100644 --- a/bard.json +++ b/bard.json @@ -5,6 +5,14 @@ "path": "src/SonsOfPHP/Bard", "repository": "git@github.com:SonsOfPHP/bard.git" }, + { + "path": "src/SonsOfPHP/Contract/Attribute", + "repository": "git@github.com:SonsOfPHP/attribute-contract.git" + }, + { + "path": "src/SonsOfPHP/Component/Attribute", + "repository": "git@github.com:SonsOfPHP/attribute.git" + }, { "path": "src/SonsOfPHP/Contract/Mailer", "repository": "git@github.com:SonsOfPHP/mailer-contract.git" diff --git a/composer.json b/composer.json index b3791522..29910ff8 100644 --- a/composer.json +++ b/composer.json @@ -54,7 +54,8 @@ "psr/http-server-handler-implementation": "^1.0", "psr/http-server-middleware-implementation": "^1.0", "sonsofphp/http-handler-implementation": "0.3.x-dev", - "sonsofphp/mailer-implementation": "0.3.x-dev" + "sonsofphp/mailer-implementation": "0.3.x-dev", + "sonsofphp/attribute-implementation": "0.3.x-dev" }, "require": { "php": ">=8.1", @@ -127,11 +128,15 @@ "sonsofphp/http-handler": "self.version", "sonsofphp/http-handler-contract": "self.version", "sonsofphp/mailer-contract": "self.version", - "sonsofphp/mailer": "self.version" + "sonsofphp/mailer": "self.version", + "sonsofphp/attribute-contract": "self.version", + "sonsofphp/attribute": "self.version" }, "autoload": { "psr-4": { "SonsOfPHP\\Bard\\": "src/SonsOfPHP/Bard/src", + "SonsOfPHP\\Contract\\Attribute\\": "src/SonsOfPHP/Contract/Attribute", + "SonsOfPHP\\Component\\Attribute\\": "src/SonsOfPHP/Component/Attribute", "SonsOfPHP\\Contract\\Mailer\\": "src/SonsOfPHP/Contract/Mailer", "SonsOfPHP\\Component\\Mailer\\": "src/SonsOfPHP/Component/Mailer", "SonsOfPHP\\Component\\Cache\\": "src/SonsOfPHP/Component/Cache", @@ -174,6 +179,7 @@ }, "exclude-from-classmap": [ "src/SonsOfPHP/Bard/Tests", + "src/SonsOfPHP/Component/Attribute/Tests", "src/SonsOfPHP/Component/Mailer/Tests", "src/SonsOfPHP/Component/Cache/Tests", "src/SonsOfPHP/Component/Clock/Tests", diff --git a/docs/contracts/attribute/index.md b/docs/contracts/attribute/index.md new file mode 100644 index 00000000..081914ee --- /dev/null +++ b/docs/contracts/attribute/index.md @@ -0,0 +1,15 @@ +--- +title: Attribute Contracts - Overview +description: PHP Attribute Contracts +--- + +# Attribute Contracts + +Attributes can be added to any object. These are used for things like a product +or customer where a user may define custom attributes on that object. + +## Installation + +```shell +composer require sonsofphp/attribute-contract +``` diff --git a/src/SonsOfPHP/Component/Attribute/.gitattributes b/src/SonsOfPHP/Component/Attribute/.gitattributes new file mode 100644 index 00000000..84c7add0 --- /dev/null +++ b/src/SonsOfPHP/Component/Attribute/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/SonsOfPHP/Component/Attribute/.gitignore b/src/SonsOfPHP/Component/Attribute/.gitignore new file mode 100644 index 00000000..5414c2c6 --- /dev/null +++ b/src/SonsOfPHP/Component/Attribute/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/SonsOfPHP/Component/Attribute/Attribute.php b/src/SonsOfPHP/Component/Attribute/Attribute.php new file mode 100644 index 00000000..3ae4ee86 --- /dev/null +++ b/src/SonsOfPHP/Component/Attribute/Attribute.php @@ -0,0 +1,73 @@ + + */ +class Attribute implements AttributeInterface, \Stringable +{ + protected ?string $code = null; + protected ?string $name = null; + protected int $position = 0; + protected ?AttributeTypeInterface $type = null; + + public function __toString(): string + { + return (string) $this->getName(); + } + + public function getCode(): ?string + { + return $this->code; + } + + public function setCode(?string $code): static + { + // Normalize value + $this->code = strtolower((string) $code); + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(?string $name): static + { + $this->name = $name; + + return $this; + } + + public function getPosition(): int + { + return $this->position; + } + + public function setPosition(int $position): static + { + $this->position = $position; + + return $this; + } + + public function getType(): ?AttributeTypeInterface + { + return $this->type; + } + + public function setType(AttributeTypeInterface $type): static + { + $this->type = $type; + + return $this; + } +} diff --git a/src/SonsOfPHP/Component/Attribute/AttributeType.php b/src/SonsOfPHP/Component/Attribute/AttributeType.php new file mode 100644 index 00000000..9151c57a --- /dev/null +++ b/src/SonsOfPHP/Component/Attribute/AttributeType.php @@ -0,0 +1,40 @@ + + */ +enum AttributeType: string implements AttributeTypeInterface +{ + case TYPE_TEXT = 'text'; + // -- OR -- + case TextType = 'text'; + // -- OR -- + case Text = 'text'; + + //case TextareaType = 'textarea'; + //case CheckboxType = 'checkbook'; + //case IntegerType = 'integer'; + //case FloatType = 'float'; + //case DatetimeType = 'datetime'; + //case DateType = 'date'; + //case SelectType = 'select'; + + public function getDisplayName(): string + { + return match($this) { + self::TYPE_TEXT => 'Text', + default => 'Unknown', + }; + } + + public function getType() + { + return $this; + } +} diff --git a/src/SonsOfPHP/Component/Attribute/AttributeValue.php b/src/SonsOfPHP/Component/Attribute/AttributeValue.php new file mode 100644 index 00000000..d25045d3 --- /dev/null +++ b/src/SonsOfPHP/Component/Attribute/AttributeValue.php @@ -0,0 +1,71 @@ + + */ +class AttributeValue implements AttributeValueInterface +{ + protected ?AttributeSubjectInterface $subject = null; + protected ?AttributeInterface $attribute = null; + protected $value = null; + + public function getSubject(): ?AttributeSubjectInterface + { + return $this->subject; + } + + public function setSubject(?AttributeSubjectInterface $subject): static + { + $this->subject = $subject; + + return $this; + } + + public function getAttribute(): ?AttributeInterface + { + return $this->attribute; + } + + public function setAttribute(?AttributeInterface $attribute): static + { + $this->attribute = $attribute; + + return $this; + } + + public function getType(): ?AttributeTypeInterface + { + return $this->getAttribute()?->getType(); + } + + public function getValue() + { + return $this->value; + } + + public function setValue($value): static + { + $this->value = $value; + + return $this; + } + + public function getCode(): ?string + { + return $this->getAttribute()?->getCode(); + } + + public function getName(): ?string + { + return $this->getAttribute()?->getName(); + } +} diff --git a/src/SonsOfPHP/Component/Attribute/LICENSE b/src/SonsOfPHP/Component/Attribute/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Component/Attribute/LICENSE @@ -0,0 +1,19 @@ +Copyright 2022 to Present Joshua Estes + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/SonsOfPHP/Component/Attribute/README.md b/src/SonsOfPHP/Component/Attribute/README.md new file mode 100644 index 00000000..113e0b20 --- /dev/null +++ b/src/SonsOfPHP/Component/Attribute/README.md @@ -0,0 +1,16 @@ +Sons of PHP - Attribute +======================= + +## Learn More + +* [Documentation][docs] +* [Contributing][contributing] +* [Report Issues][issues] and [Submit Pull Requests][pull-requests] in the [Mother Repository][mother-repo] +* Get Help & Support using [Discussions][discussions] + +[discussions]: https://github.com/orgs/SonsOfPHP/discussions +[mother-repo]: https://github.com/SonsOfPHP/sonsofphp +[contributing]: https://docs.sonsofphp.com/contributing/ +[docs]: https://docs.sonsofphp.com/components/attribute/ +[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3AAttribute +[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3AAttribute diff --git a/src/SonsOfPHP/Component/Attribute/Tests/AttributeTest.php b/src/SonsOfPHP/Component/Attribute/Tests/AttributeTest.php new file mode 100644 index 00000000..54a00f2e --- /dev/null +++ b/src/SonsOfPHP/Component/Attribute/Tests/AttributeTest.php @@ -0,0 +1,63 @@ +model = new Attribute(); + } + + /** + * @coversNothing + */ + public function testItHasTheCorrectInterfaces(): void + { + $this->assertInstanceOf(AttributeInterface::class, $this->model); + } + + /** + * @covers ::getCode + * @covers ::setCode + */ + public function testCode(): void + { + $this->assertNull($this->model->getCode()); + $this->model->setCode('sku'); + $this->assertSame('sku', $this->model->getCode()); + } + + /** + * @covers ::getName + * @covers ::setName + */ + public function testName(): void + { + $this->assertNull($this->model->getName()); + $this->model->setName('Test Attribute'); + $this->assertSame('Test Attribute', $this->model->getName()); + } + + /** + * @covers ::getPosition + * @covers ::setPosition + */ + public function testPosition(): void + { + $this->assertSame(0, $this->model->getPosition()); + $this->model->setPosition(100); + $this->assertSame(100, $this->model->getPosition()); + } +} diff --git a/src/SonsOfPHP/Component/Attribute/composer.json b/src/SonsOfPHP/Component/Attribute/composer.json new file mode 100644 index 00000000..a8e4c668 --- /dev/null +++ b/src/SonsOfPHP/Component/Attribute/composer.json @@ -0,0 +1,54 @@ +{ + "name": "sonsofphp/attribute", + "type": "library", + "description": "Attribute library for PHP", + "keywords": [ + "attribute" + ], + "homepage": "https://github.com/SonsOfPHP/attribute", + "license": "MIT", + "authors": [ + { + "name": "Joshua Estes", + "email": "joshua@sonsofphp.com" + } + ], + "support": { + "issues": "https://github.com/SonsOfPHP/sonsofphp/issues", + "forum": "https://github.com/orgs/SonsOfPHP/discussions", + "docs": "https://docs.sonsofphp.com" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Component\\Attribute\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.2", + "sonsofphp/attribute-contract": "0.3.x-dev" + }, + "provide": { + "sonsofphp/attribute-implementation": "0.3.x-dev" + }, + "extra": { + "sort-packages": true, + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/JoshuaEstes" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Contract/Attribute/.gitattributes b/src/SonsOfPHP/Contract/Attribute/.gitattributes new file mode 100644 index 00000000..3a01b372 --- /dev/null +++ b/src/SonsOfPHP/Contract/Attribute/.gitattributes @@ -0,0 +1,2 @@ +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/SonsOfPHP/Contract/Attribute/.gitignore b/src/SonsOfPHP/Contract/Attribute/.gitignore new file mode 100644 index 00000000..d8a7996a --- /dev/null +++ b/src/SonsOfPHP/Contract/Attribute/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor/ diff --git a/src/SonsOfPHP/Contract/Attribute/AttributeInterface.php b/src/SonsOfPHP/Contract/Attribute/AttributeInterface.php new file mode 100644 index 00000000..5ebc5d24 --- /dev/null +++ b/src/SonsOfPHP/Contract/Attribute/AttributeInterface.php @@ -0,0 +1,70 @@ + + */ +interface AttributeInterface +{ + public function getCode(): ?string; + + /** + * The code is unique, no two attributes should have the same code. The code + * should ONLY consist of letters, numbers, underscores, and periods. + * + * Example Code: sku + * + * @throws InvalidArgumentException + * - When the code is invalid or contains invalid characters + */ + public function setCode(?string $code): static; + + public function getName(): ?string; + + /** + * The name of the attribute. This could be displayed on the frontend to + * users or used anywhere. This is the friendly version of the code. This + * does not have to be unique. + * + * @throws InvalidArgumentException + * - When the name is invalid for any reason + */ + public function setName(?string $name): static; + + public function getType(): ?AttributeTypeInterface; + + /** + * The attribute type such as Text, Textarea, Select, etc. + */ + public function setType(AttributeTypeInterface $type): static; + + public function getPosition(): int; + + /** + * The position helps with ordering. When returning the list of attributes, the position + * should determine what order they are listed in. + */ + public function setPosition(int $position): static; + + /** + * Is this a system attribute? System attributes should not be editable by + * a user. + */ + //public function isSystem(): bool; + + /** + * If the attribute is unique, the values should be checked. An example of + * a unique value would be an email address. This will help avoid duplicate + * resources. + */ + //public function isUnique(): bool; + + /** + * Similar to a system attribute, this means that the name and code should + * not be modified by users + */ + //public function isLocked(): bool; +} diff --git a/src/SonsOfPHP/Contract/Attribute/AttributeSubjectInterface.php b/src/SonsOfPHP/Contract/Attribute/AttributeSubjectInterface.php new file mode 100644 index 00000000..7b3a45f4 --- /dev/null +++ b/src/SonsOfPHP/Contract/Attribute/AttributeSubjectInterface.php @@ -0,0 +1,27 @@ + + */ +interface AttributeSubjectInterface +{ + /** + * @return Collection + */ + public function getAttributes(): Collection; + + public function addAttribute(AttributeValueInterface $attribute): static; + + public function removeAttribute(AttributeValueInterface $attribute): static; + + public function hasAttribute(AttributeValueInterface $attribute): bool; +} diff --git a/src/SonsOfPHP/Contract/Attribute/AttributeTypeInterface.php b/src/SonsOfPHP/Contract/Attribute/AttributeTypeInterface.php new file mode 100644 index 00000000..52b2dcb3 --- /dev/null +++ b/src/SonsOfPHP/Contract/Attribute/AttributeTypeInterface.php @@ -0,0 +1,15 @@ + + */ +interface AttributeTypeInterface +{ + public function getDisplayName(): string; + + public function getType(): string; +} diff --git a/src/SonsOfPHP/Contract/Attribute/AttributeValueInterface.php b/src/SonsOfPHP/Contract/Attribute/AttributeValueInterface.php new file mode 100644 index 00000000..69d4357b --- /dev/null +++ b/src/SonsOfPHP/Contract/Attribute/AttributeValueInterface.php @@ -0,0 +1,38 @@ + + */ +interface AttributeValueInterface +{ + public function getSubject(): ?AttributeSubjectInterface; + + public function setSubject(?AttributeSubjectInterface $subject): static; + + public function getAttribute(): ?AttributeInterface; + + public function setAttribute(?AttributeInterface $attribute): static; + + public function getValue(); + + public function setValue($value): static; + + /** + * Returns the code of the Attribute + */ + public function getCode(): ?string; + + /** + * Returns the name of the Attribute + */ + public function getName(): ?string; + + /** + * Returns the type of Attribute + */ + public function getType(): ?AttributeTypeInterface; +} diff --git a/src/SonsOfPHP/Contract/Attribute/LICENSE b/src/SonsOfPHP/Contract/Attribute/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Contract/Attribute/LICENSE @@ -0,0 +1,19 @@ +Copyright 2022 to Present Joshua Estes + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/SonsOfPHP/Contract/Attribute/README.md b/src/SonsOfPHP/Contract/Attribute/README.md new file mode 100644 index 00000000..c9962624 --- /dev/null +++ b/src/SonsOfPHP/Contract/Attribute/README.md @@ -0,0 +1,16 @@ +Sons of PHP - Attribute Contract +================================ + +## Learn More + +* [Documentation][docs] +* [Contributing][contributing] +* [Report Issues][issues] and [Submit Pull Requests][pull-requests] in the [Mother Repository][mother-repo] +* Get Help & Support using [Discussions][discussions] + +[discussions]: https://github.com/orgs/SonsOfPHP/discussions +[mother-repo]: https://github.com/SonsOfPHP/sonsofphp +[contributing]: https://docs.sonsofphp.com/contributing/ +[docs]: https://docs.sonsofphp.com/contracts/attribute/ +[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3AAttribute +[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3AAttribute diff --git a/src/SonsOfPHP/Contract/Attribute/composer.json b/src/SonsOfPHP/Contract/Attribute/composer.json new file mode 100644 index 00000000..df99b0ad --- /dev/null +++ b/src/SonsOfPHP/Contract/Attribute/composer.json @@ -0,0 +1,54 @@ +{ + "name": "sonsofphp/attribute-contract", + "type": "library", + "description": "Attribute Contract for objects that have attributes", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards", + "attribute" + ], + "homepage": "https://github.com/SonsOfPHP/attribute-contract", + "license": "MIT", + "authors": [ + { + "name": "Joshua Estes", + "email": "joshua@sonsofphp.com" + } + ], + "support": { + "issues": "https://github.com/SonsOfPHP/sonsofphp/issues", + "forum": "https://github.com/orgs/SonsOfPHP/discussions", + "docs": "https://docs.sonsofphp.com" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Contract\\Attribute\\": "" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.2", + "doctrine/collections": "^2.2" + }, + "extra": { + "sort-packages": true, + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/JoshuaEstes" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" + } + ] +} \ No newline at end of file