php 类型问题 object 问题 must be compatible with

接口

interface IAdminController
{
     const service = "111";
     // function index(): View;
     function data(): array;
     function show($id): View;
     function create(): View;
     function save(Object $request): array;
     // function update($id, AdminRequest $request): array;
     function delete($id): array;
}

其中我在 function save(Object $request): array; 这个方法这里指定了参数为object

实现类

function save(AdminRequest $request): array
{
    $data = $request->input('admin');
    $data['password'] && $data['password'] = md5($data['password']);
    $temp = $this->service->create($data);
    return parent::res($temp);
}

这就报错了。

主要的报错信息为:
xxxxx must be compatible with xxxx
应该是说我两个类型不一致。

但是我单独用
gettype($adminRequest);
来打印这个AdminRequest 显示又是object.

这个我就有点懵了。。请问

1、为什么会这样,是哪里错了呢?

2、由于我很多个reqeust 类,有AdminReqestUserRequst等等,所以我的接口定义就不能写死,所以我这里就用了object,但是若objectXXXRequst不是同类型,我应该怎么来写这个接口的参数,让所有的xxxReqeust 都能当参数使用(xxreqeust继承laravel 的reqeust)

阅读 4.1k
2 个回答

你这里指定为 object 本身就是一种没有意义的应为了,如果你真想确定传进来的是一个对象,你可以创建一个空接口,后面要传入进来的类都实现这个接口,或者在方法签名不限制,而是在方法内部去检查(这并没有任何意义),另外你这里既然提到了 xxxRequest 都继承自 Laravel 的 Request ,那你可以直接在接口这里限定为 Laravel 的 Request 即可(传入的只要是这个类的后代类即可)。

根据协变与逆变的规则,子类参数签名允许比父类更加宽泛,返回类型签名允许比父类更加具体,放在你这里则自然不适合(object -> xxxRequest) 是从宽泛 -> 具体 的过程了。

误,之前使用 PHP 旧版(7.1)测试时本发现 object 作为方法签名类型,传入对象时会报错,方才使用 php 8.1 再行测试已经是可用(7.2+) 时可用。

另外值得一提的是,PHP 7.2(不含) 之前 PHP 中的 object 是个 “特殊的类型”,虽然可以用做签名类型,但是实际并不合法。

但是,这个并没有过多的用途。

这种新的对象类型, object, 引进了可用于逆变(contravariant)参数输入和协变(covariant)返回任何对象类型。

使用 is_a 可以用来验证,任何对象返回都是 false ,包括 stdClass。

$var = (object)[];
var_dump($var, is_a($var, 'object', true));

另见:

这里很简单啊,明明你接口里定义的参数是Object $object,那么你实现这个接口的时候你的参数必须和接口必须要保持一致,但是你这里定义的是AdminRequest $request,完全不一样的啊,你定义为AdminRequest $request都可以,名称可以不一样,但是参数必须要一致。

我怀疑你是把这个定义参数和传递参数搞混了,你既然是要实现,那么必须按照接口定义的要实现的方法的参数严格进行实现。

你在传递参数的时候可以(new AdminController)->save(new AdminRequest())肯定是没问题的啊,因为你传递的参数new AdminRequest的类型肯定是object,那么传递进去是没关系的。

实际上你定义了Object之后,可以传递任意的对象进去,其实这种不太符合程序设计规则的,你应该再定义一个具体的接口类型。

举个例子

<?php

/**
 * Interface Request
 * @package ${NAMESPACE}
 */
interface Request
{
    function request(array $data): array;
}

/**
 * Interface IAdminController
 * @package ${NAMESPACE}
 */
interface IAdminController
{
    function save(Request $request): array;
}

/**
 * Class AdminRequest
 */
class AdminRequest implements Request
{
    /**
     * @param array $data
     *
     * @return string[]
     */
    function request(array $data):array
    {
        return ['admin'];
    }
}

/**
 * Class ClientRequest
 */
class ClientRequest implements Request
{
    /**
     * @param array $data
     *
     * @return string[]
     */
    function request(array $data):array
    {
        return ['client'];
    }
}

/**
 * Class AdminController
 */
class AdminController implements IAdminController
{

    function save(Request $request): array
    {
        $request->request([]);
        return [];
    }
}

$a = new AdminController();
$a->save(new AdminRequest());// 这里传递AdminRequest

$b = new AdminController();
$b->save(new ClientRequest());// 这里传递ClientRequest

另外,这个接口实现有个规则就是,具体类实现定义的接口的时候,传递的参数如果定义的是某个自定义的对象类型,那么,在实现的时候,这个参数定义可以使用接口定义的对象类型可以是接口定义的对象类型的父类对象。
举个例子:

/**
 * Interface Request
 * @package ${NAMESPACE}
 */
interface Request
{
    function request(array $data): array;
}

/**
 * Interface IAdminController
 * @package ${NAMESPACE}
 */
interface IAdminController
{
    // 注意,这里定义的类型是 ClientRequest
    function save(ClientRequest $request): array;
}

/**
 * Class AdminRequest
 */
class AdminRequest implements Request
{
    /**
     * @param array $data
     *
     * @return string[]
     */
    function request(array $data):array
    {
        return ['admin'];
    }
}

/**
 * Class ClientRequest
 */
class ClientRequest implements Request
{
    /**
     * @param array $data
     *
     * @return string[]
     */
    function request(array $data):array
    {
        return ['client'];
    }
}

/**
 * Class AdminController
 */
class AdminController implements IAdminController
{
    // 注意,这里具体实现的时候可以用接口定义的父类
    function save(Request $request): array
    {
        $request->request([]);
        return [];
    }
}

所以,再回头看你这个问题,相当于正好相反,具体的实现类是接口定义的参数对象的子对象,这是不符合规范的。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题