oscar-team / odoo-json2
Modern PHP client library for Odoo JSON-2 API (Odoo 19+)
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/oscar-team/odoo-json2
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.8
Requires (Dev)
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0
This package is not auto-updated.
Last update: 2025-12-11 13:56:21 UTC
README
A modern PHP client library for interacting with Odoo's JSON-2 API (Odoo 19+). This library provides a clean, type-safe interface for performing CRUD operations and calling custom methods on Odoo models.
Features
- ✅ Modern JSON-2 API support (Odoo 19+)
- ✅ API key-based authentication
- ✅ Full CRUD operations (Create, Read, Update, Delete)
- ✅ Type-safe with PHP 8.2+
- ✅ Comprehensive error handling
- ✅ Built on Guzzle HTTP client
- ✅ Clean, Laravel-style code structure
Requirements
- PHP 8.2 or higher
- Composer
- Odoo 19 or higher with JSON-2 API enabled
Installation
composer require oscar-team/odoo-json2
Laravel Integration
This package is designed to work seamlessly with Laravel. The service provider is automatically discovered, so no manual registration is required.
Publishing Configuration
After installing the package, publish the configuration file:
php artisan vendor:publish --provider="OdooJson2\OdooServiceProvider" --tag="config"
This will create a config/odoo.php file in your Laravel application.
Environment Configuration
Add the following environment variables to your .env file:
ODOO_HOST=https://your-odoo-instance.com
ODOO_DATABASE=your-database-name
ODOO_API_KEY=your-api-key-here
ODOO_SSL_VERIFY=true
# Optional context settings
ODOO_LANG=en_US
ODOO_TIMEZONE=UTC
ODOO_COMPANY_ID=1
Using the Odoo Client in Laravel
The package automatically registers the Odoo class as a singleton, so you can use it via dependency injection:
use OdooJson2\Odoo; class YourController extends Controller { public function __construct( private Odoo $odoo ) {} public function index() { $partners = $this->odoo->search('res.partner', []); return response()->json($partners); } }
Or resolve it from the service container:
use OdooJson2\Odoo; $odoo = app(Odoo::class); $partners = $odoo->search('res.partner', []);
Using Models in Laravel
Models are automatically booted when the service provider loads. You can use them directly:
use App\Models\Partner; // Your Odoo model class PartnerController extends Controller { public function index() { $partners = Partner::query() ->where('is_company', '=', true) ->get(); return response()->json($partners); } }
Configuration File
The published configuration file (config/odoo.php) contains the following options:
host- Your Odoo instance URL (required)database- Database name (optional, can be determined from API key)api_key- Your Odoo API key (required)ssl_verify- Whether to verify SSL certificates (default: true)context- Additional context settings (lang, timezone, companyId)
All values can be overridden via environment variables.
Configuration
Getting Your API Key
- Log in to your Odoo instance
- Go to Settings → Users & Companies → API Keys
- Create a new API key
- Copy the generated API key
Usage
Basic Setup
use OdooJson2\OdooClient; $client = new OdooClient( baseUrl: 'https://your-odoo-instance.com', apiKey: 'your-api-key-here', database: 'your-database-name' );
Search Records
// Search with domain $partners = $client->search('res.partner', [ ['name', '=', 'John Doe'] ]); // Search with options $partners = $client->search('res.partner', [ ['is_company', '=', true] ], [ 'limit' => 10, 'offset' => 0, 'order' => 'name asc' ]);
Read Records
// Read specific records $partners = $client->read('res.partner', [1, 2, 3]); // Read with specific fields $partners = $client->read('res.partner', [1, 2, 3], [ 'name', 'email', 'phone' ]);
Search and Read
// Search and read in one call $partners = $client->searchRead('res.partner', [ ['is_company', '=', true] ], [ 'fields' => ['name', 'email', 'phone'], 'limit' => 10 ]);
Create Records
// Create a single record $partnerId = $client->create('res.partner', [ 'name' => 'John Doe', 'email' => 'john@example.com', 'phone' => '+1234567890', 'is_company' => false ]); // Create multiple records $partnerIds = $client->create('res.partner', [ [ 'name' => 'Company A', 'is_company' => true, ], [ 'name' => 'Company B', 'is_company' => true, ], ]);
Update Records
// Update records $success = $client->write('res.partner', [1, 2], [ 'email' => 'newemail@example.com', 'phone' => '+9876543210' ]);
Delete Records
// Delete records $success = $client->unlink('res.partner', [1, 2, 3]);
Count Records
// Count records matching a domain $count = $client->count('res.partner', [ ['is_company', '=', true] ]);
Get Model Fields
// Get all fields of a model $fields = $client->fieldsGet('res.partner'); // Get specific field attributes $fields = $client->fieldsGet('res.partner', ['type', 'required', 'string']);
Custom Method Calls
// Call any custom method on a model $result = $client->call('sale.order', 'action_confirm', [ 'ids' => [123], 'context' => [] ]);
Error Handling
use OdooJson2\Exceptions\OdooException; use OdooJson2\Exceptions\AuthenticationException; use OdooJson2\Exceptions\ConnectionException; try { $partners = $client->search('res.partner', []); } catch (AuthenticationException $e) { // Handle authentication errors echo "Authentication failed: " . $e->getMessage(); } catch (ConnectionException $e) { // Handle connection errors echo "Connection failed: " . $e->getMessage(); } catch (OdooException $e) { // Handle other Odoo errors echo "Odoo error: " . $e->getMessage(); }
Advanced Configuration
// Custom HTTP client options $client = new OdooClient( baseUrl: 'https://your-odoo-instance.com', apiKey: 'your-api-key-here', database: 'your-database-name', options: [ 'timeout' => 60, 'verify' => false, // Only for development 'proxy' => 'http://proxy.example.com:8080', ] ); // Use custom HTTP client $customClient = new \GuzzleHttp\Client([ 'timeout' => 120, ]); $client->setHttpClient($customClient);
Domain Syntax
The domain syntax follows Odoo's standard format:
// Simple condition [['field', '=', 'value']] // Multiple conditions (AND) [ ['field1', '=', 'value1'], ['field2', '!=', 'value2'] ] // OR conditions ['|', ['field1', '=', 'value1'], ['field2', '=', 'value2']] // Operators: =, !=, <, >, <=, >=, like, ilike, in, not in, child_of, parent_of
Examples
Working with Sales Orders
// Create a sales order $orderId = $client->create('sale.order', [ 'partner_id' => 1, 'order_line' => [ [ 'product_id' => 5, 'product_uom_qty' => 10, 'price_unit' => 100.00, ], ], ]); // Confirm the order $client->call('sale.order', 'action_confirm', [ 'ids' => [$orderId] ]); // Search for confirmed orders $orders = $client->searchRead('sale.order', [ ['state', '=', 'sale'] ], [ 'fields' => ['name', 'partner_id', 'amount_total'], 'limit' => 20 ]);
Working with Products
// Create a product $productId = $client->create('product.product', [ 'name' => 'New Product', 'type' => 'product', 'categ_id' => 1, 'list_price' => 99.99, 'standard_price' => 50.00, ]); // Update product price $client->write('product.product', [$productId], [ 'list_price' => 89.99 ]); // Search products by category $products = $client->searchRead('product.product', [ ['categ_id', '=', 1], ['sale_ok', '=', true] ], [ 'fields' => ['name', 'list_price', 'qty_available'], 'order' => 'list_price desc' ]);
Eloquent-like Models
This library provides an Eloquent-like ORM for working with Odoo models, making it easy to define models with relationships and perform type-safe operations.
Basic Model Definition
use OdooJson2\Attributes\Field; use OdooJson2\Attributes\Model; use OdooJson2\Odoo\OdooModel; #[Model('res.partner')] class Partner extends OdooModel { #[Field] public string $name; #[Field('email')] public ?string $email; #[Field('phone')] public ?string $phone; #[Field('is_company')] public bool $isCompany = false; }
Field Attributes
Use the #[Field] attribute to map Odoo fields to PHP properties:
#[Model('product.product')] class Product extends OdooModel { // Field name matches property name #[Field] public string $name; // Custom field name mapping #[Field('default_code')] public ?string $defaultCode; #[Field('list_price')] public ?float $listPrice; #[Field('standard_price')] public ?float $standardPrice; }
Many-to-One Relationships (BelongsTo)
Use #[Key] to extract the ID and #[BelongsTo] to define the relationship:
#[Model('product.product')] class Product extends OdooModel { #[Field] public string $name; // Extract the ID from the many-to-one field #[Field('categ_id'), Key] public ?int $categoryId; // Extract the name from the many-to-one field #[Field('categ_id'), KeyName] public ?string $categoryName; // Define the relationship #[Field('categ_id'), BelongsTo(name: 'categ_id', class: ProductCategory::class)] public ?ProductCategory $category; }
One-to-Many Relationships (HasMany)
#[Model('account.move')] class AccountMove extends OdooModel { #[Field('name')] public string $name; #[Field('partner_id'), Key] public ?int $partnerId; // One-to-many relationship #[Field('invoice_line_ids'), HasMany(class: AccountMoveLine::class, name: 'invoice_line_ids')] public ?array $invoiceLines; }
Complete Model Example
use OdooJson2\Attributes\BelongsTo; use OdooJson2\Attributes\Field; use OdooJson2\Attributes\HasMany; use OdooJson2\Attributes\Key; use OdooJson2\Attributes\KeyName; use OdooJson2\Attributes\Model; use OdooJson2\Odoo\OdooModel; #[Model('res.partner')] class Partner extends OdooModel { #[Field] public string $name; #[Field('display_name')] public ?string $displayName; #[Field('email')] public ?string $email; #[Field('phone')] public ?string $phone; #[Field('street')] public ?string $street; #[Field('city')] public ?string $city; #[Field('zip')] public ?string $zip; #[Field('country_id'), Key] public ?int $countryId; #[Field('country_id'), KeyName] public ?string $countryName; #[Field('is_company')] public bool $isCompany = false; #[Field('active')] public bool $active = true; // Self-referential relationship #[Field('parent_id'), Key] public ?int $parentId; #[Field('parent_id'), BelongsTo(name: 'parent_id', class: Partner::class)] public ?Partner $parent; #[Field('child_ids'), HasMany(class: Partner::class, name: 'child_ids')] public ?array $children; }
Initializing Models
Before using models, you need to boot the Odoo instance:
use OdooJson2\Odoo; use OdooJson2\Odoo\Config; use OdooJson2\Odoo\Context; use OdooJson2\Odoo\OdooModel; // Create Odoo instance $config = new Config( database: 'your-database', host: 'https://your-odoo-instance.com', apiKey: 'your-api-key' ); $context = new Context(); $odoo = new Odoo($config, $context); // Boot models OdooModel::boot($odoo);
Querying with Models
// Find a record by ID $partner = Partner::find(1); // Get all records $partners = Partner::all(); // Query with conditions $partners = Partner::query() ->where('is_company', '=', true) ->where('active', '=', true) ->limit(10) ->orderBy('name') ->get(); // Get first matching record $partner = Partner::query() ->where('email', '=', 'john@example.com') ->first(); // Count records $count = Partner::query() ->where('is_company', '=', true) ->count(); // Get only IDs $ids = Partner::query() ->where('active', '=', true) ->ids();
Creating Records
// Create a new partner $partner = new Partner(); $partner->name = 'John Doe'; $partner->email = 'john@example.com'; $partner->phone = '+1234567890'; $partner->isCompany = false; $partner->save(); // Or use fill method $partner = new Partner(); $partner->fill([ 'name' => 'John Doe', 'email' => 'john@example.com', 'phone' => '+1234567890', ]); $partner->save();
Updating Records
// Update an existing record $partner = Partner::find(1); $partner->email = 'newemail@example.com'; $partner->phone = '+9876543210'; $partner->save(); // Or update multiple records via query Partner::query() ->where('is_company', '=', true) ->update(['active' => false]);
Deleting Records
// Delete a single record $partner = Partner::find(1); // Note: OdooModel doesn't have a delete() method by default // Use the Odoo client directly: $odoo->unlink('res.partner', [$partner->id]); // Or delete via query Partner::query() ->where('active', '=', false) ->delete();
Working with Relationships
// Access belongs-to relationship $product = Product::find(1); $category = $product->category; // Automatically loaded // Access has-many relationship (lazy loaded) $invoice = AccountMove::find(1); $lines = $invoice->invoiceLines; // Lazy loaded when accessed // Working with self-referential relationships $partner = Partner::find(1); $parent = $partner->parent; // Parent partner $children = $partner->children; // Child partners
Advanced Queries
// Search with multiple conditions $partners = Partner::query() ->where('is_company', '=', true) ->orWhere('email', '!=', null) ->limit(50) ->offset(10) ->orderBy('name', 'desc') ->get(); // Read specific records $partners = Partner::read([1, 2, 3, 4, 5]); // Get model fields $fields = Partner::listFields();
Real-World Examples
Example 1: Create a Partner with Relationships
$partner = new Partner(); $partner->name = 'Acme Corporation'; $partner->email = 'contact@acme.com'; $partner->phone = '+1234567890'; $partner->isCompany = true; $partner->street = '123 Main St'; $partner->city = 'New York'; $partner->zip = '10001'; $partner->countryId = 233; // USA $partner->save(); echo "Created partner with ID: {$partner->id}\n";
Example 2: Find and Update Products
// Find products by category $products = Product::query() ->where('categ_id', '=', 1) ->where('sale_ok', '=', true) ->where('active', '=', true) ->get(); foreach ($products as $product) { echo "Product: {$product->name}, Price: {$product->listPrice}\n"; // Update price $product->listPrice = $product->listPrice * 1.1; // 10% increase $product->save(); }
Example 3: Working with Invoices
// Find invoices for a partner $invoices = AccountMove::query() ->where('partner_id', '=', 1) ->where('move_type', '=', 'out_invoice') ->where('state', '=', 'posted') ->get(); foreach ($invoices as $invoice) { echo "Invoice: {$invoice->name}, Total: {$invoice->amountTotal}\n"; // Access invoice lines (lazy loaded) if ($invoice->invoiceLines) { foreach ($invoice->invoiceLines as $line) { echo " - Line: {$line->name}, Amount: {$line->priceTotal}\n"; } } }
Example 4: Hierarchical Partners
// Find company partners $companies = Partner::query() ->where('is_company', '=', true) ->get(); foreach ($companies as $company) { echo "Company: {$company->name}\n"; // Access children (lazy loaded) if ($company->children) { foreach ($company->children as $child) { echo " - Contact: {$child->name} ({$child->email})\n"; } } }
Testing
# Run tests composer test # Run with coverage composer test-coverage
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This library is open-sourced software licensed under the MIT license.
Support
For issues, questions, or contributions, please open an issue on GitHub.