You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel9通过XMLRPC对接Odoo:认证成功但无法获取工单任务数据

Laravel 9对接Odoo XML-RPC获取模型数据失败排查

使用Laravel 9.46版本,通过phpxmlrpc/phpxmlrpc ^4.10包对接Odoo XML-RPC接口,已成功完成认证,但调用project.task、helpdesk.ticket等模型的search_read方法时始终报错。以下是原始实现代码,需排查并修复问题:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use PhpXmlRpc\Client;
use PhpXmlRpc\Request;
use PhpXmlRpc\Value;
use DB;

class FetchOdooData extends Command
{
    protected $signature = 'fetch:odoo-data';
    protected $description = 'Fetch data from Odoo and update local database';

    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        // Odoo credentials
        $url = 'http://urltoodoo::8049';
        $db = 'DBNAME';
        $username = 'username';
        $password = 'APIKEY';

        // Define XML-RPC endpoints
        $common_url = "{$url}/xmlrpc/2/common";
        $object_url = "{$url}/xmlrpc/2/object";

        // Create a client instance for common services
        $client = new Client($common_url);

        // Prepare the authentication request
        $request = new Request('authenticate', array(
            new Value($db, 'string'),
            new Value($username, 'string'),
            new Value($password, 'string'),
            new Value(array(), 'struct')
        ));

        // Send the authentication request
        $response = $client->send($request);

        if ($response->faultCode()) {
            $this->error('Error: ' . $response->faultString());
            return;
        }

        // Retrieve the user ID from the response
        $user_id = $response->value()->scalarval();

        if ($user_id) {
            $this->info("Authenticated successfully. User ID: $user_id");
        } else {
            $this->error("Authentication failed.");
            return;
        }

        // Function to call Odoo methods
        function callOdooMethod($client, $method, $params) {
            var_dump($method);
            var_dump($params);
            var_dump($client);
            
            $msg = new Request($method, array(new Value($params, 'array')));

            var_dump($msg);

            $resp = $client->send($msg);
            
            var_dump($resp);

            if ($resp->faultCode()) {
                return 'Error: ' . $resp->faultString();
            }
            return $resp->value();
        }

        // Fetch last sync time for a model
        function getLastSyncTime($model_name) {
            return DB::table('last_sync')->where('model_name', $model_name)->value('last_update');
        }

        // Update last sync time for a model
        function updateLastSyncTime($model_name) {
            DB::table('last_sync')->updateOrInsert(
                ['model_name' => $model_name],
                ['last_update' => now()]
            );
        }

        // Fetch data from Odoo
        function fetchData($models, $db, $user_id, $password, $model_name, $fields, $last_sync_time=0) {
            //$domain = $last_sync_time ? [['write_date', '>', $last_sync_time]] : [];
            $params = array(
                new Value($db, 'string'),
                new Value($user_id, 'int'),
                new Value($password, 'string'),
                new Value($model_name, 'string'),
                new Value('search_read', 'string'),
                new Value(array(
                    //new Value($domain, 'array'), // Domain filter
                    new Value($fields, 'array'), // Fields to retrieve
                ), 'array')
            );

            return callOdooMethod($models, 'execute_kw', $params);
        }

        // Update local database with fetched data
        function updateLocalDatabase($model_name, $data) {
            foreach ($data as $record) {

            }
        }

        // Create a client instance for object services
        $models = new Client($object_url);

        var_dump($models);

        // Define fields to retrieve for each model
        $project_fields = array('name', 'user_id', 'date_start', 'date_end', 'tasks');
        $task_fields = array('name', 'user_id', 'date_deadline', 'stage_id', 'description');
        $ticket_fields = array('name', 'stage_id', 'user_id', 'priority', 'description');
        $incident_fields = array('name', 'stage_id', 'user_id', 'priority', 'description'); // Replace with actual fields

        // Define models
        $models_to_fetch = array(
            'project.project' => $project_fields,
            'project.task' => $task_fields,
            'helpdesk.ticket' => $ticket_fields
        );

        foreach ($models_to_fetch as $model_name => $fields) {
            //$last_sync_time = getLastSyncTime($model_name);
            $data = fetchData($models, $db, $user_id, $password, $model_name, $fields);

            if (is_string($data)) {
                $this->error("Error fetching $model_name: " . $data);
            } else {
                $this->info(ucfirst(str_replace('.', ' ', $model_name)) . " Data:\n");
                print_r($data);
                updateLocalDatabase($model_name, $data);
                updateLastSyncTime($model_name);
            }
        }
    }
}

核心问题分析与修复方案

1. URL格式错误

原始代码中$url = 'http://urltoodoo::8049';包含多余的冒号,导致请求地址无效,修正为:

$url = 'http://urltoodoo:8049';

2. Odoo execute_kw参数结构错误

Odoo的execute_kw要求参数格式为:[db, uid, password, model_name, method_name, [method_args...]],原始代码错误嵌套了多层Value对象,导致Odoo无法解析参数。

修复fetchData方法:

private function fetchData($models, $db, $user_id, $password, $model_name, $fields, $last_sync_time=0) {
    $domain = $last_sync_time ? [['write_date', '>', $last_sync_time]] : [];
    
    // 将字段数组转换为XmlRpc Value数组
    $xmlrpc_fields = array_map(function($field) {
        return new Value($field, 'string');
    }, $fields);
    
    // 将domain转换为XmlRpc Value结构
    $xmlrpc_domain = [];
    foreach ($domain as $condition) {
        $xmlrpc_condition = array_map(function($val) {
            return is_string($val) ? new Value($val, 'string') : new Value($val, 'int');
        }, $condition);
        $xmlrpc_domain[] = new Value($xmlrpc_condition, 'array');
    }
    
    // 构造execute_kw的正确参数
    $params = array(
        new Value($db, 'string'),
        new Value($user_id, 'int'),
        new Value($password, 'string'),
        new Value($model_name, 'string'),
        new Value('search_read', 'string'),
        // search_read的参数:domain, fields
        new Value(
            array(
                new Value($xmlrpc_domain, 'array'),
                new Value($xmlrpc_fields, 'array')
            ),
            'array'
        )
    );

    return $this->callOdooMethod($models, 'execute_kw', $params);
}

3. callOdooMethod参数包装错误

原始代码将参数数组额外包装了一层Value对象,导致结构完全错误,修正后直接传入参数数组:

private function callOdooMethod($client, $method, $params) {
    $msg = new Request($method, $params);
    $resp = $client->send($msg);
    
    if ($resp->faultCode()) {
        return 'Error: ' . $resp->faultString();
    }
    return $resp->value()->scalarval();
}

4. 内部函数作用域问题

在handle方法内定义的内部函数无法直接访问Laravel门面,需将函数改为类的私有方法:

// 将所有内部函数改为类私有方法,调用时使用$this
private function getLastSyncTime($model_name) {
    return DB::table('last_sync')->where('model_name', $model_name)->value('last_update');
}

private function updateLastSyncTime($model_name) {
    DB::table('last_sync')->updateOrInsert(
        ['model_name' => $model_name],
        ['last_update' => now()]
    );
}

private function updateLocalDatabase($model_name, $data) {
    foreach ($data as $record) {
        // 实现本地数据库更新逻辑
    }
}

5. 权限验证

确保Odoo中使用的API账号拥有project.task、helpdesk.ticket等模型的读取权限,可在Odoo后台用户权限设置中检查调整。


内容的提问来源于stack exchange,提问作者Osama Ahmed Momin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 11:52:03