Зачем нам нужно проверять, является ли пользователь экземпляром пользовательского интерфейса


Я заметил в контроллерах FOSUserBundle (ProfileController ) проверку, является ли $user экземпляром UserInteface

$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
    throw new AccessDeniedException('This user does not have access to this section.');
}

Достаточно ли только проверить if (!is_object($user))?

Если моя пользовательская сущность расширяется FOS\UserBundle\Model\User, в этом случае $user не будет экземпляром UserInterface?

Author: Wouter J, 2015-05-14

4 answers

Да, если ваш код не предназначен для использования с открытым исходным кодом, в противном случае нет.

Отсутствие проверки экземпляра объекта не гарантирует, что объект, возвращаемый методом getUser(), будет иметь все ожидаемые методы (пример: getUsername()).

Если вы посмотрите на метод getUser() из Controller.php он не обязательно возвращает объект пользователя. Фактически, вы могли бы настроить брандмауэр Symfony2 таким образом, чтобы getUser() возвращал другой объект другого экземпляр.

Признавая, что у нас есть интерфейс UserInterface, который определяет getUsername().

В следующем коде наш объект пользователя не реализует UserInterface.

$user = $this->getUser();
if (!is_object($user)) {
    $user->getUsername();
}

Этот код выдаст ошибку, потому что getUsername() не существует на объекте, вместо этого код должен был быть следующим:

$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
    $user->getUsername();
}

Если объект пользователя не реализует соответствующий интерфейс, то код не выдаст ошибку, потому что он не будет выполнен.

Избегайте проверки объекта, подобного следующий

$user = $this->getUser();
if (!is_object($user) || !$user instanceof User) {
    $user->getRoles();
}

Если кто-то расширит объект пользователя, то оператор if больше не будет выполняться, потому что $user не будет экземпляром User, но скажет ExtendedUser, хотя в нем есть все необходимые методы.

Другое преимущество использования интерфейсов заключается в том, что вы можете реализовать несколько интерфейсов на объекте.

class A implements C {}

class B extends A implements C, D {}

interface C {}

interface D {}

$nA = new A();
$nB = new B();

$nA instanceof A; // true - instance of A
$nA instanceof B; // false - pretty obvious, no relationship with B
$nA instanceof C; // true - A implements C
$nA instanceof D; // false - A does not implement D

$nB instanceof A; // false - B is not an instance of A
$nB instanceof B; // true - instance of B
$nB instanceof C; // true - A implements C, that's the key:
                  //        both A and B implements C but B is not an
                  //        instance of A.
$nB instanceof D; // true - A implements D

TLDR; интерфейсы - отличный способ установить ожидания и избежать серьезных головных болей.

Когда вы прочитаете код, вы сможете быстро определите тип передаваемых объектов. Если кто-то изменит код, он либо покажет значимую ошибку, либо изящно ухудшится (в этом случае пользователю будет отказано в доступе).

 1
Author: Thomas Potaire, 2015-05-14 20:57:17

Если моя пользовательская сущность расширяется FOS\UserBundle\Model\User, в этом случае $user не будет экземпляром UserInterface?

Это неверно, так как FOS\UserBundle\Model\User реализует FOS\UserBundle\Model\UserInterface, который расширяет (интерфейсы расширяют другие интерфейсы) Symfony\Component\Security\Core\User\AdvancedUserInterface, который расширяет Symfony\Component\Security\Core\User\UserInterface. Так что $user instanceof UserInterface будет правдой.

Интерфейс - это контракт в объектно-ориентированном мире. С помощью is_object($user) вы знаете, что $user Является объектом, но вы не знаете, какие общедоступные методы есть у объекта и т. Д. Ничто не мешает $this->getUser() возвращать совершенно другой объект, нарушающий ваш код. Когда вы проверяете наличие экземпляра, у вас есть обещание: методы в интерфейсе доступны для вас. Как правило, я бы рекомендовал вам никогда не вызывать методы, для которых вы либо явно не вводили, либо не проверяли их использование instanceof.

 1
Author: Wouter J, 2015-05-14 21:10:51

Да, это немного странно для новых и старых разработчиков.

Интерфейс допускает множественное наследование. Меня учили, что вы используете наследование в тех случаях, когда классы лучше всего описываются как "это", например, собака - это животное или SwiftMailer - это почтовая программа.

Затем интерфейсы можно использовать для добавления дополнительных функций, это действует как контракт, в котором говорится, что этот класс должен реализовать какой-то метод. как кора или почта. И меня учили, что эти интерфейсы должны называться такими вещами, как канБарк, Лающий или отправляемый по почте и т. Д., Затем они будут реализовывать такие методы, как лай или почта.

Но современные разработчики больше склоняются к использованию интерфейсов в качестве дополнительной абстракции, чтобы вы могли быстро менять классы местами.

Таким образом, вместо привязки к вашему пользовательскому классу вы бы привязались к пользовательскому интерфейсу, который будет реализован пользовательским классом.

Итак, чтобы ответить на ваш фактический вопрос, если либо класс FOS\UserBundle\Model\User, либо ваш класс пользователя реализует интерфейс пользователя интерфейс, тогда вам хорошо идти.

 -1
Author: Kris Zani, 2015-05-14 19:07:30

Ваутер Джей прав: FOS\UserBundle\Модель\Пользователь реализует FOS\UserBundle\Модель\Интерфейс пользователя, но если вы не добавите "использовать FOS\UserBundle\Модель\Интерфейс пользователя" в свой файл, то тест instanceof не пройдет. Не забывай об этом.

 -1
Author: plancton, 2018-04-12 09:07:14