Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistency in $foregroundColor (string and array) #172

Closed
tacman opened this issue Oct 3, 2022 · 10 comments
Closed

Inconsistency in $foregroundColor (string and array) #172

tacman opened this issue Oct 3, 2022 · 10 comments

Comments

@tacman
Copy link

tacman commented Oct 3, 2022

In the PNG generator, the signature is

public function getBarcode($barcode, $type, int $widthFactor = 2, int $height = 30, array $foregroundColor = [0, 0, 0])

But in all the others, it's a string

public function getBarcode($barcode, $type, int $widthFactor = 2, int $height = 30, string $foregroundColor = 'black')

If they were the same, we could create a BarcodeGeneratorInterface, and each generator could implement it. I think that'd be better than simply extending the abstract class.

What do you think about creating a version 3 uses an interface and requires that $foregroundColor is a string in all the generators?

@tacman
Copy link
Author

tacman commented Oct 13, 2022

On a related note, the getBarcode signature is inconsistent in BarcodeGeneratorDynamicHTML

public function getBarcode($barcode, $type, string $foregroundColor = 'black')

@casperbakker
Copy link
Member

What do you think about creating a version 3 uses an interface and requires that $foregroundColor is a string in all the generators?

Yes, that could work. Lots of these issues are legacy from 10+ years of this code' existence. Bit by bit we modernise it, which is nice.

For version 3 I am also thinking about pulling the BarcodeTypes and Generators further apart, where you can inject the results of a BarcodeType into a Generator (but with different names). That way it is easier to build or tweak your own generator. A better interface could also help with that.

@casperbakker casperbakker closed this as not planned Won't fix, can't repro, duplicate, stale Sep 20, 2024
@tacman
Copy link
Author

tacman commented Sep 20, 2024

So does this mean no version 3?

@casperbakker
Copy link
Member

So does this mean no version 3?

No, even better: it means version 3 is finally here! Checkout the release

@tacman
Copy link
Author

tacman commented Sep 21, 2024

Oh, congrats! This was closed as "not planned" -- is it no longer relevant?

I need to update my Symfony bundle that is a simple wrapper about your great library: https://github.com/survos/BarcodeBundle

I remember trying to inject an interface when I ran into this problem. So is there a common interface now, where getBarcode() is consistent and can be added to an interface?

What else do I need to know about to update the bundle?

@casperbakker
Copy link
Member

Thanks!

The best way to learn about v3 is to read the readme. I explained everything in there, also about upgrading from v2. But it is backwards compatible, as the old generators are now helpers to the new classes.

It still seems hard to make a general interface for the renderers, as some support providing the width and some a width factor. But if you see a nice way to implement it, please let me know.

@tacman
Copy link
Author

tacman commented Sep 21, 2024

If all the renderers implemented the same interface, then it's really just a matter of initializing the service. You could even configure which renderer you wanted globally and inject it to a service (like twig), with no knowledge of which one is is.

for the renderers that don't support a particular feature, you could simply not implement it (or optionally though an exception that the application could handle).

But certainly the method calls themselves shouldn't change based on the renderer.

So

class myRenderer implement BarcodeRendererInterface
{
}

and normalize all the calls. You could have a method like supportsWidth() and supportsWidthFactor(), but every class would need to implement it, so that the caller wouldn't know anything at all about png or svg or jpg, simply what the service can do.

@casperbakker
Copy link
Member

Thanks. I understand how interfaces work, just not in this specific case. It sounds like it is only better on paper, but the situation does not call for this setup. Just interfaces because interfaces, does not help in practice.

For example, you cannot use the HTML barcodes the same way you can use the JPG/PNG images and different still from SVG's. So in usage you can also not just switch them. You always have to have different code before and after the rendering of the barcode, so conforming to an interface does not help. They are not interchangeable. It looks like they do the same thing, but not really.

for the renderers that don't support a particular feature, you could simply not implement it (or optionally though an exception that the application could handle).

For me, this is a big code smell. If it works differently, it should be different interfaces. The interfaces should define what to expect, not throwing exceptions or different helpers to know what will work. That is what the interface is for.

If it helps, I could make a different interface for all the renderers, if that makes building a different renderer better. But then it will be something like an ImageRendererInterface and a VectorRendererInterface. Would that help? If it is not really helpful, I would rather not add them.

Using dependency injection is also not needed I think. What different service would you inject?

@casperbakker
Copy link
Member

@tacman I have proof-of-concept of the implementation of a RendererInterface: #206

Would something like that work?

I don't like that we make it simple to give a width that does not match a round factor in PNG/JPG, as it will make the barcode not compliant. But maybe that is a trade-off worth making.

@tacman
Copy link
Author

tacman commented Sep 22, 2024

Thanks! I wrote an article about creating a Symfony bundle, using this library as an example:

https://medium.com/@tacman1123/easily-share-your-twig-extensions-with-symfony-6-1-ff633bf7d9db

My idea for the bundle was that DX is this

  • composer require survos/barcode-bundle
  • configure survos_barcode.yaml with your default, including your preferred renderer
  • in twig, render the barcode with one of these:
{% set id = 12345 %}
{{ id|barcode }}
{{ id|barcode(width='400px') }}
{{ barcode(id, {width: '400px'}) }}

e.g. https://barcode-demo.survos.com/

image

The underlying php for the twig is

    public function barcode(string $value, ?int $widthFactor = null, ?int $height = null, ?string $foregroundColor = null, string $type = BarcodeGenerator::TYPE_CODE_128, string $generatorClass = BarcodeGeneratorSVG::class ): string
    {
        if (!class_exists($generatorClass)) {
            $generatorClass = $this->barcodeService->getGeneratorClass($generatorClass);
        }
        /** @var BarcodeGeneratorSVG|BarcodeGeneratorPNG $generator */
        $generator = new $generatorClass();
        $barcodeData = $generator->getBarcode(
            barcode: $value,
            type: $type,
            widthFactor: $widthFactor ?? $this->widthFactor,
            height: $height ?? $this->height,
            // hack for array / string issue https://github.com/picqer/php-barcode-generator/issues/172
            foregroundColor: $this->barcodeService->getImageFormat($generatorClass) ? [0,0,0] : $foregroundColor ?? $this->foregroundColor
        );
        if ($this->barcodeService->getImageFormat($generatorClass)) {
            $barcodeData = base64_encode($barcodeData);
        }
        return $barcodeData;
    }

The underlying code to interact with the generators would benefit from an interface.

    public function getGeneratorTypes(): array
    {
        return (new \ReflectionClass(BarcodeGenerator::class))->getConstants();
    }
    public function getGeneratorClasses(): array
    {
        // we _could_ use classfinder to get all the classes that implement BarcodeGeneratorInterface
        return array_reduce([
            BarcodeGeneratorSVG::class,
            BarcodeGeneratorHTML::class,
//            BarcodeGeneratorDynamicHTML::class,
            BarcodeGeneratorPNG::class,
            BarcodeGeneratorJPG::class
        ], function ($carry, $className) {
            $carry[(new \ReflectionClass($className))->getShortName()] = $className;
            return $carry;
        }, []);
    }


    public function getImageFormat(string $generatorClass): ?string
    {
        return match ($generatorClass) {
            BarcodeGeneratorJPG::class => 'image/jpeg',
            BarcodeGeneratorPNG::class => 'image/png',
            default => null
        };
    }

So this is the motivation behind the interfaces, but I hear what you're saying about code smell. I know little about the internals, and in my own application this works fine. If I update the article, I'd probably add some new features like autowiring and skip listing the generators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants