wjbecker / current-rms-php
A framework-agnostic PHP library for the Current RMS API with fluent query builder, pagination, and collections
Installs: 3
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/wjbecker/current-rms-php
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.9
Requires (Dev)
- illuminate/support: ^11.0|^12.0
- mockery/mockery: ^1.6
- pestphp/pest: ^3.0|^4.0
Suggests
- illuminate/support: Required for Laravel integration (^11.0|^12.0)
This package is auto-updated.
Last update: 2026-01-09 15:32:24 UTC
README
A framework-agnostic PHP library for interacting with the Current RMS API. Provides both a flexible generic HTTP client and typed endpoint classes for common operations.
composer require wjbecker/current-rms-php
Features
- Framework Agnostic: Works standalone or with Laravel (zero required dependencies)
- Fluent Query Builder: Build queries with chainable methods instead of arrays
- Memory-Efficient Pagination: Generator-based cursor for processing large datasets
- Custom Collection Class: Lightweight collection with map, filter, reduce, and more
- Paginator with Navigation: Page metadata with next/previous navigation
- Type-Safe DTOs: Strongly-typed data objects with helper methods
- Guzzle HTTP Client: Built on Guzzle for reliable HTTP requests
- Laravel Integration: Optional service provider with auto-discovery and facade
Installation
composer require wjbecker/current-rms-php
Quick Start
Standalone Usage (No Laravel)
use Wjbecker\CurrentRms\Client\Auth\ApiKeyAuth; use Wjbecker\CurrentRms\Client\CurrentRmsClient; // Create authentication $auth = new ApiKeyAuth( subdomain: 'yourcompany', apiToken: 'your-api-token-here' ); // Create client $client = new CurrentRmsClient( baseUrl: 'https://api.current-rms.com/api/v1', auth: $auth ); // Use the client $opportunities = $client->opportunities()->list(); $opportunity = $client->opportunities()->find(123);
Laravel Usage
1. Publish Configuration (Optional)
php artisan vendor:publish --tag=current-rms-config
2. Configure Environment
Add to your .env file:
CURRENT_RMS_API_BASE_URL=https://api.current-rms.com/api/v1 CURRENT_RMS_AUTH_TYPE=api_key CURRENT_RMS_SUBDOMAIN=yourcompany CURRENT_RMS_API_TOKEN=your-api-token-here
3. Use the Client
Via Dependency Injection:
use Wjbecker\CurrentRms\Client\CurrentRmsClient; class OpportunityController extends Controller { public function __construct( private CurrentRmsClient $client ) {} public function index() { $opportunities = $this->client->opportunities()->list(); return view('opportunities.index', compact('opportunities')); } }
Via Facade:
use Wjbecker\CurrentRms\Facades\CurrentRms; $opportunities = CurrentRms::opportunities()->list(); $opportunity = CurrentRms::opportunities()->find(123);
Query Builder
Build queries with a fluent interface instead of awkward arrays:
// Find confirmed opportunities for a specific member $orders = $client->opportunities()->query() ->whereState(3) // Confirmed ->forMember(456) ->createdAfter('2025-01-01') ->with('member', 'venue') ->get(); // Search by subject $results = $client->opportunities()->query() ->whereContains('subject', 'Wedding') ->whereBetween('starts_at', '2025-06-01', '2025-08-31') ->get(); // Filter opportunity items $rentalItems = $client->opportunityItems()->query() ->whereEquals('transaction_type', 1) // Rentals ->forOpportunity(123) ->with('item') ->get(); // Complex queries with Ransack predicates $items = $client->opportunityItems()->query() ->where('quantity', 'gteq', 5) // quantity >= 5 ->where('item_id', 'in', [1, 2, 3]) // item_id in array ->whereNotNull('charge_total') ->get();
Available Query Methods
// Comparison operators ->whereEquals('field', $value) ->whereNotEquals('field', $value) ->whereGreaterThan('field', $value) ->whereGreaterThanOrEqual('field', $value) ->whereLessThan('field', $value) ->whereLessThanOrEqual('field', $value) // String matching ->whereContains('field', 'value') // LIKE %value% ->whereContainsAll('field', ['a', 'b']) // Contains ALL values (AND) ->whereContainsAny('field', ['a', 'b']) // Contains ANY value (OR) ->whereNotContains('field', 'value') // Does NOT contain ->whereStartsWith('field', 'value') ->whereNotStartsWith('field', 'value') ->whereEndsWith('field', 'value') ->whereNotEndsWith('field', 'value') ->whereMatches('field', '%pattern%') // SQL LIKE pattern ->whereNotMatches('field', '%pattern%') // Array/Null/Presence checks ->whereIn('field', [1, 2, 3]) ->whereNotIn('field', [1, 2, 3]) ->whereNull('field') ->whereNotNull('field') ->wherePresent('field') // Not null AND not blank ->whereBlank('field') // Null OR empty string ->whereTrue('field') ->whereFalse('field') // Date ranges ->whereBetween('field', $start, $end) ->createdAfter('2025-01-01') ->createdBefore('2025-12-31') ->createdBetween('2025-01-01', '2025-12-31') ->updatedAfter('2025-01-01') // Convenience methods ->whereState(3) // Filter by state ->forMember($memberId) // Filter by member ->forOpportunity($id) // Filter by opportunity ->forItem($itemId) // Filter by item // Includes and pagination ->with('item', 'member') // Include associations ->perPage(50) // Set page size
Complex Queries with AND/OR Logic
For more complex queries, use whereOr() and whereAnd() to group conditions:
// Simple OR: name contains "Bill" OR name contains "Fred" $results = $client->opportunities()->query() ->whereOr(function ($or) { $or->whereContains('name', 'Bill'); $or->whereContains('name', 'Fred'); }) ->get(); // Simple AND: state = 3 AND member_id = 123 (grouped) $results = $client->opportunities()->query() ->whereAnd(function ($and) { $and->whereEquals('state', 3); $and->whereEquals('member_id', 123); }) ->get(); // Complex: (name = "Bill" AND active = true) OR (name = "Fred" AND active = true) $results = $client->opportunities()->query() ->whereOr(function ($or) { $or->group(function ($g) { $g->whereEquals('name', 'Bill'); $g->whereTrue('active'); }); $or->group(function ($g) { $g->whereEquals('name', 'Fred'); $g->whereTrue('active'); }); }) ->get(); // Combine regular filters with grouped conditions $results = $client->opportunities()->query() ->whereState(3) // Regular filter (always applied) ->whereOr(function ($or) { $or->whereContains('subject', 'Wedding'); $or->whereContains('subject', 'Corporate'); }) ->with('member') ->get(); // Date range OR queries $results = $client->opportunities()->query() ->whereOr(function ($or) { $or->group(function ($g) { $g->whereBetween('starts_at', '2025-01-01', '2025-06-30'); }); $or->group(function ($g) { $g->whereBetween('starts_at', '2025-07-01', '2025-12-31'); }); }) ->get();
How it works:
- Each
where*call insidewhereOr()/whereAnd()creates a separate condition group - Groups are combined with OR (for
whereOr) or AND (forwhereAnd) - Use
group()to bundle multiple conditions that should be ANDed together within a group - Regular filters (outside of whereOr/whereAnd) are always applied in addition to grouped conditions
Query Execution Methods
// Get a Collection of results $results = $query->get(); // Get just the first result $item = $query->first(); // Get paginated results with metadata $page = $query->paginate(1); // Iterate through all pages (memory-efficient) foreach ($query->cursor() as $item) { // Process one item at a time } // Check existence $exists = $query->exists(); // Get count (requires API call) $count = $query->count();
Pagination
Basic Pagination
// Get a specific page $page = $client->opportunities()->paginate(page: 1, perPage: 25); // Access items on current page foreach ($page->items() as $opportunity) { echo $opportunity->subject; } // Check pagination metadata echo "Page {$page->currentPage()} of {$page->lastPage()}"; echo "Total items: {$page->total()}"; echo "Has more pages: " . ($page->hasMorePages() ? 'yes' : 'no');
Page Navigation
// Navigate between pages $nextPage = $page->nextPage(); $prevPage = $page->previousPage(); $specificPage = $page->goToPage(5);
Memory-Efficient Iteration (Generators)
For large datasets, use the cursor() method which yields items one at a time:
// Process thousands of items without loading all into memory foreach ($client->opportunityItems()->cursor() as $item) { echo "{$item->getItemName()} - Qty: {$item->quantity}\n"; // Each item is fetched as needed, pages are loaded lazily } // With filters via query builder foreach ($client->opportunityItems()->query()->forOpportunity(123)->cursor() as $item) { processItem($item); }
Pagination Limits
- Opportunities: Max 25 items per page
- Other Endpoints: Max 100 items per page (default)
Collections
The package includes a lightweight Collection class:
$opportunities = $client->opportunities()->list(); // Filtering $confirmed = $opportunities->filter(fn($o) => $o->state === 3); $drafts = $opportunities->where('state', 1); // Mapping $titles = $opportunities->map(fn($o) => $o->getTitle()); $subjects = $opportunities->pluck('subject'); // Aggregation $total = $opportunities->sum('charge_total'); $average = $opportunities->avg('charge_total'); // Iteration $opportunities->each(function($o) { echo $o->subject; }); // Sorting $sorted = $opportunities->sortBy('starts_at'); // Other operations $first = $opportunities->first(); $last = $opportunities->last(); $count = $opportunities->count(); $chunk = $opportunities->take(5); // Convert to array $array = $opportunities->toArray();
Lazy Loading Relationships
DTOs returned from endpoints support lazy loading of related resources:
// Get opportunities $opportunities = $client->opportunities()->list(); foreach ($opportunities as $opportunity) { // Lazy load items for each opportunity (makes API call) $items = $opportunity->items()->get(); foreach ($items as $item) { echo "{$item->name} - Qty: {$item->quantity}\n"; } }
How It Works
When you retrieve data through endpoints (list(), find(), create(), etc.), the client reference is automatically injected into the DTOs. This enables the items() method to make API calls on your behalf.
// These all support lazy loading $opportunity = $client->opportunities()->find(123); $opportunity = $client->opportunities()->list()->first(); $opportunity = $client->opportunities()->query()->whereState(3)->first(); // Access items without knowing the opportunity ID $items = $opportunity->items()->get(); $items = $opportunity->items()->query()->whereEquals('transaction_type', 1)->get();
When Lazy Loading is NOT Available
If you create a DTO manually without the client, lazy loading will throw an exception:
// Manual creation - no client available $opportunity = OpportunityData::from(['id' => 123]); $opportunity->items(); // Throws RuntimeException // Use the explicit endpoint instead $items = $client->opportunities()->items(123)->list();
API Reference
Opportunities Endpoint
// List all opportunities $opportunities = $client->opportunities()->list(); // List with filters (legacy array syntax) $opportunities = $client->opportunities()->list([ 'q[state_eq]' => 1 ]); // Find specific opportunity $opportunity = $client->opportunities()->find(123); // Find with includes $opportunity = $client->opportunities()->find(123, ['owner', 'member']); // Create opportunity $opportunity = $client->opportunities()->create([ 'subject' => 'New Project', 'member_id' => 456, 'starts_at' => '2025-01-15T08:00:00.000Z', 'ends_at' => '2025-01-16T18:00:00.000Z', ]); // Update opportunity $opportunity = $client->opportunities()->update(123, [ 'subject' => 'Updated Project Name' ]); // Delete opportunity $client->opportunities()->destroy(123); // Checkout (convert to order) $opportunity = $client->opportunities()->checkout([ 'opportunity_id' => 123 ]); // Clone opportunity $newOpportunity = $client->opportunities()->clone(123); // Finalize check-in $opportunity = $client->opportunities()->finalizeCheckIn(123, [ 'return' => [ 'return_at' => '2025-01-15T18:00:00.000Z' ], 'move_outstanding' => false, 'complete_sales_items' => false, ]);
Opportunity Items Endpoint
// List all opportunity items $items = $client->opportunityItems()->list(); // List with includes (item data, assets, etc.) $items = $client->opportunityItems()->list([], ['item', 'item_assets']); // List items for specific opportunity (scoped) $items = $client->opportunities()->items(123)->list(); // Find specific item $item = $client->opportunities()->items(123)->find(456); // Find with includes $item = $client->opportunities()->items(123)->find(456, ['item', 'rate_definition']); // Create item $item = $client->opportunities()->items(123)->create([ 'item_id' => 789, 'quantity' => 5 ]); // Update item $item = $client->opportunities()->items(123)->update(456, [ 'quantity' => 10 ]); // Delete item $client->opportunities()->items(123)->destroy(456);
Generic HTTP Methods
// GET request $data = $client->get('/endpoint', ['param' => 'value']); // POST request $data = $client->post('/endpoint', ['key' => 'value']); // PUT request $data = $client->put('/endpoint', ['key' => 'value']); // PATCH request $data = $client->patch('/endpoint', ['key' => 'value']); // DELETE request $client->delete('/endpoint');
Configuration Options
When creating the client manually:
$client = new CurrentRmsClient( baseUrl: 'https://api.current-rms.com/api/v1', // required auth: $auth, // optional timeout: 30, // optional, default: 30 connectTimeout: 10, // optional, default: 10 verifySsl: true // optional, default: true );
Data Objects
All endpoints return strongly-typed Data Transfer Objects (DTOs) with helper methods.
OpportunityData
// Properties (all nullable) $opportunity->id; $opportunity->subject; $opportunity->state; // State ID (1=draft, 2=quote, 3=order, etc.) $opportunity->state_name; // "Draft", "Quote", "Order", etc. $opportunity->status; $opportunity->starts_at; $opportunity->ends_at; $opportunity->charge_total; $opportunity->opportunity_items; // Array of OpportunityItemData (if included) // Helper methods $opportunity->isDraft(); // Check if in draft state $opportunity->isOpen(); // Check if status is open $opportunity->getTitle(); // Get subject/title $opportunity->getMemberName(); // Get customer name from nested data $opportunity->getOwnerName(); // Get owner name from nested data $opportunity->getCustomField('key'); // Get custom field value // Lazy loading (when retrieved via endpoint) $opportunity->items(); // Get ScopedOpportunityItemsEndpoint $opportunity->items()->get(); // Fetch all items $opportunity->items()->query()->whereEquals('transaction_type', 1)->get();
OpportunityItemData
// Properties (all nullable) $item->id; $item->opportunity_id; $item->item_id; // Product/Item ID $item->transaction_type; // 1=Rental, 2=Sale, 3=Service $item->quantity; $item->name; $item->charge; $item->item; // ItemData object (if included) // Helper methods $item->isRental(); // Check if rental (transaction_type === 1) $item->isSale(); // Check if sale (transaction_type === 2) $item->isService(); // Check if service (transaction_type === 3) $item->getItemName(); // Get item name (from item or name field) $item->getItemBarcode(); // Get barcode (from item or sku field) $item->getCustomField('key'); // Get custom field value
ItemData (Product/Item)
// Available when including 'item' association $item = $opportunityItem->item; // Properties $item->id; $item->name; $item->description; $item->barcode; $item->active; $item->replacement_charge; $item->weight; // Helper methods $item->isActive(); $item->isAccessoryOnly(); $item->isDiscountable(); $item->getProductGroupName(); $item->getTaxClassName(); $item->getIconUrl(); $item->getCustomField('key');
Testing
./vendor/bin/pest
Package Structure
.
├── src/
│ ├── Client/
│ │ ├── CurrentRmsClient.php
│ │ ├── Auth/
│ │ │ ├── AuthManager.php
│ │ │ ├── ApiKeyAuth.php
│ │ │ └── TokenStorage.php
│ │ └── Exceptions/
│ ├── Data/
│ │ ├── OpportunityData.php
│ │ ├── OpportunityItemData.php
│ │ └── ItemData.php
│ ├── Endpoints/
│ │ ├── BaseEndpoint.php
│ │ ├── OpportunitiesEndpoint.php
│ │ ├── OpportunityItemsEndpoint.php
│ │ └── ScopedOpportunityItemsEndpoint.php
│ ├── Query/
│ │ ├── QueryBuilder.php
│ │ └── GroupBuilder.php
│ ├── Support/
│ │ ├── Collection.php
│ │ └── Paginator.php
│ ├── Facades/
│ │ └── CurrentRms.php
│ └── CurrentRmsServiceProvider.php
├── config/
│ └── current-rms.php
├── tests/
├── LICENSE
├── README.md
└── composer.json
License
MIT License. See LICENSE for more information.
Credits
- Author: William Becker
- Package: wjbecker/current-rms-php