Inventory Service
The InventoryService is responsible for managing the available publication listings, implementing filtering logic, and providing search capabilities across the content catalog.
Overview
InventoryService serves as the data access layer for publications in the storefront, ensuring that all product listings respect user permissions, tenant settings, and content availability rules. It focuses on building optimized database queries to retrieve publications based on complex filtering criteria.
Main Responsibilities
- Implements low-level product filtering logic (builds database queries)
- Manages database query optimization and eager loading strategies
- Provides search functionality across multiple content dimensions
- Validates and applies filter parameters
- Enforces user content restrictions
- Handles tenant-specific inventory scope (owned, shared, marketplace)
Filtering System
Filter Types
The filtering system is built around a standardized set of filter types implemented as a PHP enum (InventoryFilter). This ensures consistent filter naming and validation throughout the application.
Supported Filter Types
| Filter | Description |
|---|---|
| file_type | Filter by file type (pdf, epub, audio) |
| taxonomy | Filter by custom taxonomy |
| contributor | Filter by contributor (author, translator, etc.) |
| bisac | Filter by BISAC category |
| year | Filter by publication year |
| month | Filter by month/year of publication |
| free | Show only free publications |
| latest | Show most recent publications |
| audience | Filter by target audience |
| recommended | Show recommended publications |
| my_issues | Show publications acquired by the user |
| continue_reading | Show publications the user is currently reading |
Filter Application
The core filtering method is applyFilters:
public function applyFilters(array $filters): self
This method processes an array of filter specifications and applies them to the query builder. For each filter:
- It extracts the filter type from the provided specification
- Validates that required values are present and correctly formatted
- Dispatches to the appropriate specialized filter method based on the filter type
- Returns the service instance for method chaining
The validation logic ensures that filters have the correct structure before they're applied, throwing exceptions for invalid configurations. This makes the filtering system both robust and extensible.
Each filter is represented as an associative array with at least a type key:
[
'type' => InventoryFilter::TAXONOMY->value,
'value' => [
'taxonomy' => 'subject',
'term' => 'history'
]
]
Filter Combination Examples
Filters can be combined to create complex queries. Here are some examples:
// Filtering by file type and year
$filters = [
['type' => InventoryFilter::FILE_TYPE->value, 'value' => 'pdf'],
['type' => InventoryFilter::YEAR->value, 'value' => 2024]
];
// Filtering by taxonomy with multiple terms (OR condition)
$filters = [
['type' => InventoryFilter::TAXONOMY->value, 'value' => [
'taxonomy' => 'subject',
'term' => ['history', 'science']
]]
];
// Combining multiple taxonomy filters (AND condition between taxonomies)
$filters = [
['type' => InventoryFilter::TAXONOMY->value, 'value' => [
'taxonomy' => 'subject',
'term' => 'history'
]],
['type' => InventoryFilter::TAXONOMY->value, 'value' => [
'taxonomy' => 'audience',
'term' => 'young-adult'
]]
];
Filter Compatibility
Not all filters can be combined with others. The table below shows which filters are mutually exclusive:
| Filter | Can be combined with other filters |
|---|---|
| FREE | Yes |
| LATEST | Yes |
| YEAR | Yes |
| MONTH | Yes |
| TAXONOMY | Yes |
| BISAC | Yes |
| CONTRIBUTOR | Yes |
| FILE_TYPE | Yes |
| AUDIENCE | Yes |
| RECOMMENDED | No - replaces all other filters |
| MY_ISSUES | No - replaces all other filters |
| CONTINUE_READING | No - replaces all other filters |
The special filters (RECOMMENDED, MY_ISSUES, and CONTINUE_READING) should be used alone as they replace all previously set filters. These filters implement specialized business logic that isn't compatible with standard filtering.
Special Filter Methods
For commonly used filters, the service provides dedicated methods:
public function myIssues(): self
public function onlyIds(): self
public function searchByMetadata(string $term): self
public function searchByISBN(string $isbn): self
public function searchWithContent(string $term): self
public function recommended(int $issueId): self
These methods apply specific filter logic and can be chained with other methods.
Technical Filter Implementation
Filter Validation and Structure
Each filter type is defined in the InventoryFilter enum:
enum InventoryFilter: string
{
case FREE = 'free_issues';
case LATEST = 'latest_issues';
case YEAR = 'years';
case MONTH = 'month';
case TAXONOMY = 'taxonomy';
case BISAC = 'bisac';
case CONTRIBUTOR = 'author';
case FILE_TYPE = 'file_type';
case AUDIENCE = 'audience';
case RECOMMENDED = 'recommended';
case MY_ISSUES = 'my_issues';
case CONTINUE_READING = 'continue_reading';
case ORDER_BY = 'orderBy';
// ...
}
Filters are validated through two enum methods:
requiresValue(): Determines if the filter requires a value parametervalidateValue(): Validates the structure and type of the provided value
// Value validation implementation
public function validateValue(mixed $value): bool
The validateValue method ensures that each filter value has the correct data structure:
- Numeric filters like
YEARexpect integer values - Date filters like
MONTHrequire an array with specific keys - Complex filters like
TAXONOMYandBISACrequire structured arrays with specific fields - String-based filters expect string values
- Filters without values (
FREE,LATEST, etc.) validate that no value is provided
This validation happens before any filter is applied to prevent runtime errors from incorrect filter specifications.
Technical Considerations
-
Mutually Exclusive Filters: Some filters like
MY_ISSUES,RECOMMENDED, andCONTINUE_READINGare designed to replace all other filters rather than combining with them. -
Taxonomy Filter Structure: Taxonomy filters require both the taxonomy type and term, with special handling for multiple terms (OR within same taxonomy) and ALL_TERMS_AVAILABLE constant for whole-taxonomy filtering.
-
Performance Impact:
- BISAC filtering uses nested set model (
lft/rgtfields) for efficient hierarchical queries - Taxonomy filtering may create complex join conditions that impact performance with large term sets
- BISAC filtering uses nested set model (
-
Filter Expansion Considerations:
- New filters must be added to the
InventoryFilterenum - Must implement validation logic in
validateValue()andrequiresValue() - Should consider query performance impact, especially for join-heavy filters
- Should be tested with large datasets to ensure acceptable performance
- New filters must be added to the
-
Tenant-specific Filter Behavior:
- Some filters (e.g., adult content filtering) depend on tenant settings
- Features like term-based search are gated behind tenant feature flags
- Permission-based filters interact with tenant access control settings
Filter Logic Implementation
The core filtering implementation is in the applyFilters method:
public function applyFilters(array $filters): self
This method processes an array of filter specifications and applies them to the query builder. For each filter:
- It extracts the filter type from the provided specification
- Validates that required values are present and correctly formatted
- Dispatches to the appropriate specialized filter method based on the filter type
- Returns the service instance for method chaining
The validation logic ensures that filters have the correct structure before they're applied, throwing exceptions for invalid configurations. This makes the filtering system both robust and extensible.
Search Implementation
The service implements three distinct search strategies:
Metadata Search (Default)
public function searchByMetadata(string $term): self
This method implements a comprehensive metadata-based publication search with an advanced scoring system:
-
Search Fields:
- Issue/publication name (exact and partial matches)
- Contributors' first and last names (authors, translators, editors)
- Taxonomy terms associated with the publication
- External identifiers (including ISBN)
-
Search Logic:
- Leverages full-text search v2 capabilities for better matching
- Implements a weighted scoring system that prioritizes exact matches
- Combines results from different metadata sources into a single relevance score
- Orders results by total score in descending order
-
Behavior:
- Search functions as an OR operation between terms
- Exact matches receive significantly higher scores
- Different weights are applied based on metadata type (name > contributors > terms)
- Results are limited to optimize performance while maintaining relevance
ISBN Search
public function searchByISBN(string $isbn): self
This method:
- Validates the ISBN format
- Searches for exact ISBN matches across the catalog
- Works with both ISBN-10 and ISBN-13 formats, handling hyphens and spaces
Content Search
public function searchWithContent(string $term): self
This method:
- Searches within the actual content text of publications
- Uses full-text indexing for performance
- May have longer response times due to the scope of the search
Query Building and Execution
Base Query Construction
The service builds a base query that respects user permissions and tenant settings:
protected function buildBaseQuery(): Builder
This method applies:
- User content restrictions
- Tenant availability rules
- Publication visibility settings
- Scheduled publication filters
Query Optimization
The service implements several query optimization techniques:
public function withEagerLoading(): self
public function limit(int $limit): self
These methods:
- Apply appropriate eager loading based on configuration
- Limit result sets to improve performance
LibraryFilters Trait
The filtering logic is implemented using the LibraryFilters trait, which provides helpers for each filter type:
trait LibraryFilters
{
private function maybeApplyFileTypeFilters(array $params, array &$filters): void
private function maybeApplyTaxonomyFilters(array $params, array &$filters): void
private function maybeApplyContributorFilters(array $params, array &$filters): void
private function maybeApplyBisacFilters(array $params, array &$filters): void
// ...
}
This trait is used by StorefrontService to translate URL parameters into structured filter arrays that are then passed to InventoryService.
Technical Implementation
Applied Design Patterns
- Builder Pattern: Used for constructing complex queries
- Strategy Pattern: Different search strategies based on context
- Fluent Interface: Methods return self for chaining
- Repository Pattern: Centralizes data access logic
Optimization Strategies
The service implements several optimization strategies to ensure efficient database queries:
-
Selective Eager Loading: Only loads related data when necessary, configurable through options.
-
Index Utilization: Queries are designed to leverage database indexes effectively, particularly for taxonomies and BISAC hierarchies.
-
Query Chunking: For operations on large result sets, the service can process data in manageable chunks to prevent memory issues.
-
Caching: Uses ID-based caching strategies to minimize database load for frequently accessed data.
-
Query Limiting: Supports limiting result sets to improve response times when complete result sets aren't required.
Important Notes for Implementers
1. All filters respect user permissions, tenant available content, and tenant configurations
2. Some filters may not be available depending on tenant features
3. Invalid filter values will trigger validation errors
4. Filter combination or ordering could be easily supported by shelves when we have the UI for it