Skip to content

Commit

Permalink
Added new property value getter and setter (replace) methods
Browse files Browse the repository at this point in the history
  • Loading branch information
fulopattila122 committed Apr 23, 2024
1 parent 96e6506 commit f657bd9
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 15 deletions.
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@
- Added the `AdjusterAliases` class that for decoupling FQCNs from the database
- Added automatic mapping of adjuster FQCN <-> aliases when saving an adjustment into the DB and when calling the `getAdjuster()` method
- Added the `itemsPreAdjustmentTotal()` method to the Foundation's adjustable Cart model
- Added the `replacePropertyValues()` and `replacePropertyValuesByScalar()` methods to the `HasPropertyValues` trait
- BC: Added the following methods to the `PropertyValue` interface:
- `findByPropertyAndValue()`
- `getByScalarPropertiesAndValues()`
- BC: Added the mixed return type to the `getCastedValue` method of the `PropertyValue` interface
- BC: Added the `findBySku()` method to the `Product` and `MasterProductVariant` interfaces
- BC: The `MasterProduct` interface no longer extends the `Product` interface
- BC: The `Checkout` interface now extends the `ArrayAccess` and the `Shippable` interfaces (until here, only the concrete classes have implementation it)
Expand Down
5 changes: 5 additions & 0 deletions src/Properties/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
- Added Laravel 11 Support
- Changed minimum Laravel version to v10.43
- Changed the behavior of assignPropertyValues/assignPropertyValue methods so that it throws an `UnknownPropertyException` when passing an inexistent property slug
- Added the `replacePropertyValues()` and `replacePropertyValuesByScalar()` methods to the `HasPropertyValues` trait
- BC: Added the following methods to the `PropertyValue` interface:
- `findByPropertyAndValue()`
- `getByScalarPropertiesAndValues()`
- BC: Added the mixed return type to the `getCastedValue` method of the `PropertyValue` interface

## 3.x Series

Expand Down
12 changes: 11 additions & 1 deletion src/Properties/Contracts/PropertyValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,20 @@

namespace Vanilo\Properties\Contracts;

use Illuminate\Database\Eloquent\Collection;

interface PropertyValue
{
/**
* Returns the transformed value according to the underlying type
*/
public function getCastedValue();
public function getCastedValue(): mixed;

public static function findByPropertyAndValue(string $propertySlug, mixed $value): ?PropertyValue;

/**
* @example ['color' => 'blue', 'shape' => 'heart']
* @param array<string, mixed> $conditions The keys of the entries = the property slug, the values = the scalar property value
*/
public static function getByScalarPropertiesAndValues(array $conditions): Collection;
}
29 changes: 28 additions & 1 deletion src/Properties/Models/PropertyValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Cviebrock\EloquentSluggable\Sluggable;
use Cviebrock\EloquentSluggable\SluggableScopeHelpers;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Vanilo\Properties\Contracts\PropertyValue as PropertyValueContract;
Expand Down Expand Up @@ -55,6 +56,32 @@ public static function findByPropertyAndValue(string $propertySlug, mixed $value
return static::byProperty($property)->whereSlug($value)->first();
}

/**
* @example ['color' => 'blue', 'shape' => 'heart']
* @param array<string, mixed> $conditions The keys of the entries = the property slug, the values = the scalar property value
*/
public static function getByScalarPropertiesAndValues(array $conditions): Collection
{
if (empty($conditions)) {
return new Collection([]);
}

$query = self::query()
->select('property_values.*')
->join('properties', 'property_values.property_id', '=', 'properties.id');

$count = 0;
foreach ($conditions as $property => $value) {
match ($count) {
0 => $query->where(fn($q) => $q->where('properties.slug', '=', $property)->where('property_values.value', '=', $value)),
default => $query->orWhere(fn($q) => $q->where('properties.slug', '=', $property)->where('property_values.value', '=', $value)),
};
$count++;
}

return $query->get();
}

public function property(): BelongsTo
{
return $this->belongsTo(PropertyProxy::modelClass());
Expand All @@ -80,7 +107,7 @@ public function scopeByProperty($query, $property)
/**
* Returns the transformed value according to the underlying type
*/
public function getCastedValue()
public function getCastedValue(): mixed
{
return $this->property->getType()->transformValue((string) $this->value, $this->settings);
}
Expand Down
5 changes: 5 additions & 0 deletions src/Properties/Models/PropertyValueProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@

namespace Vanilo\Properties\Models;

use Illuminate\Database\Eloquent\Collection;
use Konekt\Concord\Proxies\ModelProxy;

/**
* @method static PropertyValue|null findByPropertyAndValue(string $propertySlug, mixed $value)
* @method static Collection getByScalarPropertiesAndValues(array $conditions)
*/
class PropertyValueProxy extends ModelProxy
{
}
73 changes: 73 additions & 0 deletions src/Properties/Tests/ModelPropertyValuesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,79 @@ public function multiple_values_can_be_assigned_to_a_model_by_scalar_value()
$this->assertEquals('Hua lu guy', $sword->valueOfProperty('shape')->getCastedValue());
}

/** @test */
public function multiple_entries_can_be_replaced_by_scalar_key_value_pairs_at_once()
{
$wheel = Product::create(['name' => 'Wheel']);
$finish = Property::create(['name' => 'Finish', 'slug' => 'finish', 'type' => 'text']);
$diameter = Property::create(['name' => 'Diameter', 'slug' => 'diameter', 'type' => 'integer']);
$brand = Property::create(['name' => 'Brand', 'slug' => 'brand', 'type' => 'text']);
$finish->propertyValues()->createMany([
['title' => 'Glossy', 'value' => 'glossy'],
['title' => 'Matte', 'value' => 'matte'],
['title' => 'Cube', 'value' => 'cube'],
]);
$diameter->propertyValues()->createMany([
['title' => '16"', 'value' => 16],
['title' => '17"', 'value' => 17],
['title' => '18"', 'value' => 18],
]);
$brand->propertyValues()->createMany([
['title' => 'Dezent', 'value' => 'dezent'],
['title' => 'Carmani', 'value' => 'carmani'],
['title' => 'Borbet', 'value' => 'borbet'],
['title' => 'ATS', 'value' => 'ats'],
]);

$wheel->replacePropertyValuesByScalar(['finish' => 'glossy', 'diameter' => 16]);

$this->assertEquals(16, $wheel->valueOfProperty('diameter')->getCastedValue());
$this->assertEquals('glossy', $wheel->valueOfProperty('finish')->getCastedValue());
$this->assertNull($wheel->valueOfProperty('brand'));

$wheel->replacePropertyValuesByScalar(['finish' => 'matte', 'diameter' => 17]);
$wheel->refresh();

$this->assertEquals(17, $wheel->valueOfProperty('diameter')->getCastedValue());
$this->assertEquals('matte', $wheel->valueOfProperty('finish')->getCastedValue());
$this->assertNull($wheel->valueOfProperty('brand'));

$wheel->replacePropertyValuesByScalar(['diameter' => 17, 'brand' => 'ats']);
$wheel->refresh();

$this->assertEquals(17, $wheel->valueOfProperty('diameter')->getCastedValue());
$this->assertNull($wheel->valueOfProperty('finish'));
$this->assertEquals('ats', $wheel->valueOfProperty('brand')->getCastedValue());

$wheel->replacePropertyValuesByScalar(['diameter' => 18, 'brand' => 'ats', 'finish' => 'matte']);
$wheel->refresh();

$this->assertEquals(18, $wheel->valueOfProperty('diameter')->getCastedValue());
$this->assertEquals('matte', $wheel->valueOfProperty('finish')->getCastedValue());
$this->assertEquals('ats', $wheel->valueOfProperty('brand')->getCastedValue());

$wheel->replacePropertyValuesByScalar([]);
$wheel->refresh();

$this->assertNull($wheel->valueOfProperty('finish'));
$this->assertNull($wheel->valueOfProperty('brand'));
$this->assertNull($wheel->valueOfProperty('diameter'));
}

/** @test */
public function replace_by_scalar_will_create_new_values_if_necessary()
{
$tyre = Product::create(['name' => 'Tyre']);
$origin = Property::create(['name' => 'Origin', 'slug' => 'origin', 'type' => 'text']);
$origin->propertyValues()->createMany([
['title' => 'China', 'value' => 'china'],
['title' => 'India', 'value' => 'india'],
]);

$tyre->replacePropertyValuesByScalar(['origin' => 'canada']);
$this->assertEquals('canada', $tyre->fresh()->valueOfProperty('origin')?->getCastedValue());
}

/** @test */
public function attempting_to_assign_values_with_inexistent_properties_throws_an_exception()
{
Expand Down
44 changes: 44 additions & 0 deletions src/Properties/Tests/PropertyValueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,48 @@ public function the_property_and_value_finder_returns_null_when_attempting_to_lo
{
$this->assertNull(PropertyValue::findByPropertyAndValue('hey-i-am-so-stupid', 'gold'));
}

/** @test */
public function multiple_entries_can_be_returned_by_scalar_key_value_pairs()
{
$shape = Property::create(['name' => 'Shape', 'type' => 'text']);
$material = Property::create(['name' => 'Material', 'type' => 'text']);
$shape->propertyValues()->createMany([
['title' => 'Heart', 'value' => 'heart'],
['title' => 'Sphere', 'value' => 'sphere'],
['title' => 'Cube', 'value' => 'cube'],
]);
$material->propertyValues()->createMany([
['title' => 'Wood', 'value' => 'wood'],
['title' => 'Glass', 'value' => 'glass'],
['title' => 'Metal', 'value' => 'metal'],
['title' => 'Plastic', 'value' => 'plastic'],
]);

$values = PropertyValue::getByScalarPropertiesAndValues([
'shape' => 'heart',
'material' => 'wood',
]);
$this->assertCount(2, $values);
$this->assertInstanceOf(PropertyValue::class, $values[0]);
$this->assertInstanceOf(PropertyValue::class, $values[1]);
$this->assertContains('shape', $values->map->property->map->slug);
$this->assertContains('material', $values->map->property->map->slug);
$this->assertContains('heart', $values->map->value);
$this->assertContains('wood', $values->map->value);
}

/** @test */
public function it_returns_an_empty_resultset_if_not_values_get_passed_to_get_by_scalar_key_value_pairs()
{
$season = Property::create(['name' => 'Season', 'type' => 'text']);
$season->propertyValues()->createMany([
['title' => 'Winter'],
['title' => 'Summer'],
['title' => 'All Seasons'],
]);

$values = PropertyValue::getByScalarPropertiesAndValues([]);
$this->assertCount(0, $values);
}
}
53 changes: 40 additions & 13 deletions src/Properties/Traits/HasPropertyValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,7 @@ public function assignPropertyValue(string|Property $property, mixed $value): vo
return;
}

$propertyValue = PropertyValueProxy::findByPropertyAndValue($property, $value);
if (null === $propertyValue) {
if (null === $propertyId = $property instanceof Property ? $property->id : PropertyProxy::findBySlug($property)?->id) {
throw UnknownPropertyException::createFromSlug($property);
}
$propertyValue = PropertyValueProxy::create([
'property_id' => $propertyId,
'value' => $value,
'title' => $value,
]);
}

$this->addPropertyValue($propertyValue);
$this->addPropertyValue($this->findOrCreateByPropertyValue($property, $value));
}

public function assignPropertyValues(iterable $propertyValues): void
Expand All @@ -53,6 +41,28 @@ public function assignPropertyValues(iterable $propertyValues): void
}
}

/**
* @param array<string, mixed> The key of the array is the property slug, the value is the scalar property value
*/
public function replacePropertyValuesByScalar(array $propertyValues): void
{
$valuesToSet = PropertyValueProxy::getByScalarPropertiesAndValues($propertyValues);
if (count($propertyValues) !== count($valuesToSet)) {
foreach ($propertyValues as $property => $value) {
if (!$valuesToSet->contains(fn (PropertyValue $pv) => $pv->value == $value && $pv->property->slug === $property)) {
$valuesToSet[] = $this->findOrCreateByPropertyValue($property, $value);
}
}
}

$this->replacePropertyValues(...$valuesToSet);
}

public function replacePropertyValues(PropertyValue ...$propertyValues): void
{
$this->propertyValues()->sync(collect($propertyValues)->pluck('id'));
}

public function valueOfProperty(string|Property $property): ?PropertyValue
{
$propertySlug = is_string($property) ? $property : $property->slug;
Expand Down Expand Up @@ -101,4 +111,21 @@ public function removePropertyValue(PropertyValue $propertyValue)
{
return $this->propertyValues()->detach($propertyValue);
}

protected function findOrCreateByPropertyValue(string|Property $property, mixed $value): PropertyValue
{
$result = PropertyValueProxy::findByPropertyAndValue($property, $value);
if (null === $result) {
if (null === $propertyId = $property instanceof Property ? $property->id : PropertyProxy::findBySlug($property)?->id) {
throw UnknownPropertyException::createFromSlug($property);
}
$result = PropertyValueProxy::create([
'property_id' => $propertyId,
'value' => $value,
'title' => $value,
]);
}

return $result;
}
}

0 comments on commit f657bd9

Please sign in to comment.