Home > Net >  sending special attributes without using if statements
sending special attributes without using if statements

Time:11-30

<?php

    include '../classes/productsContr.cls.php';

    $sku = $_POST['sku'];
    $name = $_POST['name'];
    $price = $_POST['price'];
    $type = $_POST['type'];
    $size = $_POST['size'];
    $height = $_POST['height'];
    $width = $_POST['width'];
    $lenght = $_POST['length'];
    $weight = $_POST['weight'];


    $ob = new productsContr($sku,$name,$price,$type,$size,$height,$width,$lenght,$weight);

this is the addProducts.inc.php file. it takes the post request parameters and sends them to the controller file

<?php

include 'products.cls.php';

class productsContr extends Products {

    private $sku;
    private $name;
    private $price;
    private $type;
    private $size;
    private $height;
    private $width;
    private $length;
    private $weight;

    public function __construct($sku, $name, $price, $type, $size, $height, $width, $length, $weight) {
        $this->sku = $sku;
        $this->name = $name;
        $this->price = $price;
        $this->type = $type;
        $this->size = $size;
        $this->height = $height;
        $this->width = $width;
        $this->length = $length;
        $this->weight = $weight;
    }

    private static $specialAttributes = [
        "DVD-Disk" => [$this->size],

        "Furniture" => [
            $this->height,
            $this->width,
            $this->length,
        ],

        "Book" => [$this->weight],
    ];

    $ob = new Products();

    $ob->addProduct(
    $sku:$this->sku,
    $name:$this->name,
    $price:$this->price,
    $size:$this->size,
    $height:$this->height,
    $width:$this->width,
    $length:$this->length,
    $weight:$this->weight
);



}

this is the productsContr.cls.php file and it should tell the model file how to add the products to the database.

<?php

include 'dbh.cls.php';

class Products extends Dbh{

    public function addProduct(
        $sku = '',
        $name = '',
        $price = 0,
        $size = 0,
        $height = 0,
        $width = 0,
        $length = 0,
        $weight = 0
        ) {

        $sql = "INSERT INTO products (product_sku, product_name, product_price, product_size, product_height, product_width, product_length, product_weight) ($sku, $name, $price, $size, $height, $width, $length, $weight);";

    }

}

this is the products.cls.php model file.

with each product I want to send the sku, the name, the price, and the special attributes for each type as in the associative array $specialAtrributes, but without using if or switch statements (this is a test task)

I thought this would work with the associative array but I am stuck right now. any solutions?

CodePudding user response:

First things first

First of all your code example seems to be wide open for sql injection. Please fix this first. There are some rules when it comes to software developments. One of the most important patterns is seperate things as most as you can. Your code tries to mix up different things. That 's always not a good approach.

Seperation of concerns

Think of the terms you use. When we are speaking of products we as humans know, that products can be a furniture, a book or what so ever. Taking a deeper dive we will recognize, that a book has different attributes than a furniture. When thinking of seperation of concerns we have to seperate this two things, because they have different attributes.

Data modeling

As you can see in your own code you will always run into trouble when trying to do several things at once. First think of all attributes that all products have in common. As you already found out sku, name and price are attributes that all products have in common. Let us create an abstraction of that.

<?php

declare(strict_types=1);

namespace Marcel\Model;

abstract class Product
{
    public function __construct(
        protected string $sku = '',
        protected string $name = '',
        protected float $price = 0,
    ) {}

    public function toArray(): array
    {
        return get_object_vars($this);
    }
}

The abstract class above takes all properties every product has in common. From now on you 're able to inherit from that class and create all the products you want to offer.

<?php

declare(strict_types=1);

namespace Marcel\Model;

class Furniture extends Product
{
    public function __construct(
        protected string $sku = '',
        protected string $name = '',
        protected float $price = 0,
        protected float $height = 0,
        protected float $width = 0,
        protected float $length = 0
    ) {}
}

class Book extends Product
{
    public function __construct(
        protected string $sku = '',
        protected string $name = '',
        protected float $price = 0,
        protected float $weight = 0
    ) {}
}

Seems to be a bit of work. But this effort will pay off very easily later. Because with this exactly defined product models you 're able to do some fancy things like hydration. But let us take a look at your controller first.

The controller

As you might already know your controller handles all your incoming data. Because you don't know what data you are receiving, you have to be a bit more dynamic at this point. As long as you know the type of your product, our project is very simple.

<?php

declare(strict_types=1);

namespace Marcel\Controller;

use InvalidArgumentException;
use ReflectionClass;

class ProductController
{
    public function addAction(): void
    {
        /*
            [
                'sku'    => 'abc-123',
                'name'   => 'harry potter',
                'price'  => '99.99',
                'type'   => 'book',
                'size'   => '',
                'height' => ''
                'width'  => '',
                'length' => '',
                'weight' => '350',
            ]
        */
        if ($_POST) {
            if (! isset($_POST['type'])) {
                throw new InvalidArgumentException('No product type');
            }

            $productFqcn = '\\Marcel\\Model\\' . ucfirst($_POST['type']);
            if (! class_exists($productFqcn) {
                throw new InvalidArgumentException('Invalid product type');
            }

            // this will throw type errors because you have to sanitize your
            // post data before hydration. Take care of floats and strings.
            $product = new $productFqcn();
            $reflector = new ReflectionClass($product);
            $properties = $reflector->getProperties();

            foreach ($properties as $property) {
                if (isset($_POST[$property->getName()])) {
                    $property->setAccessible(true);
                    $property->setValue($product, $_POST[$property->getName()]);
                }
            }

            // at this point $product contains all relevant properties
        }
    }
}

The controller above uses reflection to find out which properties your product has. A book has no height property so it won 't be hydrated. At the end the book data object contains all the needed properties.

Hydration is a common approach when it comes to data handling. As statet out in the controller you have to sanitize your post data before hydration. Just filter and validate your incoming data that it fits for the declared types in the data models.

Repositories for database handling

The same abstraction level goes for repositories, that can store the received data into the database. Because every product has different properties, you can seperate into different product database tables. If you want to store every product in just one single table watch out for columns with null as default value.

The following approach assumes one single product table.

<?php

declare(strict_types=1);

namespace Marcel\Repository;

use Marcel\Model\Product;

class ProductRepository
{
    public function add(Product $product): void
    {
        // find your which datafields to store
        $data = $product->toArray();

        $sql = 'INSERT INTO products (%s) VALUES (:%s)';
        $sql = sprintf(
            $sql, 
            implode(', ', array_keys($data)), 
            implode(', :', array_values($data))
         );
        
         // sql prepared statement here and so on
    }
}

As you can see the repository just takes a single parameter $product, which must be an instance of our product data model. Type hints lead to type safety. From now on you 're able to take any product, extract its data and insert it into a database table dynamically. The example above hints to prepared statements. Another point you have an eye on.

Let us extend the controller from above.

<?php

declare(strict_types=1);

namespace Marcel\Controller;

use InvalidArgumentException;
use Marcel\Repository\ProductRepository;
use ReflectionClass;

class ProductController
{
    public function addAction(): void
    {
        ...
        // code from above
        
        // persist a new product in the database
        $repository = new ProductRepository();
        $repository->add($product);
    }
}

Conclusion

It is important to think about how the data is structured before programming. Get a clear picture of what data is the same and what data is not. Abstract as much as possible, because you don't want to make unnecessary work for yourself. Always follow the separation of concerns strategy. This will make your life much easier in the future.

As you can see from the code shown, it is not really easy to abstract so far that you can store all products with one controller. But it is possible. However, you will not get around some important things like input filters and prepared statements. These are elementarily important for the security of your application.

But hey ... no if or switch conditions shown in the code above, to seperate the different products. ;)

  •  Tags:  
  • php
  • Related