Prestashop : Déploiement continu, vérifier la qualité du code

Cet article est le 3ème de la série sur le déploiement et l’intégration continu dans Prestashop.
Les autres articles de cette série sont les suivants :

État des lieux

En préalable de cette étape nous allons ajouter un nouveau module prestashop de mauvaise qualité et avec une erreur de syntaxe php.
C’est volontairement ce qu’il ne faut pas faire 🙂

Pour faire ça simplement vous pouvez utiliser l’outil prestashopConsole qui est disponible ici : https://github.com/nenes25/prestashop_console
Et créer un module basique de test hhtestci  via la commande suivante :
php prestashopConsole.phar module:generate:module hhtestci

Dans ce module rajouter n’importe quelle erreur php, commiter et envoyer la modification dans git, puis faire une merge request comme nous l’avons vu dans l’étape précédente.
Dans le code de la merge request, rien ne nous indique qu’il y’a une erreur.

L’erreur n’est pas forcément très visible et il est donc  tout à fait possible de passer à côté et d’envoyer une erreur en production.

On voit bien qu’il n’y a aucun contrôle, et cela veut dire que même si on sécurise qui peut livrer le code, nous ne sommes pas à l’abri d’envoyer du code non fonctionnel.
Il est donc temps de simplifier le travail de celui qui est chargé de valider le code 🙂


Le code prestashop, est majoritairement constitué de fichiers php ( mais aussi :  javascript, css ,tpl  …)
Lors de chaque livraison de code ces fichiers sont changés.

En fonction des intervenants, la qualité du développement pourra être plus ou moins bonne et générer des erreurs.

Afin de limiter ces erreurs on va donc mettre en place des outils d’analyse de la qualité de code.
Ces outils vont se charger pour nous de vérifier le code qu’on va envoyé ne comporte pas d’erreurs.
Pour la vérification des fichiers php dans un premier temps nous allons utliser Phpstan

Configuration locale de phpstan

Pour ceux qui ne connaissent pas phpstan :
C’est un outil d’analyse statique pour PHP qui aide les développeurs à détecter les erreurs de type, les problèmes de cohérence, et les violations des bonnes pratiques dans leur code PHP.
Il améliore la qualité du code, favorise la maintenance et permet de gagner du temps lors du processus de développement en fournissant des informations sur les erreurs potentielles, les types de données et les performances du code.
L’avantage des outils d’analyses statiques est donnée dans leur nom, ils n’ont pas besoin d’exécuter le projet pour détecter les problèmes potentiels.
Vous pouvez trouver plus d’information ( en anglais ) sur le site officiel : https://phpstan.org/

Pour commencer il va falloir faire fonctionner phpstan en local.

Créer une nouvelle branche depuis master
En fonction de la structuration du projet , créer un dossier tests soit à la racine, au même niveau que le dossier www

Dans ce dossier, lancer la commande suivante pour récupérer phpstan :

composer require phpstan/phpstan

Pour ne pas versionner le dossier vendor ajouter également un fichier .gitignore dans ce dossier avec le contenu suivant :
vendor

Ensuite vous pouvez créer le dossier suivant php/phpstan et mettre le contenu suivant dans le fichier phpstan.neon
Ce fichier permets de configurer phpstan, je mets volontairement tous les commentaires dans le fichiers pour ce que ce soit plus clair

parameters:
  scanFiles:
    - class_stub.php
  scanDirectories: #Listes des fichiers systèmes de Prestashop auxquels votre module peut être lié
    # Tous les chemins sont relatifs à l'emplacement du fichier de ce fichier configuration phpstan.neon
    - ../../../www/classes
    - ../../../www/config/
    - ../../../www/controllers
    - ../../../www/override
    - ../../../www/src
    - ../../../www/vendor
  level: 4 # Niveau de qualité du code, commencer (cf. https://phpstan.org/user-guide/rule-levels )
  paths: # Liste des modules spécifiques à tester
    - ../../../www/modules/hhtestci #Notre module de démo
  excludePaths:
    - ../../../www/modules/*/vendor/* #On ne veut pas analyser les fichiers vendor des modules
  ignoreErrors: #Liste des erreurs à ignorer
  #Pour les autres éléments je vous renvoie vers la documentation de phpstan
  reportUnmatchedIgnoredErrors: false
  checkMissingIterableValueType: false
  treatPhpDocTypesAsCertain: false
  parallel:
    maximumNumberOfProcesses: 1

Pour aider Phpstan à connaître les classes de Prestashop rajoutez également le fichier class_stub.php , dans ce même dossier

<?php
 
class SmartyDevTemplate extends SmartyDevTemplateCore {};
class SmartyResourceParent extends SmartyResourceParentCore {};
class SmartyCustom extends SmartyCustomCore {};
class TemplateFinder extends TemplateFinderCore {};
class SmartyCustomTemplate extends SmartyCustomTemplateCore {};
class SmartyResourceModule extends SmartyResourceModuleCore {};
class Search extends SearchCore {};
class WarehouseAddress extends WarehouseAddressCore {};
class LocalizationPack extends LocalizationPackCore {};
class CartRule extends CartRuleCore {};
class FeatureValue extends FeatureValueCore {};
class AddressFormat extends AddressFormatCore {};
class Currency extends CurrencyCore {};
class CategoryLang extends CategoryLangCore {};
class DataLang extends DataLangCore {};
class ConfigurationLang extends ConfigurationLangCore {};
class AttributeGroupLang extends AttributeGroupLangCore {};
class CarrierLang extends CarrierLangCore {};
class TabLang extends TabLangCore {};
class StockMvtReasonLang extends StockMvtReasonLangCore {};
class MetaLang extends MetaLangCore {};
class GroupLang extends GroupLangCore {};
class AttributeLang extends AttributeLangCore {};
class GenderLang extends GenderLangCore {};
class QuickAccessLang extends QuickAccessLangCore {};
class FeatureValueLang extends FeatureValueLangCore {};
class SupplyOrderStateLang extends SupplyOrderStateLangCore {};
class ThemeLang extends ThemeLangCore {};
class ContactLang extends ContactLangCore {};
class OrderStateLang extends OrderStateLangCore {};
class OrderMessageLang extends OrderMessageLangCore {};
class ProfileLang extends ProfileLangCore {};
class RiskLang extends RiskLangCore {};
class FeatureLang extends FeatureLangCore {};
class CmsCategoryLang extends CmsCategoryLangCore {};
class OrderReturnStateLang extends OrderReturnStateLangCore {};
class ProductDownload extends ProductDownloadCore {};
class QuickAccess extends QuickAccessCore {};
class ImageType extends ImageTypeCore {};
abstract class Db extends DbCore {};
class DbPDO extends DbPDOCore {};
class DbQuery extends DbQueryCore {};
class DbMySQLi extends DbMySQLiCore {};
class Tools extends ToolsCore {};
class DateRange extends DateRangeCore {};
class Employee extends EmployeeCore {};
abstract class PaymentModule extends PaymentModuleCore {};
class Page extends PageCore {};
class Carrier extends CarrierCore {};
abstract class Controller extends ControllerCore {};
class AdminController extends AdminControllerCore {};
class FrontController extends FrontControllerCore {};
class ModuleFrontController extends ModuleFrontControllerCore {};
abstract class ProductPresentingFrontController extends ProductPresentingFrontControllerCore {};
abstract class ProductListingFrontController extends ProductListingFrontControllerCore {};
abstract class ModuleAdminController extends ModuleAdminControllerCore {};
class PrestaShopDatabaseException extends PrestaShopDatabaseExceptionCore {};
class PrestaShopPaymentException extends PrestaShopPaymentExceptionCore {};
class PrestaShopObjectNotFoundException extends PrestaShopObjectNotFoundExceptionCore {};
class PrestaShopModuleException extends PrestaShopModuleExceptionCore {};
class PrestaShopException extends PrestaShopExceptionCore {};
class Tree extends TreeCore {};
class TreeToolbarLink extends TreeToolbarLinkCore {};
class TreeToolbarSearch extends TreeToolbarSearchCore {};
class TreeToolbarSearchCategories extends TreeToolbarSearchCategoriesCore {};
abstract class TreeToolbarButton extends TreeToolbarButtonCore {};
class TreeToolbar extends TreeToolbarCore {};
class Attribute extends AttributeCore {};
class Country extends CountryCore {};
class WebserviceException extends WebserviceExceptionCore {};
class WebserviceSpecificManagementImages extends WebserviceSpecificManagementImagesCore {};
class WebserviceOutputBuilder extends WebserviceOutputBuilderCore {};
class WebserviceSpecificManagementAttachments extends WebserviceSpecificManagementAttachmentsCore {};
class WebserviceOutputJSON extends WebserviceOutputJSONCore {};
class WebserviceRequest extends WebserviceRequestCore {};
class WebserviceKey extends WebserviceKeyCore {};
class WebserviceOutputXML extends WebserviceOutputXMLCore {};
class WebserviceSpecificManagementSearch extends WebserviceSpecificManagementSearchCore {};
class LinkProxy extends LinkProxyCore {};
class SearchEngine extends SearchEngineCore {};
class CustomerAddress extends CustomerAddressCore {};
class AttributeGroup extends AttributeGroupCore {};
class Link extends LinkCore {};
class ProductSupplier extends ProductSupplierCore {};
class Media extends MediaCore {};
class ShopGroup extends ShopGroupCore {};
class ShopUrl extends ShopUrlCore {};
class Shop extends ShopCore {};
class Supplier extends SupplierCore {};
class ImageManager extends ImageManagerCore {};
abstract class AbstractCheckoutStep extends AbstractCheckoutStepCore {};
class PaymentOptionsFinder extends PaymentOptionsFinderCore {};
class DeliveryOptionsFinder extends DeliveryOptionsFinderCore {};
class CheckoutDeliveryStep extends CheckoutDeliveryStepCore {};
class CheckoutPaymentStep extends CheckoutPaymentStepCore {};
class AddressValidator extends AddressValidatorCore {};
class CheckoutSession extends CheckoutSessionCore {};
class CheckoutProcess extends CheckoutProcessCore {};
class CartChecksum extends CartChecksumCore {};
class CheckoutAddressesStep extends CheckoutAddressesStepCore {};
class ConditionsToApproveFinder extends ConditionsToApproveFinderCore {};
class CheckoutPersonalInformationStep extends CheckoutPersonalInformationStepCore {};
class CustomerThread extends CustomerThreadCore {};
class PhpEncryptionLegacyEngine extends PhpEncryptionLegacyEngineCore {};
class CacheXcache extends CacheXcacheCore {};
abstract class Cache extends CacheCore {};
class CacheMemcache extends CacheMemcacheCore {};
class CacheApc extends CacheApcCore {};
class CacheMemcached extends CacheMemcachedCore {};
class PhpEncryption extends PhpEncryptionCore {};
class Delivery extends DeliveryCore {};
class Gender extends GenderCore {};
class Translate extends TranslateCore {};
class HelperOptions extends HelperOptionsCore {};
class HelperCalendar extends HelperCalendarCore {};
class HelperImageUploader extends HelperImageUploaderCore {};
class HelperForm extends HelperFormCore {};
class HelperTreeShops extends HelperTreeShopsCore {};
class HelperKpiRow extends HelperKpiRowCore {};
class HelperTreeCategories extends HelperTreeCategoriesCore {};
class HelperUploader extends HelperUploaderCore {};
class HelperList extends HelperListCore {};
class HelperKpi extends HelperKpiCore {};
class HelperView extends HelperViewCore {};
class HelperShop extends HelperShopCore {};
class Helper extends HelperCore {};
class Tag extends TagCore {};
class CMS extends CMSCore {};
class Customization extends CustomizationCore {};
class Cookie extends CookieCore {};
class SupplyOrderReceiptHistory extends SupplyOrderReceiptHistoryCore {};
class StockAvailable extends StockAvailableCore {};
class StockManager extends StockManagerCore {};
class StockMvt extends StockMvtCore {};
class Warehouse extends WarehouseCore {};
class WarehouseProductLocation extends WarehouseProductLocationCore {};
class Stock extends StockCore {};
class SupplyOrderDetail extends SupplyOrderDetailCore {};
class StockManagerFactory extends StockManagerFactoryCore {};
abstract class StockManagerModule extends StockManagerModuleCore {};
class StockMvtReason extends StockMvtReasonCore {};
class SupplyOrderState extends SupplyOrderStateCore {};
class StockMvtWS extends StockMvtWSCore {};
class SupplyOrder extends SupplyOrderCore {};
class SupplyOrderHistory extends SupplyOrderHistoryCore {};
class Customer extends CustomerCore {};
class Hook extends HookCore {};
class CustomerAddressPersister extends CustomerAddressPersisterCore {};
class FormField extends FormFieldCore {};
class CustomerLoginFormatter extends CustomerLoginFormatterCore {};
class CustomerAddressFormatter extends CustomerAddressFormatterCore {};
class CustomerPersister extends CustomerPersisterCore {};
class CustomerForm extends CustomerFormCore {};
class CustomerFormatter extends CustomerFormatterCore {};
class CustomerAddressForm extends CustomerAddressFormCore {};
class CustomerLoginForm extends CustomerLoginFormCore {};
abstract class AbstractForm extends AbstractFormCore {};
class Context extends ContextCore {};
class Connection extends ConnectionCore {};
class QqUploadedFileForm extends QqUploadedFileFormCore {};
class ProductSale extends ProductSaleCore {};
class Group extends GroupCore {};
class PrestaShopCollection extends PrestaShopCollectionCore {};
class ValidateConstraintTranslator extends ValidateConstraintTranslatorCore {};
abstract class ModuleGridEngine extends ModuleGridEngineCore {};
abstract class Module extends ModuleCore {};
abstract class CarrierModule extends CarrierModuleCore {};
abstract class ModuleGraph extends ModuleGraphCore {};
abstract class ModuleGrid extends ModuleGridCore {};
abstract class ModuleGraphEngine extends ModuleGraphEngineCore {};
class SupplierAddress extends SupplierAddressCore {};
class Store extends StoreCore {};
class QqUploadedFileXhr extends QqUploadedFileXhrCore {};
class CMSCategory extends CMSCategoryCore {};
class Profile extends ProfileCore {};
class Upgrader extends UpgraderCore {};
class Category extends CategoryCore {};
class Meta extends MetaCore {};
class ProductPresenterFactory extends ProductPresenterFactoryCore {};
class Address extends AddressCore {};
class Windows extends WindowsCore {};
class HTMLTemplateSupplyOrderForm extends HTMLTemplateSupplyOrderFormCore {};
class HTMLTemplateOrderReturn extends HTMLTemplateOrderReturnCore {};
class HTMLTemplateDeliverySlip extends HTMLTemplateDeliverySlipCore {};
class PDF extends PDFCore {};
abstract class HTMLTemplate extends HTMLTemplateCore {};
class HTMLTemplateInvoice extends HTMLTemplateInvoiceCore {};
class HTMLTemplateOrderSlip extends HTMLTemplateOrderSlipCore {};
class PDFGenerator extends PDFGeneratorCore {};
class OrderReturn extends OrderReturnCore {};
class OrderDiscount extends OrderDiscountCore {};
class OrderHistory extends OrderHistoryCore {};
class OrderMessage extends OrderMessageCore {};
class Order extends OrderCore {};
class OrderState extends OrderStateCore {};
class OrderCarrier extends OrderCarrierCore {};
class OrderCartRule extends OrderCartRuleCore {};
class OrderInvoice extends OrderInvoiceCore {};
class OrderPayment extends OrderPaymentCore {};
class OrderReturnState extends OrderReturnStateCore {};
class OrderDetail extends OrderDetailCore {};
class OrderSlip extends OrderSlipCore {};
class RangeWeight extends RangeWeightCore {};
class RangePrice extends RangePriceCore {};
abstract class AbstractLogger extends AbstractLoggerCore {};
class FileLogger extends FileLoggerCore {};
class Uploader extends UploaderCore {};
class Guest extends GuestCore {};
class SpecificPriceRule extends SpecificPriceRuleCore {};
class CustomerSession extends CustomerSessionCore {};
class PrestaShopBackup extends PrestaShopBackupCore {};
class Alias extends AliasCore {};
class Configuration extends ConfigurationCore {};
class Manufacturer extends ManufacturerCore {};
class ConfigurationTest extends ConfigurationTestCore {};
class RequestSql extends RequestSqlCore {};
class JsMinifier extends JsMinifierCore {};
class JavascriptManager extends JavascriptManagerCore {};
abstract class AbstractAssetManager extends AbstractAssetManagerCore {};
class CssMinifier extends CssMinifierCore {};
class CccReducer extends CccReducerCore {};
class StylesheetManager extends StylesheetManagerCore {};
class Tab extends TabCore {};
abstract class ObjectModel extends ObjectModelCore {};
class ProductAssembler extends ProductAssemblerCore {};
class SpecificPrice extends SpecificPriceCore {};
class CustomerMessage extends CustomerMessageCore {};
class EmployeeSession extends EmployeeSessionCore {};
class ManufacturerAddress extends ManufacturerAddressCore {};
class CustomizationField extends CustomizationFieldCore {};
class State extends StateCore {};
class Combination extends CombinationCore {};
class Access extends AccessCore {};
class TranslatedConfiguration extends TranslatedConfigurationCore {};
class AddressChecksum extends AddressChecksumCore {};
class CSV extends CSVCore {};
class GroupReduction extends GroupReductionCore {};
class Language extends LanguageCore {};
class Image extends ImageCore {};
class Risk extends RiskCore {};
class SpecificPriceFormatter extends SpecificPriceFormatterCore {};
class Pack extends PackCore {};
class FileUploader extends FileUploaderCore {};
class CMSRole extends CMSRoleCore {};
class PhpEncryptionEngine extends PhpEncryptionEngineCore {};
class Referrer extends ReferrerCore {};
class Contact extends ContactCore {};
class Feature extends FeatureCore {};
class Mail extends MailCore {};
class Notification extends NotificationCore {};
class PrestaShopLogger extends PrestaShopLoggerCore {};
class Curve extends CurveCore {};
abstract class TaxManagerModule extends TaxManagerModuleCore {};
class TaxManagerFactory extends TaxManagerFactoryCore {};
class Tax extends TaxCore {};
class TaxRulesTaxManager extends TaxRulesTaxManagerCore {};
class TaxConfiguration extends TaxConfigurationCore {};
class TaxRule extends TaxRuleCore {};
class TaxCalculator extends TaxCalculatorCore {};
class TaxRulesGroup extends TaxRulesGroupCore {};
class Dispatcher extends DispatcherCore {};
class Validate extends ValidateCore {};
class ConfigurationKPI extends ConfigurationKPICore {};
class Attachment extends AttachmentCore {};
class ConnectionsSource extends ConnectionsSourceCore {};
class Chart extends ChartCore {};
class Product extends ProductCore {};
class Message extends MessageCore {};
class Zone extends ZoneCore {};
class Cart extends CartCore {};

La structure du projet devrait à présent être la suivante :

Et  on devrait maintenant être bon pour lancer phpstan en local et vérifier si tout fonctionne comme prévu
Via la commande suivante

php vendor/bin/phpstan analyze -c php/phpstan/phpstan.neon

Parfait la vérification locale est bien fonctionnelle !
Place maintenant à la configuration dans la CI

Configuration de phpstan dans la CI

Reste donc maintenant à ajouter cette vérification dans l’intégration continue.
Pour cela il va donc falloir rajouter les informations suivantes dans le fichier .gitlab-ci.yml

#Vérification de la qualité du code
test:code_quality:
  stage: test
  image: php:7.4
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  before_script:
    - mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
    - apt-get update -yqq
    - apt install zip unzip
    - "curl -sSLf -o /usr/local/bin/install-php-extensions https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions &&
        chmod +x /usr/local/bin/install-php-extensions &&
        install-php-extensions zip "
    # Install and run Composer
    - cd tests
    - curl -sS https://getcomposer.org/installer | php
    - php composer.phar install
  script:
    #SCRIPTS DE TESTS ICI
    - php -d memory_limit=512M vendor/bin/phpstan analyze -c php/phpstan/phpstan.neon

La ligne importante est celle après #SCRIPTS DE TESTS ICI
qui va lancer le test phpstan dans la CI 🙂

Le  reste du code permets de mettre en place une image docker et de préparer l’exécution des scripts.
Il ne nous reste plus qu’à tester que tout fonctionne comme attendu.
Commitez et poussez votre code, puis créer une merge request dans gitlab.

Et le code de la merge request devrait maintenant générer une erreur.

Et si on regarde les détails de l’erreur on peut voir que c’est bien la même que lors de nos tests locaux.

Notre vérification de la qualité du code php est maintenant en place, et nous sommes à présents en mesure de vérifier de manière simple et automatisé que notre code php ne comporte pas d’erreurs manifestes.
Corriger l’erreur et pousser le code et tout devrait être ok ensuite.

Il est possible d’aller plus loin dans cette partie à la fois en augmentant le niveau des règles de phpstan, mais également en rajoutant d’autres outils tels que :

  • Php Mess Detector ( PhpMd) qui permets de vérifier la complexité du votre code
  • Php Cs Fixer (  Qui permets de vérifier le formatage du code )

En tant que développeur front il doit également être possible de mettre en place le même genre de linting sur les fichiers javascript ou css.
Cependant je n’en fais pas assez de mon côté pour être pertinent sur cette partie, n’hésitez pas à partager vos actuces sur le sujet.

Dans la prochaine étape nous répondrons à la problématique de Comment réduire les interactions manuelles dans notre procédure de livraison de code.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *