├── UPGRADE.md
├── CHANGELOG.md
├── composer.json
├── LICENSE.md
├── src
├── ManyToManyAttribute.php
└── SyncManyToManyAttribute.php
└── README.md
/UPGRADE.md:
--------------------------------------------------------------------------------
1 | Upgrading Instructions for Sync Many-to-Many via Attribute
2 | ==========================================================
3 |
4 | !!!IMPORTANT!!!
5 |
6 | The following upgrading instructions are cumulative. That is,
7 | if you want to upgrade from version A to version C and there is
8 | version B between A and C, you need to following the instructions
9 | for both A and B.
10 |
11 | Upgrade from 1.0.1
12 | ------------------
13 |
14 | * "illuminate/database" package requirements were raised to 6.0. Make sure to upgrade your code accordingly.
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Sync Many-to-Many via Attribute Change Log
2 | ==========================================
3 |
4 | 1.1.6, March 6, 2025
5 | --------------------
6 |
7 | - Enh: Added support for "illuminate/database" 12.0 (klimov-paul)
8 |
9 |
10 | 1.1.5, March 25, 2024
11 | ---------------------
12 |
13 | - Enh: Added support for "illuminate/database" 11.0 (klimov-paul)
14 |
15 |
16 | 1.1.4, February 27, 2023
17 | ------------------------
18 |
19 | - Enh: Added support for "illuminate/database" 10.0 (klimov-paul)
20 |
21 |
22 | 1.1.3, February 9, 2022
23 | -----------------------
24 |
25 | - Enh: Added support for "illuminate/database" 9.0 (klimov-paul)
26 |
27 |
28 | 1.1.2, September 9, 2020
29 | ------------------------
30 |
31 | - Enh: Added support for "illuminate/database" 8.0 (klimov-paul)
32 |
33 |
34 | 1.1.1, March 4, 2020
35 | --------------------
36 |
37 | - Enh: Added support for "illuminate/database" 7.0 (klimov-paul)
38 |
39 |
40 | 1.1.0, September 6, 2019
41 | ------------------------
42 |
43 | - Enh: Added support for "illuminate/database" 6.0 (klimov-paul)
44 |
45 |
46 | 1.0.0, February 12, 2019
47 | ------------------------
48 |
49 | - Initial release.
50 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "illuminatech/sync-many-attribute",
3 | "description": "Allows control over Eloquent many-to-many relation via array attribute",
4 | "keywords": ["laravel", "eloquent", "many-to-many", "attribute", "sync"],
5 | "license": "BSD-3-Clause",
6 | "support": {
7 | "issues": "https://github.com/illuminatech/sync-many-attribute/issues",
8 | "wiki": "https://github.com/illuminatech/sync-many-attribute/wiki",
9 | "source": "https://github.com/illuminatech/sync-many-attribute"
10 | },
11 | "authors": [
12 | {
13 | "name": "Paul Klimov",
14 | "email": "klimov.paul@gmail.com"
15 | }
16 | ],
17 | "require": {
18 | "illuminate/database": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0"
19 | },
20 | "require-dev": {
21 | "illuminate/events": "*",
22 | "phpunit/phpunit": "^7.5 || ^8.0 || ^9.3 || ^10.5"
23 | },
24 | "autoload": {
25 | "psr-4": {
26 | "Illuminatech\\SyncManyAttribute\\": "src"
27 | }
28 | },
29 | "autoload-dev": {
30 | "psr-4": {
31 | "Illuminatech\\SyncManyAttribute\\Test\\": "tests"
32 | }
33 | },
34 | "extra": {
35 | "branch-alias": {
36 | "dev-master": "1.0.x-dev"
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | This is free software. It is released under the terms of the
2 | following BSD License.
3 |
4 | Copyright © 2019 by Illuminatech (https://github.com/illuminatech)
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions
9 | are met:
10 |
11 | * Redistributions of source code must retain the above copyright
12 | notice, this list of conditions and the following disclaimer.
13 | * Redistributions in binary form must reproduce the above copyright
14 | notice, this list of conditions and the following disclaimer in
15 | the documentation and/or other materials provided with the
16 | distribution.
17 | * Neither the name of Illuminatech nor the names of its
18 | contributors may be used to endorse or promote products derived
19 | from this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 | POSSIBILITY OF SUCH DAMAGE.
33 |
--------------------------------------------------------------------------------
/src/ManyToManyAttribute.php:
--------------------------------------------------------------------------------
1 |
21 | * @since 1.0
22 | */
23 | class ManyToManyAttribute
24 | {
25 | /**
26 | * @var string underlying many-to-many relation name.
27 | */
28 | protected $relationName;
29 |
30 | /**
31 | * @var Closure|array|null pivot attributes to be applied at relation synchronization.
32 | */
33 | protected $pivotAttributes = [];
34 |
35 | /**
36 | * Constructor.
37 | *
38 | * @param array|string|null $definition
39 | */
40 | public function __construct($definition = null)
41 | {
42 | if ($definition === null) {
43 | return;
44 | }
45 |
46 | if (is_array($definition)) {
47 | if (count($definition) !== 1) {
48 | throw new InvalidArgumentException('Attribute definition must be refer to exact one relation.');
49 | }
50 |
51 | $definitionKeys = array_keys($definition);
52 |
53 | $this->relationName(array_shift($definitionKeys));
54 | $this->pivotAttributes(array_shift($definition));
55 |
56 | return;
57 | }
58 |
59 | $this->relationName($definition);
60 | }
61 |
62 | /**
63 | * Sets relation name for this definition.
64 | *
65 | * @param string $relationName relation name.
66 | * @return static self reference.
67 | */
68 | public function relationName(string $relationName): self
69 | {
70 | $this->relationName = $relationName;
71 |
72 | return $this;
73 | }
74 |
75 | /**
76 | * Sets pivot attributes to be applied at relation synchronization.
77 | *
78 | * @param Closure|array|null $pivotAttributes
79 | * @return static self reference.
80 | */
81 | public function pivotAttributes($pivotAttributes): self
82 | {
83 | if ($pivotAttributes !== null && ! is_array($pivotAttributes) && ! $pivotAttributes instanceof Closure) {
84 | throw new InvalidArgumentException('"'.get_class($this).'::$pivotAttributes" must be null, array or Closure.');
85 | }
86 |
87 | $this->pivotAttributes = $pivotAttributes;
88 |
89 | return $this;
90 | }
91 |
92 | /**
93 | * Returns relation instance from given model.
94 | *
95 | * @param \Illuminate\Database\Eloquent\Model $model model instance to get relation from.
96 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|mixed relation instance.
97 | */
98 | public function getRelation($model)
99 | {
100 | return $model->{$this->relationName}();
101 | }
102 |
103 | /**
104 | * Get all of the IDs for the related models.
105 | *
106 | * @param \Illuminate\Database\Eloquent\Model $model
107 | * @return array all of the IDs for the related models.
108 | */
109 | public function getRelatedIds($model): array
110 | {
111 | return $this->getRelation($model)->allRelatedIds()->toArray();
112 | }
113 |
114 | /**
115 | * Synchronizes relation with a list of IDs.
116 | *
117 | * @param \Illuminate\Database\Eloquent\Model $model model to be synchronized.
118 | * @param array $ids list of IDs from related models.
119 | * @return array sync changes report.
120 | */
121 | public function sync($model, array $ids)
122 | {
123 | $relation = $this->getRelation($model);
124 |
125 | if (empty($this->pivotAttributes)) {
126 | return $relation->sync($ids);
127 | }
128 |
129 | if ($this->pivotAttributes instanceof Closure) {
130 | $pivotAttributes = call_user_func($this->pivotAttributes, $model);
131 | } else {
132 | $pivotAttributes = array_map(function ($value) use ($model) {
133 | if (is_callable($value)) {
134 | return call_user_func($value, $model);
135 | }
136 |
137 | return $value;
138 | }, $this->pivotAttributes);
139 | }
140 |
141 | $ids = array_fill_keys($ids, $pivotAttributes);
142 |
143 | return $relation->sync($ids);
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/SyncManyToManyAttribute.php:
--------------------------------------------------------------------------------
1 | 'categories',
36 | * 'tag_ids' => [
37 | * 'tags' => [
38 | * 'created_at' => function ($model) {
39 | * return now();
40 | * }
41 | * ],
42 | * ],
43 | * 'article_ids' => (new ManyToManyAttribute)
44 | * ->relationName('articles')
45 | * ->pivotAttributes(['type' => 'help-content']),
46 | * ];
47 | * }
48 | *
49 | * public function categories(): BelongsToMany
50 | * {
51 | * return $this->belongsToMany(Category::class);
52 | * }
53 | *
54 | * public function tags(): BelongsToMany
55 | * {
56 | * return $this->belongsToMany(Tag::class)->withPivot(['created_at']);
57 | * }
58 | *
59 | * public function articles(): BelongsToMany
60 | * {
61 | * return $this->belongsToMany(Article::class)->withPivot(['type']);
62 | * }
63 | *
64 | * // ...
65 | * }
66 | * ```
67 | *
68 | * Usage example:
69 | *
70 | * ```php
71 | * $item = new Item();
72 | * $item->category_ids = Category::query()->pluck('id')->toArray();
73 | * // ...
74 | * $item->save(); // relation `Item::categories` synchronized automatically
75 | *
76 | * $item = $item->fresh();
77 | * var_dump($item->category_ids); // outputs array of category IDs like `[1, 3, 8, ...]`
78 | * ```
79 | *
80 | * @see ManyToManyAttribute
81 | * @see \Illuminate\Database\Eloquent\Relations\BelongsToMany
82 | *
83 | * @mixin \Illuminate\Database\Eloquent\Model
84 | *
85 | * @author Paul Klimov
2 |
3 |
4 |
5 |
Sync Eloquent Many-to-Many via Array Attribute
6 |
7 |