Dostęp do nowo utworzonej metody w interfejsie

0

Interfejs **TestInterface **i klasa **Parent **są już zaimplementowane w programie.

interface TestInterface
{
    public function test1();
}
class Parent implements TestInterface
{
    public function test1()
    {
        // coś tam wykonuje
    }
}

Potrzebuję stworzyć dodatkową metodę, która będzie implementowała interfejs TestInterface.

class Child extends Parent
{
    public function test2()
    {
        // coś tam wykonuje
    }
}

Korzystam z tej metody wywołując ją poprzez wstrzyknięcie zależności.

class Handler 
{
    /**
     * @var TestInterface
     */
    private $test;

    public function __construct(TestInterface $test)
    {
        $this->test = $test;
    }

    public function handlerMethod()
    {
        $this->test->test2();
    }
}

I to działa ale PhpStorm podkreśla mi linijkę w funkcji handlerMethod() wskazując na brak metody w interfejsie TestInterface.

Method 'test2' not found in TestInterface
Inspection info: Referenced method is not found in subject class.

Czy powinienem w takim razie zmienić podejście i np. rozszerzyć interfejs TestInterface, dodając tam nową metodę?
Czy zostawić tak jak jest i nie przejmować się warningiem?

Wyżej podałem przykład w uproszczeniu, moim interfejsem jest: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Mailer/MailerInterface.php
Natomiast klasa Parent to https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Mailer/TwigSwiftMailer.php

2

To co robisz jest wbrew wszelkim praktykom OOP, Teoretycznie wywołujesz nieistniejącą metodę. To jest błąd na poziomie projektowym. To że wiesz, że tam trafia klasa Child i że ma taką metodę nic nie zmienia. Masz dwa wyjścia:

  1. Dodać metodę test2 do interfejsu
  2. Wstrzykiwać klasę Child
0

EDIT: Najlepiej będzie jak opiszesz swój konkretny przypadek. Teraz zauważyłem dopiero, że chodzi o zewnętrznego bundl'a, więc rozwiązanie 1 odpada całkowicie.

To, co zrobiłeś łamię Liskov Substitution Principle, ponieważ jeżeli wstrzykniesz inną implementację interfejsu TestInterface, to program się wysypie. Masz kilka wyjść w zależności od tego, co chcesz zrobić:

  1. Możesz rozszerzyć interfejs TestInterface o nową metodę i problem z głowy. Pamiętaj jednak o Interface segregation principle - klasy nie powinny być zmuszone do implementowania metod, których nie potrzebują. Mowa tu o innych implementacjach interfejsu TestInterface. Dodając metodę do interfejsu musisz ją zaimplementować wszędzie.

  2. Rozwiązanie wynikające z punktu pierwszego, czyli rozszerzasz interfejs, ale przez dziedziczenie. Wtedy Twoja klasa child implementuje nowy interfejs, a pozostałe imlementacje interfejsu bazowego nie muszą implementować niepotrzebnej metody. To bardzo zbliżone rozwiązanie do tego, które zaproponował @Sarrus, tylko takie "by the book".

interface TestInterfaceOnSteroids extends TestInterface {
    public function test2();
}

class Child extends Parent implements TestInterfaceOnSteroids {
}
  1. Zastanów się, czy w tym wypadku lepszym rozwiązaniem nie będzie kompozycja. Pytanie co robi metoda test2. Może powinieneś stworzyć oddzielną klasę, która będzie miała metodę test2 i to ta klasa będzie miała zależność na interfejsie TestInterface i wywoływała z niego odpowiednie metody.
class SomeManager implements SomeManagerInterface
{
    private $test;
    
    public function __construct(TestInterface $test)
    {
        $this->test = $test;
    }

    public function test2()
    {
        //...
        /...
        $this->test->doSomething();
    }
}

class Handler 
{
    private $manager;

    public function __construct(SomeManagerInterface $manager)
    {
        $this->manager = $test;
    }

    public function handlerMethod()
    {
        $this->manager->test2();
    }
}
0

@Wiara czyni cuda: odpowiadaj w postach.

Tak na szybko po przeczytaniu tego bundla, to możesz dodać własny listener i własny mailer.. bo czemu nie?

Ewentualnie jezeli chcesz użyć metody sendMessage z tej klasy, to utwórz nową klase, która po niej dziedziczy i dodaj tam swoje metody. Tak jak w punkcie 2. To powinno załatwić sprawę.

A tak nawiasem mówiąc, to nie wiem czy posiadanie jednej klasy, która zawiera różne maile to dobry pomysł. Za pół roku będziesz tam miał 50 różnych metod i tone zależności, bo się okaże, że kazdy mail potrzebuje inne. Może warto tworzyć male klasy per mail?

Swoją droga klasa z tego bundla nie jest najlepsza, bo miesza poziomy abstrakcji. Dlatego musisz tak kombinować. Gdybyś miał klasę która służy tylko do wysyłania maili z twiga, to by nie było problemu, a teraz masz w jednej klasie logikę biznesowa (typy maili), pomieszane ze sposobem wysyłki maila, który jest ładowany z twiga. Słabo. Chociaż prawdopodobnie zrobili to w ten sposób, bo nie zakładałi, że ktoś będzie chciał tego użyć, więc może lepiej utwórz swój własny mailer :)

1

Problem, który poruszyłem w tym temacie dotyczy projektu realizowanego z użyciem Symfony4 + FOSUserBundle.

Co chcę uzyskać:
Podczas logowania kiedy użytkownik trzykrotnie, błędnie wprowadzi hasło wówczas jego konto zostanie zablokowane a dodatkowo zostanie wysłane powiadomienie na adres email tej osoby z linkiem, który umożliwi odblokowanie konta.

Jak to robię (problem dotyczy wyłącznie wysyłania maila):
Logowanie realizuje za pomocą Ajax (od 40 linii) - https://github.com/MajorKuprich/Health-Card/blob/MailerTesting/assets/js/login.js

Żądanie jest przechwytywane przez klasę AuthenticationHandler - https://github.com/MajorKuprich/Health-Card/blob/MailerTesting/src/Handler/AuthenticationHandler.php
Implementuje dwa interfejsy, jeden w przypadku błędów walidacji, drugi dla prawidłowego logowania.
W metodzie onAuthenticationFailure w miejsce zakomentowanego kodu (linia 105) chcę wysłać mail z powiadomieniem o blokadzie konta.

Wstrzykuje więc interfejs, który stworzyłem - CustomMailerInterface i inicjuje go w konstruktorze.
Zgodnie z tym co napisali @Desu i @Sarrus rozszerzyłem interfejs odpowiadający za wysyłanie maili - https://github.com/MajorKuprich/Health-Card/blob/MailerTesting/src/Mailer/CustomMailerInterface.php i stworzyłem nową klasę implementującą nowy interfejs - https://github.com/MajorKuprich/Health-Card/blob/MailerTesting/src/Mailer/CustomMailer.php

Interfejs służący do wysyłania maili, który wstrzykuję w klasie AuthenticationHandler jest skonfigurowany w pliku services.yaml w następujący sposób (linia 49). Ostatni serwis w tym pliku to moja nowo utworzona klasa CustomMailer, która implementuje interfejs CustomMailerInterface.
https://github.com/MajorKuprich/Health-Card/blob/MailerTesting/config/services.yaml

Dodatkowo w pliku fos_user.yaml musiałem zmienić serwis na ten, który stworzyłem (linijka 7) - https://github.com/MajorKuprich/Health-Card/blob/MailerTesting/config/packages/fos_user.yaml

Niestety program nie działa - wyrzuca błąd:

Cannot autowire service "App\Mailer\CustomMailer": argument "$parameters" of method "FOS\UserBundle\Mailer\TwigSwiftMailer::__construct()" is type-hinted "array", you should configure its value explicitly.

Mimo, iż nadałem mu dla argumentu $parameters tablice z kluczami dla widoków maili generowanych przez FOSUserBundle (linia 57):
https://github.com/MajorKuprich/Health-Card/blob/MailerTesting/config/services.yaml

Cały program dostępny tutaj: https://github.com/MajorKuprich/Health-Card/tree/MailerTesting
Dziękuję z góry za pomoc :)

0

Po pierwsze, to możesz usunąć te wywołania parent:: ze swojego mailera. To nie jest konieczne. Po drugie spróbuj zmodyfikować services.yml tak:

app.mailer.custom_fos_user_mailer:
    parent: fos_user.mailer.twig_swift (albo 'fos_user.mailer.twig_swift') 
    class: App\Mailer\CustomMailer

Ale i tak moim zdaniem powinieneś utworzyć swój mailer. Zaraz Ci pokaże, co mam na myśli.

0
Desu napisał(a):

Po pierwsze, to możesz usunąć te wywołania parent:: ze swojego mailera. To nie jest konieczne. Po drugie spróbuj zmodyfikować services.yml tak:

app.mailer.custom_fos_user_mailer:
    parent: fos_user.mailer.twig_swift (albo 'fos_user.mailer.twig_swift') 
    class: App\Mailer\CustomMailer

Niestety tak też nie działa:

Attribute "autowire" on service "app.mailer.custom_fos_user_mailer" cannot be inherited from "_defaults" when a "parent" is set. Move your child definitions to a separate file or define this attribute explicitly in C:\xampp\htdocs\health-card\config/services.yaml (which is loaded in resource "C:\xampp\htdocs\health-card\config/services.yaml").

Po dodaniu kilku parametrów do services.yaml tak jak to jest wyjaśnione tutaj - znów wracam do początkowego błędu:

Cannot autowire service "App\Mailer\CustomMailer": argument "$parameters" of method "FOS\UserBundle\Mailer\TwigSwiftMailer::__construct()" is type-hinted "array", you should configure its value explicitly.
1

Utworzyłem Ci taki proof of concept, który może zadziałać. Kodzik tutaj https://github.com/MajorKuprich/Health-Card/pull/1 :) Jak coś, to tego nie odpalałem, więc nie wiem :D

0

Kłóci mu się z klasą EmailConfirmationListener z FOSUserBundle:
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/EventListener/EmailConfirmationListener.php

Argument 1 passed to FOS\UserBundle\EventListener\EmailConfirmationListener::__construct() must be an instance of FOS\UserBundle\Mailer\MailerInterface, instance of App\Mailer\Mailer given, called in C:\xampp\htdocs\health-card\var\cache\dev\ContainerMF7xj3B\getFosUser_Listener_EmailConfirmationService.php on line 13

Oprócz logowania mam oczywiście rejestrację - jeśli użytkownik prawidłowo założy konto to ten Bundle automatycznie wysyła maila potwierdzającego tą operację a więc wchodzi do tej klasy, w której to korzysta z własnego MailerInterface :(

0

Co to znaczy, że mu się kłóci? To są dwa różne serwisy, więc powinno być okej. Masz konkretne komunikaty o błędzie?

1

Tutaj przywróć fos_user.mailer.twig_swift. Chodzi o to, że będziemy mieli dwa mailary. FOSUserBundle niech używa swojego, a my swojego ;)

0

Wszystko działa jak należy, dziękuje @Desu jeszcze raz za poświęcony czas :)

0

Dzięki za ten temat!!

1 użytkowników online, w tym zalogowanych: 0, gości: 1