Использовать экземпляр дочернего класса вместо родительского класса при заданном условии - PullRequest
1 голос
/ 20 февраля 2020

Я работаю над тем, чтобы разбить большой монолитный класс c на несколько подклассов, но это слишком много, чтобы делать все сразу, поэтому я хочу разделить их один за другим на несколько выпусков, как позволит время. Это класс аутентификации, который авторизует некоторый канал, поэтому в настоящее время он выглядит следующим образом:

$auth = new Auth($user, $data);
$output = $auth->authChannel($channelName);

Внутри Auth, он в основном выглядит следующим образом:

public function __construct($user, $data)
{
    $this->user = $user;
    $this->data = $data;
}

public function authChannel($channel)
{
    $this->setUserData();

    if (isset(self::CHANNEL_AUTH_FUNCTIONS[$channel])) {
        $authFunction = self::CHANNEL_AUTH_FUNCTIONS[$channel];
        return $this->$authFunction();
    } else {
        // invalid channel
    }
}

Итак, self::CHANNEL_AUTH_FUNCTIONS в основном ['channelA' => 'authChannelA', 'channelB' => 'authChannelB'], et c., и все эти функции находятся в этом одном классе.

Теперь то, что я хочу сделать, по одному, это if $legacyChannel => callLegacyFunction() / else $newChannel => instantiate its own class and call auth().

Поэтому я поместил Auth.php в его собственное пространство имен, и в этом же пространстве имен появился новый класс Channel.php. И Channel extends Auth.

В настоящее время у меня есть это:

public function authChannel($channel)
{
    $this->setUserData();

    if (isset(self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel])) {
        $authFunction = self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel];

        if ($authFunction) {
            return $this->$authFunction();
        } else {
            $authClassName = __NAMESPACE__ . '\\' . ucwords($channel);
            $authClass = new $authClassName($user, $data);
            return $authClass->auth();
        }
    } else {
        // invalid channel
    }
}

Есть ли лучший способ сделать это? В настоящее время это кажется немного расточительным, поскольку создаются два разных объекта, и, например, функцию setUserData() необходимо будет вызвать снова. Мне также интересно, есть ли лучший способ получить имя класса Dynami c, кроме как через __NAMESPACE__ . / . $className.

Ответы [ 2 ]

1 голос
/ 20 февраля 2020

Вам придется немного поработать, пока этот код не станет лучше. Я постараюсь предложить как можно меньше изменений, чтобы сделать «миграцию» максимально безболезненной, хотя вы на несколько шагов удалены от чистого дизайна.

Для начала вы можете создать AuthStrategyInterface для ваших новых классов аутентификации.

interface AuthStrategyInterface
{
    public function supports(string $channel): bool;
    public function auth($user, $data);
}

Каждый из ваших новых классов аутентификации должен реализовывать этот интерфейс. Метод supports($channel) достаточно прост для понимания: если класс аутентификации может работать с определенным каналом, он должен возвращать true.

Вашему классу Auth потребуется способ внедрения этих стратегий. Обычно вы делаете это в конструкторе ... но чтобы оставить ваш API неизменным, мы просто создадим для этого метод установки.

При выполнении authChannel() сначала проверяются введенные стратегии, чтобы увидеть если кто-либо поддерживает используемый $channel, и используйте его, если это возможно. Если нет, вернитесь, чтобы проверить свои старые реализации.

Таким образом, вам не нужно прикасаться к старому коду при добавлении новых стратегий аутентификации. По мере добавления новых реализаций вы постепенно душите унаследованную систему. В какой-то момент ни одна из старых реализаций не используется, и вы можете перейти к новой фазе рефакторинга кода.

class Auth {
    private iterable $strategies = [];

    public function __construct($user, $data)
    {
        $this->user = $user;
        $this->data = $data;
    }

    public function setAuthStrategies(iterable $strategies)
    {
        $this->strategies = $strategies;
    }


    public function authChannel($channel)
    {
        $this->setUserData();

        // check if any of the new strategies supports  
        foreach ($this->strategies as $strategy) {
            if ($strategy->supports($channel) {
                return $strategy->auth($this->user, $this->data);
            }
        }

        // check "legacy" authentication methods.
        if (isset(self::CHANNEL_AUTH_FUNCTIONS[$channel])) {
            $authFunction = self::CHANNEL_AUTH_FUNCTIONS[$channel];
            return $this->$authFunction($this->user, $this->data);
        }

        // no valid authentication method
        return false;     
    }
}

Чтобы использовать его, вы должны сделать что-то вроде этого:

$fooAuthStrategy = new FooAuthStrategy();
$barAuthStrategy = new BarAuthStrategy();
$bazAuthStrategy = new BazAuthStrategy();

$auth = new Auth($user, $data);
$auth->setAuthStrategies(
    [
        $fooAuthStrategy,
        $barAuthStrategy,
        bazAuthStrategy
    ]
);

$auth->authChannel($channel);

Специфика будет меняться в зависимости от того, как именно настроено ваше приложение, но что-то подобное приведет вас в правильном направлении, чем ваш текущий подход.

0 голосов
/ 20 февраля 2020

Я не знаю, правильно ли я понял вопрос, но вы не могли сделать это так?

 public function authChannel($channel)
{
    $this->setUserData();

    if (!isset(self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel])) {
        // Invalid channel
        return;
    }
    return  self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel]
            ? $this->$authFunction()
            : parent::auth();
}

...