m-tech-stack / laravel-api-model-client
A powerful Laravel package that enables Eloquent-like models to interact seamlessly with external APIs instead of a local database
Requires
- php: ^8.0|^8.1|^8.2|^8.3
- cebe/php-openapi: ^1.0
- guzzlehttp/guzzle: ^7.0
- illuminate/database: ^8.0|^9.0|^10.0|^11.0|^12.0
- illuminate/support: ^8.0|^9.0|^10.0|^11.0|^12.0
Requires (Dev)
- mockery/mockery: ^1.5
- orchestra/testbench: ^6.0|^7.0|^8.0|^9.0
- phpunit/phpunit: ^9.0|^10.0
- symfony/yaml: ^5.0|^6.0
README
Transform your Laravel applications with Eloquent-like API models powered by OpenAPI specifications
Build robust, type-safe API integrations with automatic model generation, intelligent caching, and comprehensive relationship support
π Why Choose Laravel API Model Client?
The most advanced Laravel package for API integration - Transform external APIs into familiar Eloquent models with zero configuration complexity.
β¨ OpenAPI-First Approach - Automatic model generation from your API specifications
π Eloquent Compatibility - Use familiar Laravel syntax with external APIs
β‘ High Performance - Intelligent caching and query optimization
π‘οΈ Type Safety - Full schema validation and type checking
π Relationship Support - Complete relationship mapping and lazy loading
π NEW in v1.2.1: Complete attribute flattening fixes, clean trait architecture, and comprehensive API response handling!
π§ MAJOR FIXES: All attribute flattening issues resolved - no more single 'data' attribute pollution!
π Table of Contents
Click to expand navigation
- π Why Choose Laravel API Model Client?
- π§ Recent Major Fixes
- π Features
- π¦ Installation
- β‘ Quick Start
- π OpenAPI Integration
- ποΈ Basic Usage
- π API Relationships
- π Query Builder
- β‘ Caching
- π Authentication
- π οΈ Artisan Commands
- π§ͺ Testing
- π Performance & Benchmarks
- π§ Configuration
- π Documentation
- π¨ Troubleshooting
- πΊοΈ Roadmap
- π€ Contributing
- π License
π§ Recent Major Fixes
β Comprehensive Attribute Flattening Resolution (v1.2.1)
Problem Solved: API responses were being stored in a single data
attribute instead of being flattened into individual model attributes.
Root Cause Found: Multiple methods were using mapApiAttributes()
and fill()
instead of the proper newFromApiResponse()
method.
Fixed Methods:
- β
find()
method inApiModelQueries.php
- β
all()
method inApiModelQueries.php
- β
save()
method inApiModelQueries.php
- β
createViaApi()
method inApiModel.php
- β
updateViaApi()
method inApiModel.php
Before (Problematic):
$product = Product::find(1); echo $product->data['name']; // β All data in single attribute
After (Fixed):
$product = Product::find(1); echo $product->name; // β Individual attributes accessible echo $product->id; // β Clean attribute access echo $product->sku; // β No data pollution
π§Ή Complete Trait Architecture Cleanup
Removed Problematic Methods:
- β Infinite recursion in
HasApiOperations::newFromApiResponse()
- β Complex reflection logic in
LazyLoadsApiRelationships
- β Method conflicts between traits
Result: Clean, professional trait architecture with no conflicts or redundant code.
π§ Enhanced Metadata Storage
- β
Separate
$apiResponseData
property for metadata - β Clean model serialization without pollution
- β
Original API response accessible via
getApiResponseData()
π Features
π― OpenAPI Integration (NEW)
- π Automatic Model Generation: Generate Laravel models directly from OpenAPI 3.0+ specifications
- β Schema Validation: Validate API requests/responses against OpenAPI schemas with configurable strictness
- π Dynamic Query Building: Build queries with automatic OpenAPI parameter validation and transformation
- π Relationship Detection: Automatic relationship mapping from OpenAPI
$ref
references - π Multi-Schema Support: Handle multiple API versions and schemas simultaneously
- β‘ Performance Optimization: Schema caching, lazy loading, and intelligent query optimization
ποΈ Core Capabilities
- Eloquent-like API Models: Work with external APIs using familiar Eloquent syntax with full Laravel integration
- Hybrid Data Source Management: Intelligent switching between database and API with 5 sophisticated modes
- Advanced Multi-Layer Caching: High-performance caching system with Redis support and configurable TTL
- Comprehensive Query Builder: Chainable query methods with advanced filtering, sorting, and pagination
- Full API Relationships: Complete relationship support with lazy loading and eager loading optimization
π§ Advanced Features
- Multi-Authentication Support: Bearer tokens, Basic auth, API keys, OAuth2, and custom authentication strategies
- Robust Error Handling: Circuit breaker pattern, retry logic, fallback mechanisms, and detailed logging
- Event-Driven Architecture: Laravel event integration with custom API model events and lifecycle hooks
- Extensible Middleware Pipeline: Built-in middleware for authentication, rate limiting, caching, and validation
- Powerful Response Transformers: Transform API responses with custom transformer classes and data mapping
- Intelligent Database Synchronization: Seamless sync between API and local database with conflict resolution
π― Enterprise Features
- High-Performance Caching: Redis-based caching with intelligent cache warming and distributed support
- Testing Framework: Comprehensive testing utilities, mock factories, and API response mocking
- Console Commands: Rich set of Artisan commands for schema validation, model generation, and debugging
- Developer Tools: OpenAPI schema parsing, model generation, and comprehensive documentation
- Migration Tools: Seamless migration from manual to OpenAPI-driven configuration
- Monitoring & Debugging: Performance monitoring, health checks, and detailed error reporting
π¦ Installation
Requirements
- PHP 8.1 or higher
- Laravel 10.0 or higher
- OpenAPI 3.0+ specification (for OpenAPI features)
Install via Composer
# Install the main package composer require m-tech-stack/laravel-api-model-client # Install OpenAPI dependency for schema parsing composer require cebe/php-openapi
Publish Configuration
# Publish all configuration files php artisan vendor:publish --provider="MTechStack\LaravelApiModelClient\ServiceProvider" # Or publish specific configurations php artisan vendor:publish --tag="api-client-config" php artisan vendor:publish --tag="api-client-examples"
Environment Setup
# Copy example environment variables cp .env.api-client.example .env.local # Add to your .env file API_CLIENT_PRIMARY_SCHEMA=https://api.example.com/openapi.json API_CLIENT_PRIMARY_BASE_URL=https://api.example.com API_CLIENT_PRIMARY_TOKEN=your-api-token
β‘ Quick Start
1. Basic Configuration
// config/api-client.php return [ 'schemas' => [ 'primary' => [ 'source' => env('API_CLIENT_PRIMARY_SCHEMA'), 'base_url' => env('API_CLIENT_PRIMARY_BASE_URL'), 'authentication' => [ 'type' => 'bearer', 'token' => env('API_CLIENT_PRIMARY_TOKEN'), ], 'validation' => [ 'enabled' => true, 'strictness' => 'moderate', // strict, moderate, lenient ], 'caching' => [ 'enabled' => true, 'ttl' => 3600, ], ], ], ];
2. Create Your First Model
<?php namespace App\Models\Api; use MTechStack\LaravelApiModelClient\Models\ApiModel; use MTechStack\LaravelApiModelClient\Traits\HasOpenApiSchema; class Product extends ApiModel { use HasOpenApiSchema; protected string $openApiSchemaSource = 'primary'; protected string $endpoint = '/products'; protected $fillable = [ 'name', 'description', 'price', 'category_id' ]; // Relationships are automatically detected from OpenAPI schema public function category() { return $this->belongsToFromApi(Category::class, 'category_id'); } }
3. Use the Model
// Query with OpenAPI validation $products = Product::whereOpenApi('status', 'active') ->whereOpenApi('price', '>', 10.00) ->orderByOpenApi('created_at', 'desc') ->limitOpenApi(20) ->get(); // Create with automatic validation $product = Product::create([ 'name' => 'New Product', 'price' => 29.99, 'status' => 'active', ]); // Access relationships $category = $product->category;
4. Validate Your Setup
# Validate schema and configuration php artisan api-client:validate-schema # Test API connectivity php artisan api-client:test-connection # Generate models from schema php artisan api-client:generate-models
5. π§ Test Attribute Flattening (CRITICAL)
The most important verification step - Ensure API responses are properly flattened into individual model attributes:
# Test in Laravel Tinker
php artisan tinker
// β Test individual attribute access (should work) $product = Product::find(1); echo "ID: " . $product->id . "\n"; // β Should work echo "Name: " . $product->name . "\n"; // β Should work echo "SKU: " . $product->sku . "\n"; // β Should work // β Verify no data pollution (should be null/empty) var_dump($product->data); // β Should be null // β Check model attributes are clean print_r($product->getAttributes()); // β Should show individual fields // β Test relations work properly $variants = $product->variants; // β Should return collection $image = $product->featured_image; // β Should return model instance // β Test metadata storage is separate $apiData = $product->getApiResponseData(); // β Should contain raw API response
Expected Results (v1.2.1 Fixes):
- β
Individual attributes accessible:
$product->id
,$product->name
,$product->sku
- β
No single
data
attribute containing everything - β Clean model serialization without metadata pollution
- β Working relations that return proper model instances
- β
Raw API data accessible via
getApiResponseData()
method
If you see problems:
// β PROBLEM: Single data attribute (old behavior) $product = Product::find(1); var_dump($product->getAttributes()); // Shows: ['data' => ['id' => 1, 'name' => 'Product']] β // β EXPECTED: Individual attributes (fixed behavior) $product = Product::find(1); var_dump($product->getAttributes()); // Shows: ['id' => 1, 'name' => 'Product', 'sku' => 'ABC123'] β
If you encounter the problem behavior, ensure you're using v1.2.1+ and clear caches:
composer update m-tech-stack/laravel-api-model-client php artisan cache:clear php artisan config:clear
π OpenAPI Integration
Automatic Model Generation
Generate Laravel models directly from your OpenAPI specification:
# Generate all models from primary schema php artisan api-client:generate-models # Generate from specific schema php artisan api-client:generate-models --schema=ecommerce # Generate with custom namespace php artisan api-client:generate-models --namespace=App\Models\Ecommerce
Schema Validation
Validate API requests and responses against your OpenAPI schema:
class Product extends ApiModel { use HasOpenApiSchema; // Validation is automatic based on OpenAPI schema protected array $validationConfig = [ 'strictness' => 'moderate', // strict, moderate, lenient 'validate_requests' => true, 'validate_responses' => true, 'fail_on_validation_error' => true, ]; }
Dynamic Query Building
Build queries with automatic parameter validation:
// These methods validate parameters against OpenAPI schema $products = Product::whereOpenApi('category_id', 1) ->whereOpenApi('price', 'between', [10, 100]) ->whereOpenApi('tags', 'contains', 'electronics') ->orderByOpenApi('popularity', 'desc') ->paginateOpenApi(20); // Custom query parameters from OpenAPI spec $products = Product::withOpenApiParams([ 'include' => 'category,reviews', 'fields' => 'id,name,price', 'filter[status]' => 'active', ])->get();
Multi-Schema Support
Handle multiple APIs and versions:
// config/api-client.php 'schemas' => [ 'ecommerce_v1' => [ 'source' => 'https://api.shop.com/v1/openapi.json', 'base_url' => 'https://api.shop.com/v1', ], 'ecommerce_v2' => [ 'source' => 'https://api.shop.com/v2/openapi.json', 'base_url' => 'https://api.shop.com/v2', ], 'payment' => [ 'source' => storage_path('schemas/stripe-openapi.json'), 'base_url' => 'https://api.stripe.com', ], ]; // Use different schemas in models class ProductV1 extends ApiModel { use HasOpenApiSchema; protected string $openApiSchemaSource = 'ecommerce_v1'; } class ProductV2 extends ApiModel { use HasOpenApiSchema; protected string $openApiSchemaSource = 'ecommerce_v2'; }
π οΈ Artisan Commands
The package provides a comprehensive set of Artisan commands for managing OpenAPI schemas, models, and testing:
Schema Management
# Validate OpenAPI schema and configuration php artisan api-client:validate-schema php artisan api-client:validate-schema primary --health-check php artisan api-client:validate-schema --detailed --format=json # Test API connectivity and authentication php artisan api-client:test-connection php artisan api-client:test-connection primary --timeout=30 php artisan api-client:test-connection --all-schemas # Parse and analyze OpenAPI schemas php artisan api-client:parse-openapi schema.json php artisan api-client:parse-openapi --output=parsed-schema.json
Model Generation
# Generate models from OpenAPI schema php artisan api-client:generate-models php artisan api-client:generate-models --schema=ecommerce php artisan api-client:generate-models --namespace=App\\Models\\Api php artisan api-client:generate-models --output-dir=app/Models/Generated # Generate specific models php artisan api-client:generate-models --models=Product,Category,Order php artisan api-client:generate-models --force --backup
Cache Management
# Manage schema and response caches php artisan api-client:cache clear php artisan api-client:cache warm php artisan api-client:cache status php artisan api-client:cache clear --tags=products,categories # Performance optimization php artisan api-client:cache optimize php artisan api-client:cache benchmark
Testing and Debugging
# Comprehensive testing suite with enhanced capabilities php artisan api-client:test # Schema-specific testing php artisan api-client:test --schema=primary php artisan api-client:test --schema=ecommerce --verbose # Model and endpoint filtering php artisan api-client:test --models=Product,Category,Order php artisan api-client:test --endpoints=/products,/categories php artisan api-client:test --models=Product --endpoints=/products # Performance and load testing php artisan api-client:test --performance --iterations=100 php artisan api-client:test --load-test --concurrent=10 --iterations=50 php artisan api-client:test --performance --load-test --verbose # Coverage analysis php artisan api-client:test --coverage php artisan api-client:test --coverage --models=Product,Category php artisan api-client:test --schema=primary --coverage --verbose # Output formatting and saving php artisan api-client:test --format=json php artisan api-client:test --format=yaml --output=test-results.yaml php artisan api-client:test --format=html --output=test-report.html # Advanced testing options php artisan api-client:test --timeout=60 --fail-fast php artisan api-client:test --dry-run --verbose php artisan api-client:test --performance --concurrent=5 --iterations=200 # Complete test suite example php artisan api-client:test \ --schema=ecommerce \ --models=Product,Category,Order \ --performance \ --coverage \ --format=html \ --output=comprehensive-test-report.html \ --verbose
Test Command Features
The enhanced api-client:test
command provides comprehensive testing capabilities:
π Configuration & Schema Testing
- Validates configuration files and settings
- Tests OpenAPI schema accessibility and validity
- Checks schema version compatibility
- Validates authentication configuration
π Connectivity Testing
- Tests API endpoint connectivity
- Measures response times
- Validates authentication headers
- Checks SSL/TLS configuration
ποΈ Model Testing
- Tests model instantiation and configuration
- Validates model-to-schema mapping
- Tests query builder functionality
- Checks relationship definitions
- Validates OpenAPI integration
π― Endpoint Testing
- Tests individual API endpoints
- Validates request/response formats
- Measures endpoint performance
- Checks authentication requirements
β‘ Performance Testing
- Benchmarks API response times
- Tests concurrent request handling
- Measures memory usage
- Calculates requests per second
- Provides percentile analysis (P95, P99)
π Load Testing
- Simulates concurrent users
- Tests system under load
- Measures peak performance
- Analyzes concurrent user performance
- Provides RPS over time analysis
π Coverage Analysis
- Analyzes schema definition coverage
- Measures endpoint test coverage
- Evaluates model implementation coverage
- Validates validation rule coverage
- Provides overall coverage metrics
π Reporting & Output
- Multiple output formats (table, JSON, YAML, HTML)
- Detailed verbose reporting
- Save results to files
- Comprehensive HTML reports
- Performance metrics and charts
Debug API requests and responses
php artisan api-client:debug php artisan api-client:debug --endpoint=/products php artisan api-client:debug --model=Product --method=create
### Configuration Management
```bash
# Publish and manage configurations
php artisan api-client:publish-config
php artisan api-client:publish-config --examples
php artisan api-client:publish-config --force
# Environment setup
php artisan api-client:setup
php artisan api-client:setup --interactive
π Performance & Benchmarks
Performance Metrics
Laravel API Model Client is designed for high-performance applications with enterprise-grade requirements:
Metric | Performance | Details |
---|---|---|
Response Time | < 50ms | Average API model query response time |
Memory Usage | < 32MB | Peak memory usage during test suite execution |
Cache Hit Rate | 95%+ | Intelligent caching with Redis backend |
Concurrent Requests | 100+ RPS | Sustained requests per second |
Test Coverage | 96%+ | Comprehensive test suite coverage |
Schema Validation | < 5ms | OpenAPI schema validation overhead |
Benchmarking Results
# Run performance benchmarks php artisan api-client:test --performance --iterations=1000 # Results (average over 1000 iterations): # β Model Creation: 12ms # β Query Execution: 8ms # β Relationship Loading: 15ms # β Cache Operations: 2ms # β Schema Validation: 3ms
Optimization Features
- π Intelligent Query Batching - Automatic request batching for bulk operations
- β‘ Redis-Based Caching - High-performance caching with configurable TTL
- π Connection Pooling - Efficient HTTP connection management
- π Query Optimization - Smart query parameter optimization
- π― Lazy Loading - Efficient relationship loading strategies
π§ͺ Testing
Built-in Testing Framework
The package includes a comprehensive testing framework with utilities for mocking API responses and testing OpenAPI integration:
<?php namespace Tests\Feature; use Tests\TestCase; use App\Models\Api\Product; use MTechStack\LaravelApiModelClient\Testing\MocksApiResponses; class ProductApiTest extends TestCase { use MocksApiResponses; /** @test */ public function it_can_create_product_with_openapi_validation() { // Mock API response $this->mockApiResponse('POST', '/products', [ 'id' => 1, 'name' => 'Test Product', 'price' => 29.99, 'status' => 'active', ]); // Test with OpenAPI validation $product = Product::create([ 'name' => 'Test Product', 'price' => 29.99, 'status' => 'active', ]); $this->assertInstanceOf(Product::class, $product); $this->assertEquals('Test Product', $product->name); } /** @test */ public function it_validates_against_openapi_schema() { $this->expectException(ValidationException::class); // This should fail OpenAPI validation Product::create([ 'name' => '', // Required field 'price' => -10, // Invalid price ]); } }
Performance Testing
/** @test */ public function it_performs_efficiently_with_large_datasets() { $startTime = microtime(true); $startMemory = memory_get_usage(); // Test bulk operations $products = Product::whereOpenApi('status', 'active') ->limitOpenApi(1000) ->get(); $endTime = microtime(true); $endMemory = memory_get_usage(); $this->assertLessThan(2.0, $endTime - $startTime); $this->assertLessThan(50 * 1024 * 1024, $endMemory - $startMemory); }
π¨ Troubleshooting
Common Issues & Solutions
π§ API Connection Issues
// Enable debug mode to see detailed request/response information config(['api-client.debug' => true]); // Check the last request and response $lastRequest = ApiClient::getLastRequest(); $lastResponse = ApiClient::getLastResponse(); // Test connectivity php artisan api-client:test-connection --verbose
Common causes:
- Invalid API endpoint URLs
- Authentication token expired
- Network connectivity issues
- SSL certificate problems
β‘ Performance Issues
// Enable high-performance caching config(['high-performance-cache.enabled' => true]); // Use query optimization $products = Product::select(['id', 'name', 'price']) ->with('category') ->limit(100) ->get(); // Check cache status php artisan api-client:cache status
Optimization tips:
- Enable Redis caching
- Use selective field loading
- Implement proper pagination
- Utilize relationship eager loading
π‘οΈ Schema Validation Errors
// Adjust validation strictness config(['api-client.schemas.primary.validation.strictness' => 'lenient']); // Validate schema manually php artisan api-client:validate-schema --detailed // Debug validation issues Product::create($data); // Will show detailed validation errors
Common solutions:
- Update OpenAPI schema files
- Adjust validation strictness levels
- Check data type compatibility
- Verify required field mappings
Debug Commands
# Comprehensive system check php artisan api-client:test --verbose # Schema validation php artisan api-client:validate-schema --health-check # Performance analysis php artisan api-client:test --performance --coverage # Cache diagnostics php artisan api-client:cache status --detailed
πΊοΈ Roadmap
Upcoming Features
Feature | Status | Target Version | Description |
---|---|---|---|
GraphQL Support | π In Progress | v1.3.0 | Native GraphQL API integration |
Real-time Subscriptions | π Planned | v1.4.0 | WebSocket and SSE support |
Advanced Caching | π In Progress | v1.3.0 | Multi-tier caching strategies |
API Versioning | π Planned | v1.5.0 | Automatic API version management |
Monitoring Dashboard | π‘ Concept | v2.0.0 | Web-based monitoring interface |
AI-Powered Optimization | π‘ Concept | v2.0.0 | ML-based query optimization |
Recent Updates
- β v1.2.0 - Complete OpenAPI 3.0+ integration
- β v1.1.0 - High-performance caching system
- β v1.0.14 - Built-in SyncWithApi trait integration
- β v1.0.0 - Initial stable release
Community Requests
Vote for features on our GitHub Discussions page!
π Documentation
π Comprehensive Guides
- OpenAPI Integration Guide - Complete guide to OpenAPI features
- Migration Guide - Migrate from manual to OpenAPI configuration
- Best Practices - Performance, security, and optimization
- Troubleshooting - Common issues and solutions
- E-commerce Examples - Real-world implementations
π§ API Reference
- Model Methods - Complete API model method reference
- Query Builder - OpenAPI query builder methods
- Validation - Schema validation options
- Caching - Caching strategies and configuration
π Examples and Tutorials
- Quick Start Tutorial - Get started in 5 minutes
- E-commerce Integration - Build an e-commerce API client
- Multi-tenant SaaS - Handle multiple API versions
- Performance Optimization - Optimize for scale
ποΈ Configuration
The package uses multiple configuration files for different aspects:
Main Configuration (config/api-client.php
)
return [ 'client' => [ 'base_url' => env('API_MODEL_RELATIONS_BASE_URL'), 'timeout' => env('API_MODEL_RELATIONS_TIMEOUT', 30), 'connect_timeout' => env('API_MODEL_RELATIONS_CONNECT_TIMEOUT', 10), ], 'auth' => [ 'strategy' => env('API_MODEL_RELATIONS_AUTH_STRATEGY', 'bearer'), 'credentials' => [ 'token' => env('API_MODEL_RELATIONS_AUTH_TOKEN'), 'username' => env('API_MODEL_RELATIONS_AUTH_USERNAME'), 'password' => env('API_MODEL_RELATIONS_AUTH_PASSWORD'), 'api_key' => env('API_MODEL_RELATIONS_AUTH_API_KEY'), 'header_name' => env('API_MODEL_RELATIONS_AUTH_HEADER_NAME', 'X-API-KEY'), ], ], 'events' => [ 'enabled' => env('API_MODEL_RELATIONS_EVENTS_ENABLED', true), ], 'debug' => env('API_MODEL_RELATIONS_DEBUG', false), ];
Hybrid Data Source Configuration (config/hybrid-data-source.php
)
return [ 'global_mode' => env('API_MODEL_DATA_SOURCE_MODE', 'hybrid'), 'models' => [ 'product' => [ 'data_source_mode' => 'api_first', 'sync_enabled' => true, 'cache_ttl' => 3600, ], ], ];
High-Performance Cache Configuration (config/high-performance-cache.php
)
return [ 'enabled' => env('HIGH_PERFORMANCE_CACHE_ENABLED', true), 'driver' => env('HIGH_PERFORMANCE_CACHE_DRIVER', 'redis'), 'redis' => [ 'connection' => 'default', 'prefix' => 'api_model_hp:', 'serializer' => 'igbinary', ], 'ttl' => [ 'default' => 3600, 'models' => [ 'product' => 7200, 'category' => 86400, ], ], ];
API Cache Configuration (config/api-cache.php
)
return [ 'enabled' => env('API_CACHE_ENABLED', true), 'default_ttl' => env('API_CACHE_DEFAULT_TTL', 3600), 'store' => env('API_CACHE_STORE', 'redis'), 'prefix' => env('API_CACHE_PREFIX', 'api_cache:'), 'tags' => [ 'enabled' => true, 'separator' => ':', ], ];
Basic Usage
Creating an API Model
Create a model that extends ApiModel
(which includes all necessary traits automatically):
<?php namespace App\Models\Api; use MTechStack\LaravelApiModelClient\Models\ApiModel; class Product extends ApiModel { /** * The API endpoint for this model. * * @var string */ protected $apiEndpoint = 'products'; /** * The database table for hybrid data source mode (optional). * * @var string */ protected $table = 'products'; /** * Data source mode for this model (optional - defaults to config). * Available modes: 'api_only', 'db_only', 'hybrid', 'api_first', 'dual_sync' * * @var string */ protected $dataSourceMode = 'hybrid'; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'id', 'name', 'description', 'price', 'category_id', ]; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'price' => 'float', 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; /** * Get the category that owns the product. */ public function category() { return $this->belongsToFromApi(Category::class, 'category_id'); } /** * Get the reviews for the product. */ public function reviews() { return $this->hasManyFromApi(Review::class, 'product_id'); } }
Built-in Traits and Capabilities
The ApiModel
class automatically includes these powerful traits:
ApiModelAttributes
: Enhanced attribute handling for API dataApiModelCaching
: Intelligent caching with TTL and invalidationApiModelErrorHandling
: Comprehensive error handling and loggingApiModelEvents
: Laravel event integration for API operationsApiModelInterfaceMethods
: Core API interface methodsApiModelQueries
: Advanced query builder for API requestsHasApiRelationships
: Full relationship support (hasMany, belongsTo, etc.)LazyLoadsApiRelationships
: Efficient lazy loading of relationshipsSyncWithApi
: Database synchronization methods (syncFromApi, syncToApi)HybridDataSource
: Intelligent switching between API and database
β οΈ Important Note: As of v1.0.14, the
SyncWithApi
trait is now built intoApiModel
. You no longer need to manually adduse SyncWithApi;
to your models. The trait collision betweenSyncWithApi::syncToApi
andHybridDataSource::syncToApi
has been resolved withHybridDataSource
taking precedence for better hybrid data source compatibility.
This means you get all these capabilities automatically without needing to manually add traits:
// All of these work out of the box with ApiModel $product = Product::find(1); // HybridDataSource $products = Product::where('active', true)->get(); // ApiModelQueries $category = $product->category; // HasApiRelationships + LazyLoading $product->save(); // ApiModelInterfaceMethods + Events $product->syncFromApi(); // SyncWithApi (now built-in) // Automatic caching, error handling, and event firing
Migration from Previous Versions
If you're upgrading from a version prior to v1.0.14 and have models that manually include the SyncWithApi
trait:
// β Old way (will cause trait collision) class Product extends ApiModel { use SyncWithApi; // Remove this line protected $apiEndpoint = 'products'; } // β New way (v1.0.14+) class Product extends ApiModel { // SyncWithApi is now included automatically protected $apiEndpoint = 'products'; }
Using API Models
Use API models just like regular Eloquent models:
// Find a product by ID $product = Product::find(1); // Get all products $products = Product::all(); // Query products $expensiveProducts = Product::where('price', '>', 100)->get(); // Use relationships $category = $product->category; $reviews = $product->reviews; // Create a new product $newProduct = Product::create([ 'name' => 'New Product', 'description' => 'This is a new product', 'price' => 99.99, 'category_id' => 2, ]); // Update a product $product->update(['price' => 149.99]); // Delete a product $product->delete();
API Model Lifecycle
Understanding the API model lifecycle helps you work with the package more effectively:
// Creating a new model instance $product = new Product(); $product->name = 'New Product'; $product->price = 99.99; // Save to API - triggers API POST request $product->save(); // At this point, the model has been saved to the API and has an ID // Refresh from API - triggers API GET request $product->refreshFromApi(); // Update model - triggers API PUT/PATCH request $product->price = 149.99; $product->save(); // Delete from API - triggers API DELETE request $product->delete();
API Relationships
Available Relationships
The package supports the following relationship types:
hasManyFromApi
: One-to-many relationshipbelongsToFromApi
: Many-to-one relationshiphasOneFromApi
: One-to-one relationshipbelongsToManyFromApi
: Many-to-many relationshiphasManyThroughFromApi
: One-to-many relationship through an intermediate modelmorphManyFromApi
: Polymorphic one-to-many relationship
Defining Relationships
Define relationships in your models:
// One-to-many relationship public function reviews() { return $this->hasManyFromApi(Review::class, 'product_id'); } // Many-to-one relationship public function category() { return $this->belongsToFromApi(Category::class, 'category_id'); } // One-to-one relationship public function featuredImage() { return $this->hasOneFromApi(Image::class, 'product_id'); } // Many-to-many relationship public function tags() { return $this->belongsToManyFromApi( Tag::class, 'product_tags', // pivot endpoint 'product_id', 'tag_id' ); } // One-to-many relationship through an intermediate model public function comments() { return $this->hasManyThroughFromApi( Comment::class, // Final model we want to access Post::class, // Intermediate model 'user_id', // Foreign key on intermediate model 'post_id' // Foreign key on final model ); } // Polymorphic one-to-many relationship public function comments() { return $this->morphManyFromApi( Comment::class, 'commentable' // The name of the relationship ); } // In your Comment model public function commentable() { return $this->morphToFromApi(); }
Working with Relationships
Here are examples of working with different relationship types:
// One-to-many relationship $product = Product::find(1); $reviews = $product->reviews; // Get all reviews for this product // Create a new related model $newReview = $product->reviews()->create([ 'rating' => 5, 'comment' => 'Great product!' ]); // Many-to-one relationship $review = Review::find(1); $product = $review->product; // Get the product for this review // One-to-one relationship $product = Product::find(1); $image = $product->featuredImage; // Get the featured image // Update a related model $product->featuredImage->update([ 'url' => 'https://example.com/new-image.jpg' ]); // Many-to-many relationship $product = Product::find(1); $tags = $product->tags; // Get all tags for this product // Attach a tag to a product $product->tags()->attach(5); // Detach a tag from a product $product->tags()->detach(3); // Sync tags (remove existing and add new ones) $product->tags()->sync([1, 2, 5]); // One-to-many through relationship $user = User::find(1); $comments = $user->comments; // Get all comments on the user's posts // Polymorphic relationships $product = Product::find(1); $comments = $product->comments; // Get comments for this product $post = Post::find(1); $comments = $post->comments; // Get comments for this post
Query Builder
Use the query builder to filter API results:
// Basic where clauses $products = Product::where('category_id', 1) ->where('price', '>', 50) ->get(); // Where with array of conditions $products = Product::where([ ['status', '=', 'active'], ['price', '>', 100] ])->get(); // Where with OR condition $products = Product::where('category_id', 1) ->orWhere('featured', true) ->get(); // Where with nested conditions $products = Product::where('category_id', 1) ->where(function($query) { $query->where('price', '>', 100) ->orWhere('featured', true); }) ->get(); // Order by $products = Product::orderBy('price', 'desc')->get(); // Multiple order by $products = Product::orderBy('category_id') ->orderBy('price', 'desc') ->get(); // Limit and offset $products = Product::limit(10)->offset(20)->get(); // Pagination $products = Product::paginate(15); $products = Product::where('category_id', 1)->paginate(15); // Custom query parameters $products = Product::withQueryParam('include', 'category,tags') ->withQueryParam('fields', 'id,name,price') ->get(); // Custom macros $products = Product::whereContains('name', 'phone')->get();
Caching
API responses are automatically cached based on your configuration. You can customize caching behavior:
// Set a custom cache TTL for a specific model class Product extends ApiModel { use SyncWithApi; protected $apiEndpoint = 'products'; protected $cacheTtl = 1800; // 30 minutes } // Disable caching for a query $products = Product::withoutCache()->get(); // Refresh cache for a model $product = Product::find(1); $product->refreshFromApi(); // Clear cache for a model Product::clearCache(); // Clear cache for a specific model instance $product = Product::find(1); $product->clearCache(); // Clear cache for a specific query Product::where('category_id', 1)->clearCache(); // Set custom cache key $products = Product::withCacheKey('featured_products') ->where('featured', true) ->get();
Authentication
The package supports multiple authentication strategies:
Bearer Token
// In your .env file API_MODEL_RELATIONS_AUTH_STRATEGY=bearer API_MODEL_RELATIONS_AUTH_TOKEN=your-token-here // Or set dynamically in your code ApiClient::setAuthStrategy('bearer'); ApiClient::setAuthToken('your-dynamic-token');
Basic Auth
// In your .env file API_MODEL_RELATIONS_AUTH_STRATEGY=basic API_MODEL_RELATIONS_AUTH_USERNAME=your-username API_MODEL_RELATIONS_AUTH_PASSWORD=your-password // Or set dynamically in your code ApiClient::setAuthStrategy('basic'); ApiClient::setBasicAuth('username', 'password');
API Key
// In your .env file API_MODEL_RELATIONS_AUTH_STRATEGY=api_key API_MODEL_RELATIONS_AUTH_API_KEY=your-api-key API_MODEL_RELATIONS_AUTH_HEADER_NAME=X-API-KEY // Or set dynamically in your code ApiClient::setAuthStrategy('api_key'); ApiClient::setApiKey('your-api-key', 'X-API-KEY');
Custom Authentication
You can implement custom authentication strategies:
use MTechStack\LaravelApiModelClient\Auth\AuthStrategyInterface; class CustomAuthStrategy implements AuthStrategyInterface { public function apply($request) { // Apply your custom authentication to the request return $request->withHeader('X-Custom-Auth', 'custom-value'); } } // Register your custom strategy app()->bind('api-model-relations.auth.custom', function() { return new CustomAuthStrategy(); }); // Use your custom strategy ApiClient::setAuthStrategy('custom');
Events
The package dispatches events during the API request lifecycle:
use MTechStack\LaravelApiModelClient\Events\ApiRequestEvent; use MTechStack\LaravelApiModelClient\Events\ApiResponseEvent; use MTechStack\LaravelApiModelClient\Events\ApiExceptionEvent; use MTechStack\LaravelApiModelClient\Events\ModelCreatedEvent; use MTechStack\LaravelApiModelClient\Events\ModelUpdatedEvent; use MTechStack\LaravelApiModelClient\Events\ModelDeletedEvent; use Illuminate\Support\Facades\Event; // Listen for API request events Event::listen(ApiRequestEvent::class, function (ApiRequestEvent $event) { $method = $event->method; $endpoint = $event->endpoint; $options = $event->options; // Do something before the API request logger()->info("API Request: {$method} {$endpoint}"); }); // Listen for API response events Event::listen(ApiResponseEvent::class, function (ApiResponseEvent $event) { $response = $event->response; $statusCode = $event->statusCode; // Do something with the API response logger()->info("API Response: {$statusCode}"); }); // Listen for API exception events Event::listen(ApiExceptionEvent::class, function (ApiExceptionEvent $event) { $exception = $event->exception; // Handle API exceptions logger()->error("API Exception: {$exception->getMessage()}"); }); // Listen for model lifecycle events Event::listen(ModelCreatedEvent::class, function (ModelCreatedEvent $event) { $model = $event->model; logger()->info("Model created: " . get_class($model) . " #{$model->id}"); });
Middleware Pipeline
The package uses a middleware pipeline to process API requests. You can add custom middleware:
use MTechStack\LaravelApiModelClient\Middleware\AbstractApiMiddleware; class CustomMiddleware extends AbstractApiMiddleware { public function __construct() { $this->priority = 50; // Set middleware priority } public function handle($request, \Closure $next) { // Modify the request $request = $request->withHeader('X-Custom-Header', 'custom-value'); // Call the next middleware $response = $next($request); // Modify the response return $response->withHeader('X-Response-Time', microtime(true) - LARAVEL_START); } } // Register your middleware app()->bind('api-model-relations.middleware.custom', function() { return new CustomMiddleware(); }); // Add your middleware to the pipeline config(['api-model-relations.middleware' => array_merge( config('api-model-relations.middleware', []), ['custom'] )]);
Error Handling
The package provides comprehensive error handling for API requests:
// Try to find a model that doesn't exist try { $product = Product::findOrFail(999); } catch (\MTechStack\LaravelApiModelClient\Exceptions\ModelNotFoundException $e) { // Handle not found exception logger()->error("Product not found: {$e->getMessage()}"); } // Try to create a model with validation errors try { $product = Product::create([ 'name' => '', // Required field 'price' => 'invalid' // Should be a number ]); } catch (\MTechStack\LaravelApiModelClient\Exceptions\ValidationException $e) { // Get validation errors $errors = $e->getErrors(); logger()->error("Validation errors: " . json_encode($errors)); } // Handle API connection errors try { $products = Product::all(); } catch (\MTechStack\LaravelApiModelClient\Exceptions\ApiConnectionException $e) { // Handle connection error logger()->error("API connection error: {$e->getMessage()}"); } // Get the last API response $lastResponse = ApiClient::getLastResponse(); $statusCode = $lastResponse->getStatusCode(); $body = $lastResponse->getBody()->getContents(); // Get the last API request $lastRequest = ApiClient::getLastRequest(); $method = $lastRequest->getMethod(); $uri = $lastRequest->getUri();
Advanced Usage
Hybrid Data Source Management
The ApiModel
class includes the HybridDataSource
trait automatically, providing sophisticated hybrid data management between API and database:
use MTechStack\LaravelApiModelClient\Models\ApiModel; class Product extends ApiModel { protected $apiEndpoint = 'products'; protected $table = 'products'; // Set the data source mode (optional - defaults to config) protected $dataSourceMode = 'hybrid'; } // Usage examples for different modes: // 1. Hybrid Mode (database first, API fallback) $product = Product::find(1); // Checks database first, then API // 2. API First Mode (API first, sync to database) $product = new Product(); $product->dataSourceMode = 'api_first'; $product = $product->find(1); // Checks API first, syncs to database // 3. Dual Sync Mode (keep both in sync) $product = new Product(); $product->dataSourceMode = 'dual_sync'; $product->name = 'Updated Product'; $product->save(); // Saves to both API and database
Available Data Source Modes
The HybridDataSource
trait supports five intelligent modes:
api_only
: All operations use API exclusivelydb_only
: All operations use database exclusivelyhybrid
: Check database first, fallback to APIapi_first
: Check API first, sync to databasedual_sync
: Keep both database and API in sync
Configuration
Configure hybrid data source modes using the dedicated configuration file:
// config/hybrid-data-source.php return [ 'global_mode' => env('API_MODEL_DATA_SOURCE_MODE', 'hybrid'), 'models' => [ 'product' => [ 'data_source_mode' => env('PRODUCT_DATA_SOURCE_MODE', 'api_first'), 'sync_enabled' => true, 'cache_ttl' => 3600, // 1 hour 'auto_sync_threshold' => 300, // 5 minutes ], 'category' => [ 'data_source_mode' => env('CATEGORY_DATA_SOURCE_MODE', 'hybrid'), 'sync_enabled' => true, 'conflict_resolution' => 'timestamp', // 'timestamp', 'api_wins', 'db_wins' ], ], 'sync_options' => [ 'batch_size' => 100, 'retry_attempts' => 3, 'retry_delay' => 1000, // milliseconds ], ];
Usage Examples
// Basic usage with hybrid mode $product = Product::find(1); // Checks database first, then API if not found // Switch modes dynamically $product = new Product(); $product->setDataSourceMode('api_first'); $allProducts = $product->all(); // Gets from API first, syncs to database // Dual sync mode - keeps both sources in sync $product = Product::find(1); $product->setDataSourceMode('dual_sync'); $product->name = 'Updated Product'; $product->save(); // Saves to both API and database automatically // Advanced conflict resolution in dual_sync mode $product = Product::find(1); // Automatically compares timestamps and chooses most recent data // Syncs both sources to maintain consistency
High-Performance Caching
The package includes a sophisticated multi-layer caching system with Redis support:
// config/high-performance-cache.php return [ 'enabled' => env('HIGH_PERFORMANCE_CACHE_ENABLED', true), 'driver' => env('HIGH_PERFORMANCE_CACHE_DRIVER', 'redis'), 'redis' => [ 'connection' => env('HIGH_PERFORMANCE_CACHE_REDIS_CONNECTION', 'default'), 'prefix' => env('HIGH_PERFORMANCE_CACHE_PREFIX', 'api_model_hp:'), 'serializer' => 'igbinary', // 'php', 'igbinary', 'json' ], 'ttl' => [ 'default' => env('HIGH_PERFORMANCE_CACHE_TTL', 3600), 'models' => [ 'product' => 7200, // 2 hours 'category' => 86400, // 24 hours ], ], 'warming' => [ 'enabled' => env('CACHE_WARMING_ENABLED', true), 'batch_size' => 100, 'concurrent_requests' => 5, ], 'invalidation' => [ 'strategy' => 'tag_based', // 'tag_based', 'key_pattern', 'manual' 'auto_invalidate_on_update' => true, ], ]; // Usage in models class Product extends ApiModel { protected $cacheProfile = 'high_performance'; protected $cacheTags = ['products', 'catalog']; protected $cacheTtl = 7200; // Override default TTL // Enable cache warming for this model protected $enableCacheWarming = true; } // Advanced caching operations $product = Product::withCache('products:featured') ->where('featured', true) ->remember(3600) // Cache for 1 hour ->get(); // Cache warming Product::warmCache(['featured' => true], 100); // Warm cache for featured products // Cache invalidation Product::invalidateCache(['products', 'catalog']); // Invalidate by tags Product::find(1)->invalidateModelCache(); // Invalidate specific model cache
Custom Response Transformers
You can transform API responses before they're converted to models:
use MTechStack\LaravelApiModelClient\Transformers\AbstractResponseTransformer; class CustomProductTransformer extends AbstractResponseTransformer { public function transform($response) { $data = json_decode($response->getBody()->getContents(), true); // Transform the data if (isset($data['products'])) { return $data['products']; } return $data; } } // Register your transformer app()->bind('api-model-relations.transformers.product', function() { return new CustomProductTransformer(); }); // Use your transformer in your model class Product extends ApiModel { protected $apiEndpoint = 'products'; protected $responseTransformer = 'product'; // ApiModel already includes all necessary traits automatically // No need to manually add SyncWithApi or other traits }
API Mocking
For testing, you can mock API responses:
use MTechStack\LaravelApiModelClient\Testing\MocksApiResponses; use Tests\TestCase; class ProductTest extends TestCase { use MocksApiResponses; public function testGetProducts() { // Mock a response for GET /products $this->mockApiResponse('GET', 'products', [ 'data' => [ ['id' => 1, 'name' => 'Product 1', 'price' => 99.99], ['id' => 2, 'name' => 'Product 2', 'price' => 149.99], ] ]); // Now when Product::all() is called, it will use the mocked response $products = Product::all(); $this->assertCount(2, $products); $this->assertEquals('Product 1', $products[0]->name); } public function testCreateProduct() { // Mock a response for POST /products $this->mockApiResponse('POST', 'products', [ 'id' => 3, 'name' => 'New Product', 'price' => 199.99 ]); $product = Product::create([ 'name' => 'New Product', 'price' => 199.99 ]); $this->assertEquals(3, $product->id); $this->assertEquals('New Product', $product->name); } }
Performance Optimization
Tips for optimizing performance:
// Eager load relationships to reduce API calls $products = Product::with('category', 'reviews')->get(); // Select only the fields you need $products = Product::select(['id', 'name', 'price'])->get(); // Use pagination for large datasets $products = Product::paginate(20); // Use caching effectively $products = Product::withCacheTtl(3600)->get(); // Cache for 1 hour // Batch operations when possible $products = Product::whereIn('id', [1, 2, 3, 4, 5])->get(); // Use custom endpoints for specific operations $featuredProducts = Product::withEndpoint('products/featured')->get();
Troubleshooting
Common issues and solutions:
API Connection Issues
// Enable debug mode to see detailed request/response information config(['api-model-relations.debug' => true]); // Check the last request and response $lastRequest = ApiClient::getLastRequest(); $lastResponse = ApiClient::getLastResponse(); // Log all API requests and responses Event::listen(ApiRequestEvent::class, function ($event) { logger()->debug('API Request', [ 'method' => $event->method, 'endpoint' => $event->endpoint, 'options' => $event->options ]); }); Event::listen(ApiResponseEvent::class, function ($event) { logger()->debug('API Response', [ 'status' => $event->statusCode, 'body' => (string) $event->response->getBody() ]); });
Caching Issues
// Clear all cache \Illuminate\Support\Facades\Cache::store(config('api-model-relations.cache.store'))->flush(); // Disable caching temporarily config(['api-model-relations.cache.enabled' => false]); // Debug cache keys logger()->debug('Cache key: ' . Product::where('id', 1)->getCacheKey());
π€ Contributing
We welcome contributions from the community! Laravel API Model Client is an open-source project that thrives on community involvement.
π Ways to Contribute
- π Bug Reports - Found a bug? Open an issue
- π‘ Feature Requests - Have an idea? Start a discussion
- π Documentation - Help improve our docs and examples
- π§ Code Contributions - Submit pull requests for bug fixes and features
- π§ͺ Testing - Help expand our test coverage
- π Translations - Help translate documentation
π Development Setup
# Clone the repository git clone https://github.com/mabou7agar/laravel-api-model-client.git cd laravel-api-model-client # Install dependencies composer install # Run tests vendor/bin/phpunit # Run code style checks vendor/bin/php-cs-fixer fix --dry-run --diff
π§ͺ Testing Guidelines
- 96%+ test coverage - Maintain our high testing standards
- All test suites must pass - Unit, Integration, and Performance tests
- Add tests for new features - Every new feature needs corresponding tests
- Follow existing patterns - Keep consistency with existing test structure
π Pull Request Process
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Write tests for your changes
- Ensure all tests pass (
vendor/bin/phpunit
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to your branch (
git push origin feature/amazing-feature
) - Open a Pull Request
π Contributors
Thanks to all our amazing contributors! π
π Community & Support
- π¬ GitHub Discussions - Ask questions and share ideas
- π Issues - Report bugs and request features
- π§ Email - For security issues: security@m-tech-stack.com
π Acknowledgments
Special thanks to:
- Laravel Community - For the amazing framework and ecosystem
- OpenAPI Initiative - For the excellent API specification standard
- All Contributors - For making this package better every day
- Early Adopters - For testing and providing valuable feedback
π License
The MIT License (MIT). Please see License File for more information.
Made with β€οΈ by M-Tech Stack
β Star us on GitHub if this package helped you!