Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/symfony.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ jobs:
fail-fast: false
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
- "8.5"
symfony:
- '6.4.*'
- '7.3.*'
Expand All @@ -42,9 +42,6 @@ jobs:
- "ubuntu-latest"
exclude:
# Symfony 7.3 does not support PHP 8.1
- php-version: "8.1"
symfony: '7.3.*'
operating-system: "ubuntu-latest"
- php-version: "8.1"
symfony: '8.0.*'
operating-system: "ubuntu-latest"
Expand Down
85 changes: 56 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
![Tests](https://github.com//tito10047/persistent-preference-bundle/actions/workflows/symfony.yml/badge.svg)
![Tests](https://github.com//tito10047/persistent-state-bundle/actions/workflows/symfony.yml/badge.svg)

# 🛒 Persistent Preference Bundle
# 🛒 Persistent State Bundle

```yaml
persistent_preference:
managers:
default:
storage: 'persistent_preference.storage.session'
my_pref_manager:
storage: 'app.persistent_preference.storage.doctrine'
storage:
doctrine:
id: 'app.persistent_preference.storage.doctrine'
enabled: true
preference_class: App\Entity\UserPreference
entity_manager: 'default'

context_providers:
users:
class: App\Entity\User
prefix: 'user'
companies:
class: App\Entity\Company
prefix: 'company'
identifier_method: 'getUuid'
services:
app.users_resolver:
class: Tito10047\PersistentStateBundle\Resolver\ObjectContextResolver
arguments:
$targetClass: App\Entity\User
$identifierMethod: 'getName'
app.companies_resolver:
class: Tito10047\PersistentStateBundle\Resolver\ObjectContextResolver
arguments:
$targetClass: App\Entity\Company
app.storage.doctrine:
class: Tito10047\PersistentStateBundle\Storage\DoctrinePreferenceStorage
arguments:
- '@doctrine.orm.entity_manager'
- Tito10047\PersistentStateBundle\Tests\App\AssetMapper\Src\Entity\UserPreference

persistent:
preference:
managers:
default:
storage: '@persistent.preference.storage.session'
my_pref_manager:
storage: '@app.storage.doctrine'
selection:
managers:
default:
storage: 'persistent.selection.storage.session'
simple:
storage: 'persistent.selection.storage.doctrine'
```

```php
Expand All @@ -32,20 +40,34 @@ namespace ;
use \Symfony\Component\DependencyInjection\Attribute\Autowire;
use \App\Entity\User;
use \App\Entity\Company;
use \App\Entity\Product;

class Foo{

public function __construct(
private readonly PreferenceManagerInterface $sessionPrefManager
#[Autowire('persistent_preference.manager.my_pref_manager')]
private readonly PreferenceManagerInterface $doctrinePrefManager,
private readonly PreconfiguredPreferenceInterface $sessionPrefManager
#[Autowire('persistent.preference.my_pref_manager')]
private readonly PreconfiguredPreferenceInterface $doctrinePrefManager,
private readonly PreconfiguredSelectionInterface $sessionPrefManager,
#[Autowire('persistent.selection.my_sel_manager')]
private readonly PreconfiguredSelectionInterface $doctrinePrefManager,
private readonly EntityManagerInterface $em
) {}

public function bar(User $user, Company $company){
public function bar(User $user, Company $company, Product $product){

$userPref = $this->sessionPrefManager->getPreference($user);
$companyPref = $this->doctrinePrefManager->getPreference($company);

$cartSelection = $this->sessionPrefManager->getSelection($user,"cart");
$companySelection = $this->doctrinePrefManager->getSelection($company, "products");

$cartSelection->select($product, [
'quantity' => $request->get('qty', 1),
'added_at' => new \DateTime()
]);

$companySelection->select($product);

$userPref->set('foo', 'bar');
$userPref->set('baz', [1,2,3]);
Expand All @@ -60,8 +82,13 @@ class Foo{

$foo2 = $companyPref->get('foo2');
$baz2 = $companyPref->get('baz2');

$selectedItems = $cartSelection->getSelectedObjects();
$selectedProducts = $companySelection->getSelectedObjects();


$cart->destroy();
}

}
```

Expand Down Expand Up @@ -98,6 +125,6 @@ Storage: doctrine

Notes:
- The `context` argument accepts either a pre-resolved key like `user_15` or any object supported by your configured context resolvers.
- The `--manager` option selects which preference manager to use. It maps to the service id `persistent_preference.manager.{name}` and defaults to `default` when omitted.
- The `--manager` option selects which preference manager to use. It maps to the service id `persistent.manager.{name}` and defaults to `default` when omitted.
- The Storage line reflects the underlying storage: `session`, `doctrine`, or the short class name for custom storages.
- Non-scalar values are JSON-encoded for readability; `null` and booleans are rendered as `null`, `true`/`false`.
131 changes: 131 additions & 0 deletions assets/controllers/persistent-selection_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {Controller} from '@hotwired/stimulus';

export default class extends Controller {

static targets = ["checkbox", "selectAll"]

static values = {
urlToggle: String,
urlSelectAll: String,
urlSelectRange: String,
key: String,
manager: String,
selectAllClass: String,
unselectAllClass: String
}

toggle({params: {id}, target}) {
let checked = target.checked;
fetch(this._getUrl(this.urlToggleValue, id, checked)).then((response) => {
if (!response.ok) {
target.checked = !checked;
console.error("can't select item #" + id);
}
}).catch((error) => {
target.checked = !checked;
console.error(error);
})
}

selectAll({target, params: {checked}}) {
const isCheckbox = target.matches('[type="checkbox"]');
if (isCheckbox) {
checked = target.checked;
}else{
if (this.hasSelectAllClassValue) {
checked = !target.classList.contains(this.selectAllClassValue)
}
}
fetch(this._getUrl(this.urlSelectAllValue, null, checked)).then((response) => {
if (!response.ok) {
if (isCheckbox) {
target.checked = !checked;
}
console.error("can't select all items")
} else {
if (this.hasSelectAllClassValue) {
if (checked) {
target.classList.add(this.selectAllClassValue);
} else {
target.classList.remove(this.selectAllClassValue);
}
}
if (this.hasUnselectAllClassValue) {
if (checked) {
target.classList.remove(this.unselectAllClassValue);
} else {
target.classList.add(this.unselectAllClassValue);
}
}
this.checkboxTargets.forEach((checkbox) => {
checkbox.checked = checked;
})
}
}).catch((error) => {
if (isCheckbox) {
target.value = !checked;
}
console.error(error);
})
}

selectCurrentPage({target, params: {checked}}) {
const isCheckbox = target.matches('[type="checkbox"]');
if (isCheckbox) {
checked = target.checked;
}else{
if (this.hasSelectAllClassValue) {
checked = !target.classList.contains(this.selectAllClassValue)
}
}
let ids = [];
this.checkboxTargets.forEach((checkbox) => {
ids.push(checkbox.value);
})
fetch(this._getUrl(this.urlSelectRangeValue, null, checked), {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(ids)
}).then((response) => {
if (!response.ok) {
console.error("can't select all items")
} else {
if (this.hasSelectAllClassValue) {
if (checked) {
target.classList.add(this.selectAllClassValue);
} else {
target.classList.remove(this.selectAllClassValue);
}
}
if (this.hasUnselectAllClassValue) {
if (checked) {
target.classList.remove(this.unselectAllClassValue);
} else {
target.classList.add(this.unselectAllClassValue);
}
}

this.checkboxTargets.forEach((checkbox) => {
checkbox.checked = checked;
})
}
}).catch((error) => {
console.error(error);
})
}

_getUrl(url, id, selected) {
let params = {
key: this.keyValue,
manager: this.managerValue,
selected: selected ? "1" : "0",
};
if (id) {
params["id"] = id;
}
return url + '?' + new URLSearchParams(params).toString()
}
}
9 changes: 5 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "tito10047/persistent-preference-bundle",
"name": "tito10047/persistent-state-bundle",
"type": "symfony-bundle",
"license": "MIT",
"description": "Provides a unified API for handling persistent user preferences, UI states, and settings across different storage adapters (Doctrine, Session, Redis).",
Expand All @@ -8,7 +8,8 @@
"preference",
"batch",
"pagination",
"ui"
"ui",
"symfony-ux"
],
"minimum-stability": "stable",
"authors": [
Expand All @@ -31,12 +32,12 @@
},
"autoload": {
"psr-4": {
"Tito10047\\PersistentPreferenceBundle\\": "src/"
"Tito10047\\PersistentStateBundle\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tito10047\\PersistentPreferenceBundle\\Tests\\": "tests/"
"Tito10047\\PersistentStateBundle\\Tests\\": "tests/"
}
},
"extra": {
Expand Down
Loading