www.hubleto.eu Reddit User Guide Stack Overflow LinkedIn Report an issue

Extendibles

An extendible is a small class in the Extendibles/ folder of an app that contributes a list of items to a feature of another app. The app that owns the feature collects the items from all enabled apps. The collecting app does not know the contributors in advance, so you can extend the menu, the product types, the tools or the context help of Hubleto just by adding a file to your app.

How it works

  1. The app that owns the feature calls collectExtendibles('Name') on its Loader.
  2. For each enabled app the container creates the class <app namespace>\Extendibles\Name (if it exists), sets its $app property and calls getItems().
  3. The returned arrays are merged into one array, which the collecting app uses.
framework: src/App.php (excerpt)
  public function collectExtendibles(string $extendibleName): array
  {
    $items = [];
    foreach ($this->appManager()->getEnabledApps() as $app) {
      try {
        $extendible = $this->getService($app->namespace . '\\Extendibles\\' . $extendibleName);
        $extendible->app = $app;
        if ($extendible instanceof Extendible) {
          $items = array_merge($items, $extendible->getItems());
        }
      } catch (\Throwable $e) {
        // do nothing
      }
    }

    return $items;
  }

Apps that do not have the class are skipped silently, so an extendible is always optional.

NOTE The items are merged with array_merge(). Items with string keys override each other, but items with integer keys are renumbered ([10 => 'a'] becomes [0 => 'a']). Return a plain list or use string keys when the keys matter.

Extendibles collected by the community apps

Extendible name Collected by getItems() returns Example
AppMenu Desktop menu entries (app, url, title, icon) of the app PriceLists\Extendibles\AppMenu
Tools Tools tools shown on the Tools page Settings\Extendibles\Tools
ContextHelp Help context help: URL fragment → documentation path per language Customers\Extendibles\ContextHelp
MailTemplateVariables Mail variables that can be used in e-mail templates Deals\Extendibles\MailTemplateVariables
ProductTypes Products types of products and where they come from Deals\Extendibles\ProductTypes
PriceListItemAdapters PriceLists adapters that map item models to the fields of the price calculator PriceLists\Extendibles\PriceListItemAdapters
PriceCalculatorConsumers PriceLists models that use the price calculator PriceLists\Extendibles\PriceCalculatorConsumers

Extendibles of the community apps.

Contributing to an extendible

Create the class in Extendibles/ of your app, extend Hubleto\Framework\Extendible and return the items. The property $this->app is the Loader of your app, use it to translate texts.

Adding the app to the desktop menu
apps/PriceLists/Extendibles/AppMenu.php
<?php

namespace Hubleto\App\Community\PriceLists\Extendibles;

class AppMenu extends \Hubleto\Framework\Extendible
{
  public function getItems(): array
  {
    return [
      [
        'app' => $this->app,
        'url' => 'price-lists',
        'title' => $this->app->translate('Price lists'),
        'icon' => 'fas fa-tags',
      ],
      [
        'app' => $this->app,
        'url' => 'price-lists/items',
        'title' => $this->app->translate('Prices'),
        'icon' => 'fas fa-list',
      ],
      [
        'app' => $this->app,
        'url' => 'price-lists/vat-rates',
        'title' => $this->app->translate('VAT rates'),
        'icon' => 'fas fa-receipt',
      ],
      [
        'app' => $this->app,
        'url' => 'price-lists/simulator',
        'title' => $this->app->translate('Price simulator'),
        'icon' => 'fas fa-flask',
      ],
    ];
  }
}
Adding variables to e-mail templates
apps/Deals/Extendibles/MailTemplateVariables.php
<?php

namespace Hubleto\App\Community\Deals\Extendibles;

class MailTemplateVariables extends \Hubleto\Framework\Extendible
{
  public function getItems(): array
  {
    return [
      'deal.identifier',
      'deal.price_excl_vat',
    ];
  }

}
Adding a tool
apps/Settings/Extendibles/Tools.php
<?php

namespace Hubleto\App\Community\Settings\Extendibles;

class Tools extends \Hubleto\Framework\Extendible
{
  public function getItems(): array
  {
    return [
      [
        'title' => $this->app->translate('Log viewer'),
        'icon' => 'fas fa-screwdriver-wrench',
        'url' => 'settings/log-viewer',
      ]
    ];
  }

}
Adding a link to the context help
apps/Customers/Extendibles/ContextHelp.php
<?php

namespace Hubleto\App\Community\Customers\Extendibles;

class ContextHelp extends \Hubleto\Framework\Extendible
{
  public function getItems(): array
  {
    return [
      'customers' => [
        'en' => 'en/apps/community/customers',
      ],
    ];
  }

}

In your custom app the files have the same shape, only the namespace changes:

<?php

namespace Hubleto\App\Custom\CarRental\Extendibles;

class AppMenu extends \Hubleto\Framework\Extendible
{
  public function getItems(): array
  {
    return [
      [
        'app' => $this->app,
        'url' => 'car-rental',
        'title' => $this->app->translate('Car rental'),
        'icon' => 'fas fa-car',
      ],
    ];
  }
}

Creating your own extendible

Create an extendible when your app needs other apps to provide data to it. PriceLists lets any app describe how its document items map to a price list:

1. Collect the items from all apps
apps/PriceLists/Services/ItemAdapterRegistry.php (excerpt)
  public function getAdapters(): array
  {
    if ($this->adapters === null) {
      /** @var PriceListApp|null $app */
      $app = $this->appManager()->getApp('Hubleto\\App\\Community\\PriceLists');
      $this->adapters = $app ? $app->collectExtendibles('PriceListItemAdapters') : [];
    }
    return $this->adapters;
  }
2. Let other apps contribute
apps/PriceLists/Extendibles/PriceListItemAdapters.php
<?php

namespace Hubleto\App\Community\PriceLists\Extendibles;

use Hubleto\App\Community\Cashdesk\Models\Receipt;
use Hubleto\App\Community\Cashdesk\Models\ReceiptItem;
use Hubleto\App\Community\Deals\Models\Deal;
use Hubleto\App\Community\Deals\Models\Item as DealItem;
use Hubleto\App\Community\Invoices\Models\Invoice;
use Hubleto\App\Community\Invoices\Models\Item as InvoiceItem;
use Hubleto\App\Community\Orders\Models\Item as OrderItem;
use Hubleto\App\Community\Orders\Models\Order;

/**
 * Declares document-item models that PriceList can price. Other apps can provide their own
 * Extendibles\PriceListItemAdapters class with the same shape (see community-apps-patches.md).
 *
 * Adapter keys:
 *   productField, quantityField, priceField, discountField, vatField,
 *   outputMap (canonical calculator key => column; leave empty when the item model recalculates its own prices),
 *   parent => model, fkField, customerField, supplierField, currencyField, dateField,
 *             typeField, purchaseValue, shopField, cashRegisterField
 */
class PriceListItemAdapters extends \Hubleto\Framework\Extendible
{
  public function getItems(): array
  {
    return [
      OrderItem::class => [
        'productField' => 'id_product', 'quantityField' => 'amount',
        'priceField' => 'unit_price', 'discountField' => 'discount', 'vatField' => 'vat',
        'outputMap' => [],
        'parent' => [
          'model' => Order::class, 'fkField' => 'id_order',
          'customerField' => 'id_customer', 'supplierField' => 'id_supplier', 'currencyField' => 'id_currency',
          'dateField' => 'date_order', 'typeField' => 'purchase_sales', 'purchaseValue' => Order::PURCHASE_ORDER,
        ],
      ],
      DealItem::class => [
        'productField' => 'id_product', 'quantityField' => 'amount',
        'priceField' => 'unit_price', 'discountField' => 'discount', 'vatField' => 'vat',
        'outputMap' => [],
        'parent' => [
          'model' => Deal::class, 'fkField' => 'id_deal',
          'customerField' => 'id_customer', 'currencyField' => 'id_currency',
        ],
      ],
      InvoiceItem::class => [
        'productField' => 'id_product', 'quantityField' => 'amount',
        'priceField' => 'unit_price', 'discountField' => 'discount', 'vatField' => 'vat',
        'outputMap' => [],
        'parent' => [
          'model' => Invoice::class, 'fkField' => 'id_invoice',
          'customerField' => 'id_customer', 'currencyField' => 'id_currency', 'dateField' => 'date_issue',
        ],
      ],
      ReceiptItem::class => [
        'productField' => 'id_product', 'quantityField' => 'quantity',
        'priceField' => 'unit_price_excl_vat', 'discountField' => null, 'vatField' => 'vat_percent',
        'outputMap' => [
          'unit_price_excl_vat' => 'unit_price_excl_vat', 'unit_vat' => 'unit_vat', 'unit_price_incl_vat' => 'unit_price_incl_vat',
          'price_excl_vat' => 'total_price_excl_vat', 'price_vat' => 'total_vat', 'price_incl_vat' => 'total_price_incl_vat',
        ],
        'parent' => [
          'model' => Receipt::class, 'fkField' => 'id_receipt', 'cashRegisterField' => 'id_cash_register', 'dateField' => 'created',
        ],
      ],
    ];
  }
}
Guidelines

Related pages