Tweeted twitter.com/StackProgrammer/status/699894298876186624
2 deleted 2171 characters in body; edited title
source | link

Architecture: API with slightly different behavior depending on the logged-in user roles

This is something that I have been dealing in the past andHow should I usually end creatingstructure a piece of code hard to understandthat executes an operation, with many if/else statementsbut may have slightly different behavior depending on, and hard to updated once newlet's say, user roles are introduced.?

Next I'll show you how IBoth can edit the products the have been doing it sincecreated themselves, but managers can edit products belonging to his employees. If a long time; then below you'll seemanager edits a product created by an employee, the employee will get an email notification about the edit.

The issue I have, is that I´m creating if/else or switch/case statements for checking the user roles, and I feel once I add new roles my attempted solutioncode will need to check for them too and my code will become harder to read.

For example:

// What I currently have:
public function updateProductForUser(Product $product, UserInterface $user)
{
    $userWhoCreatedProduct = $product->getCreatedByUser();

    if ($userWhoCreatedProduct === $user) {
        // If the user who created the product is the same one trying to update it,
        // then go ahead and execute the update.
        $this->_doUpdateProductForUser($product, $user);
        return;

    } elseif ($user instanceof Manager) {
        $allManagerEmployees = $this->someService->findAllEmployeesOfManager($user);

        if (in_array($userWhoCreatedProduct, $allManagerEmployees)) {
            // If the user trying to update the product is a manager, and the product was
            // created by an employee of the manager, then execute the update but also
            // send an notification to the employee that the product he created got updated
            // by his manager.
            $this->_doUpdateProductForUser($product, $user);
            $this->notifyUserThatProductGotUpdated($product);
        }
    }
}

// My attempted solution. ProductManager class is converted to a Facade, that calls a CommandResolver
// to get a class able to manage the update given the user role.

public function updateProductForUser(Product $product, UserInterface $user)
{
    // Get the update command for the user role.
    // Throws 'UserCannotUpdateProductException';
    $updateProductCommand = $this->productCommandResolver->getUpdateProductCommand($product, $user);
    $updateProductCommand->execute();
}

class ProductCommandResolver
{
    public function getUpdateProductCommand(Product $product, UserInterface $user)
    {
        $userWhoCreatedProduct = $product->getCreatedByUser();

        if ($userWhoCreatedProduct === $user) {
            return new ProductOwner_UpdateProductCommand($product, $user);

        } elseif ($user instanceof Manager) {
            $allManagerEmployees = $this->someService->findAllEmployeesOfManager($user);

            if (in_array($userWhoCreatedProduct, $allManagerEmployees)) {
                return new Manager_UpdateProductCommand($product, $user);
            }
        }

        throw new UserCannotUpdateProductException();
    }
}

class Manager_UpdateProductCommand implements CommandInterface
{
    private $product;
    private $manager;
    private $mailer;

    public function __construct(Product $product, Manager $manager, MailerInterface $mailer)
    {
        $this->product = $product;
        $this->manager = $manager;
        $this->mailer  = $mailer;
    }

    /**
     * @CommandInterface
     */
    public function execute()
    {
        $this->product->save();
        // Notice that only in this class we send a notification.
        $this->mailer->notifyUserThatProductGotUpdated($this->product);
    }
}

class ProductOwner_UpdateProductCommand implements CommandInterface
{
    private $product;
    private $manager;

    public function __construct(Product $product, Manager $manager)
    {
        $this->product = $product;
        $this->manager = $manager;
    }

    /**
     * @CommandInterface
     */
    public function execute()
    {
        $this->product->save();
    }
}

API with slightly different behavior depending on the logged-in user roles

This is something that I have been dealing in the past and I usually end creating code hard to understand, with many if/else statements, and hard to updated once new user roles are introduced.

Next I'll show you how I have been doing it since a long time; then below you'll see my attempted solution.

// What I currently have:
public function updateProductForUser(Product $product, UserInterface $user)
{
    $userWhoCreatedProduct = $product->getCreatedByUser();

    if ($userWhoCreatedProduct === $user) {
        // If the user who created the product is the same one trying to update it,
        // then go ahead and execute the update.
        $this->_doUpdateProductForUser($product, $user);
        return;

    } elseif ($user instanceof Manager) {
        $allManagerEmployees = $this->someService->findAllEmployeesOfManager($user);

        if (in_array($userWhoCreatedProduct, $allManagerEmployees)) {
            // If the user trying to update the product is a manager, and the product was
            // created by an employee of the manager, then execute the update but also
            // send an notification to the employee that the product he created got updated
            // by his manager.
            $this->_doUpdateProductForUser($product, $user);
            $this->notifyUserThatProductGotUpdated($product);
        }
    }
}

// My attempted solution. ProductManager class is converted to a Facade, that calls a CommandResolver
// to get a class able to manage the update given the user role.

public function updateProductForUser(Product $product, UserInterface $user)
{
    // Get the update command for the user role.
    // Throws 'UserCannotUpdateProductException';
    $updateProductCommand = $this->productCommandResolver->getUpdateProductCommand($product, $user);
    $updateProductCommand->execute();
}

class ProductCommandResolver
{
    public function getUpdateProductCommand(Product $product, UserInterface $user)
    {
        $userWhoCreatedProduct = $product->getCreatedByUser();

        if ($userWhoCreatedProduct === $user) {
            return new ProductOwner_UpdateProductCommand($product, $user);

        } elseif ($user instanceof Manager) {
            $allManagerEmployees = $this->someService->findAllEmployeesOfManager($user);

            if (in_array($userWhoCreatedProduct, $allManagerEmployees)) {
                return new Manager_UpdateProductCommand($product, $user);
            }
        }

        throw new UserCannotUpdateProductException();
    }
}

class Manager_UpdateProductCommand implements CommandInterface
{
    private $product;
    private $manager;
    private $mailer;

    public function __construct(Product $product, Manager $manager, MailerInterface $mailer)
    {
        $this->product = $product;
        $this->manager = $manager;
        $this->mailer  = $mailer;
    }

    /**
     * @CommandInterface
     */
    public function execute()
    {
        $this->product->save();
        // Notice that only in this class we send a notification.
        $this->mailer->notifyUserThatProductGotUpdated($this->product);
    }
}

class ProductOwner_UpdateProductCommand implements CommandInterface
{
    private $product;
    private $manager;

    public function __construct(Product $product, Manager $manager)
    {
        $this->product = $product;
        $this->manager = $manager;
    }

    /**
     * @CommandInterface
     */
    public function execute()
    {
        $this->product->save();
    }
}

Architecture: API with slightly different behavior depending on the logged-in user roles

How should I structure a piece of code that executes an operation, but may have slightly different behavior depending on, let's say, user roles?

Both can edit the products the have created themselves, but managers can edit products belonging to his employees. If a manager edits a product created by an employee, the employee will get an email notification about the edit.

The issue I have, is that I´m creating if/else or switch/case statements for checking the user roles, and I feel once I add new roles my code will need to check for them too and my code will become harder to read.

For example:

// What I currently have:
public function updateProductForUser(Product $product, UserInterface $user)
{
    $userWhoCreatedProduct = $product->getCreatedByUser();

    if ($userWhoCreatedProduct === $user) {
        // If the user who created the product is the same one trying to update it,
        // then go ahead and execute the update.
        $this->_doUpdateProductForUser($product, $user);
        return;

    } elseif ($user instanceof Manager) {
        $allManagerEmployees = $this->someService->findAllEmployeesOfManager($user);

        if (in_array($userWhoCreatedProduct, $allManagerEmployees)) {
            // If the user trying to update the product is a manager, and the product was
            // created by an employee of the manager, then execute the update but also
            // send an notification to the employee that the product he created got updated
            // by his manager.
            $this->_doUpdateProductForUser($product, $user);
            $this->notifyUserThatProductGotUpdated($product);
        }
    }
}
1
source | link

API with slightly different behavior depending on the logged-in user roles

This is something that I have been dealing in the past and I usually end creating code hard to understand, with many if/else statements, and hard to updated once new user roles are introduced.

Example: My app has a 'manager' and a 'employee' roles. There are many managers in my app, and each of them have many employees. I have a dashboard where managers and their employees can add/edit/delete products.

Next I'll show you how I have been doing it since a long time; then below you'll see my attempted solution.

// What I currently have:
public function updateProductForUser(Product $product, UserInterface $user)
{
    $userWhoCreatedProduct = $product->getCreatedByUser();

    if ($userWhoCreatedProduct === $user) {
        // If the user who created the product is the same one trying to update it,
        // then go ahead and execute the update.
        $this->_doUpdateProductForUser($product, $user);
        return;

    } elseif ($user instanceof Manager) {
        $allManagerEmployees = $this->someService->findAllEmployeesOfManager($user);

        if (in_array($userWhoCreatedProduct, $allManagerEmployees)) {
            // If the user trying to update the product is a manager, and the product was
            // created by an employee of the manager, then execute the update but also
            // send an notification to the employee that the product he created got updated
            // by his manager.
            $this->_doUpdateProductForUser($product, $user);
            $this->notifyUserThatProductGotUpdated($product);
        }
    }
}

// My attempted solution. ProductManager class is converted to a Facade, that calls a CommandResolver
// to get a class able to manage the update given the user role.

public function updateProductForUser(Product $product, UserInterface $user)
{
    // Get the update command for the user role.
    // Throws 'UserCannotUpdateProductException';
    $updateProductCommand = $this->productCommandResolver->getUpdateProductCommand($product, $user);
    $updateProductCommand->execute();
}

class ProductCommandResolver
{
    public function getUpdateProductCommand(Product $product, UserInterface $user)
    {
        $userWhoCreatedProduct = $product->getCreatedByUser();

        if ($userWhoCreatedProduct === $user) {
            return new ProductOwner_UpdateProductCommand($product, $user);

        } elseif ($user instanceof Manager) {
            $allManagerEmployees = $this->someService->findAllEmployeesOfManager($user);

            if (in_array($userWhoCreatedProduct, $allManagerEmployees)) {
                return new Manager_UpdateProductCommand($product, $user);
            }
        }

        throw new UserCannotUpdateProductException();
    }
}

class Manager_UpdateProductCommand implements CommandInterface
{
    private $product;
    private $manager;
    private $mailer;

    public function __construct(Product $product, Manager $manager, MailerInterface $mailer)
    {
        $this->product = $product;
        $this->manager = $manager;
        $this->mailer  = $mailer;
    }

    /**
     * @CommandInterface
     */
    public function execute()
    {
        $this->product->save();
        // Notice that only in this class we send a notification.
        $this->mailer->notifyUserThatProductGotUpdated($this->product);
    }
}

class ProductOwner_UpdateProductCommand implements CommandInterface
{
    private $product;
    private $manager;

    public function __construct(Product $product, Manager $manager)
    {
        $this->product = $product;
        $this->manager = $manager;
    }

    /**
     * @CommandInterface
     */
    public function execute()
    {
        $this->product->save();
    }
}

How can I improve this? Is there a better way? What are the pitfalls of the implementation? Any advice is very welcomed. Thanks!