Jak číst data o firmách čistě?


Nabídky práce


Včera zveřejnil Radek Hulán jednoduchý kód, který vytáhne data ze služby nabízené Evropskou Unií a vrátí je jako JSON. Podle svých slov se jedná o zjednodušený kód a tím pádem lze pochopit, že má jít o koncept, který může obsahovat chyby.

Zdrojový kód si můžete prohlédnout zde.

Na Radkovu kódu se mi nelíbí řada věcí, některé pochopitelné z důvodu jednoduchosti, druhé pro mě naprosto nepochopitelné.

Zkusil jsem je sepsat:

  • návratová hodnota v proměnné $a? Proč? Mnohem lepší by bylo nějaké $responseData
  • vracení stavu v poli stav v řetězci? V češtině? Myslím, že lepší by bylo vrátit error se zprávou (kvůli ladění) a errorCode, kde je číselný identifikátor chyby
  • proč tolik magických konstant?
  • proč chytání obecné Exception, když stačí konkrétní SoapFault?

K chybám, které i v jednoduchém kódu nemají co dělat, jsem přidal několik dalších úprav a aplikaci jsem postupně přepsal. Na Bitbucketu můžete vidět, jak jsem postupně kód přetvářel z původního na nový.

Cíle, které jsem si kladl jsou:

  • testovatelný kód (při refaktoringu jsem udělal některé chyby proto, že jsem si testy nenapsal – ponaučení pro příště)
  • čitelný kód
  • oddělení MVC (a to tak, že pokud použiju vlastní request, response a controller, je funkcionalita použitelná v libovolném frameworku)
  • použití Dependency Injection tam, kde to dává smysl
  • oddělení stavu, kdy je odpověď OK a kdy je odpověď v nepořádku do samostatné logiky, ne jen přidat status do pole.
  • znovupoužitelnost service
  • pokud aplikace poroste, možnost šířit data napříč aplikací jako Company – objekt, který bude absorbovat práci s daty, nešířit aplikací pole (typické vyhnutí se Primitive Obsession, což je jeden z Code Smells)

Výsledkem je kód, který je sice delší, ale je daleko čitelnější, znovupoužitelnější a vhodný pro libovolný framework. Nehledě na to, že na něj jdou napsat testy (unit i integrační).

Výsledný kód:

<?php


class CompanyServiceException extends RuntimeException
{
        const CODE_SERVICE_NOT_AVAILALE = 1;
        const CODE_VAT_ID_UNKNOWN = 2;
}

class CompanyService
{
        const SERVICE_WSDL_URL = 'http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl';

        private function readFromSource($id, $country)
        {
                $soap = $this->createSoapClient();
                $params = array('countryCode' => $country, 'vatNumber' => $id);
                $result = $soap->checkVatApprox($params);
                return $result;
        }

        protected function createSoapClient()
        {
                return new SoapClient(self::SERVICE_WSDL_URL);
        }

        private function createCompany($result, $country)
        {
                if ($this->isTraderAddressAndNotCity($result)) {
                        $t = explode("\n",$result->traderAddress);
                        $traderStreet = $t[0];
                        $traderCity = $t[1];
                        $traderPostcode = $t[2];
                } else {
                        $traderStreet = $result->traderStreet;
                        $traderCity = $result->traderCity;
                        $traderPostcode = $result->traderPostcode;
                }

                return new Company($result->vatNumber, $country, $result->traderName, $traderStreet,
                        $traderCity, $traderPostcode);
        }

        private function isTraderAddressAndNotCity($result)
        {
                return !isset($result->traderCity) && isset($result->traderAddress);
        }

        function readByIdAndCountry($id, $country)
        {
                try {
                        $result = $this->readFromSource($id, $country);

                        if (! $result->valid) {
                                throw new CompanyServiceException("DIC {$country}{$id} nebylo nalezeno",
                                        CompanyServiceException::CODE_VAT_ID_UNKNOWN, $e);
                        } else {
                                return $this->createCompany($result, $country);
                        }
                } catch (SoapFault $e) {
                        throw new CompanyServiceException("EC neni dostupna",
                                CompanyServiceException::CODE_SERVICE_NOT_AVAILALE, $e);
                }
        }
}

class Company
{
        private $id;
        private $country;
        private $firma;
        private $ulice;
        private $mesto;
        private $psc;

        function __construct($id, $country, $firma, $ulice, $mesto, $psc)
        {
                $this->id = $id;
                $this->country = $country;
                $this->firma = $firma;
                $this->ulice = $ulice;
                $this->mesto = $mesto;
                $this->psc = $psc;
        }

        function toArray()
        {
                // dic = country . id
                return array(
                        'ic' => $this->id,
                        'dic' => $this->country . $this->id,
                        'firma' => $this->firma,
                        'ulice' => $this->ulice,
                        'mesto' => $this->mesto,
                        'psc' => $this->psc,
                );
        }
}

class Request
{
        private $data;

        function __construct(Array $data = array())
        {
                $this->data = $data;
        }

        function __set($name, $value)
        {
                $this->data[$name] = $value;
        }

        function __get($name)
        {
                if (array_key_exists($name, $this->data)) {
                        return $this->data[$name];
                }
        }
}

class JsonResponse
{
        private $data;

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

        function send()
        {
                header("Content-Type: application/json; charset=UTF-8");
                echo json_encode($this->data);
        }
}

class CompanyServiceController
{
        private $companyService;

        function __construct($companyService)
        {
                $this->companyService = $companyService;
        }

        function run($request)
        {
                try {
                        $company = $this->companyService->readByIdAndCountry($request->id, $request->country);
                        $responseData = $company->toArray();
                } catch (CompanyServiceException $e) {
                        $responseData = array(
                                "error"=>$e->getMessage(),
                                "errorCode"=>$e->getCode(),
                        );
                }
                return new JsonResponse($responseData);
        }
}

//$request = new Request($_REQUEST):
$request = new Request(array("id"=>"8501074769", "country"=>"CZ"));
$controller = new CompanyServiceController(new CompanyService);
$response = $controller->run($request);
$response->send();

Kategorie

Agile
Cestování
Life Hacking
Minimalismus
Podnikání & Startupy
Použitelnost
Programování

Copyright © 2010 Jiří Knesl; 777 002 104 jiri.knesl@gmail.com RSS
Followujte mě na twitteru