Skip to content

5: Services

Sebastian Schendel edited this page Jan 4, 2021 · 2 revisions

In a complex website, you may want to have a central place where you can define general functions and attributes that can be used by multiple components.

Twack has a concept for this: Services! Every service class is a singleton that is globally managed by Twack. If multiple components ask for a service, they will all get the same instance. Services aren't meant to render anything, so they have no view.

You can define your own service-classes in site/templates/services/. This path is configurable in the module's configuration.

Here is, for example, a news service that can retrieve, sort and filter our news-item pages.

<?php
namespace ProcessWire;

class NewsService extends TwackComponent {
  public function __construct($args) {
    parent::__construct($args);
  }

  public function getNewsPage() {
    return wire('pages')->get('template.name="newsroom"');
  }

  public function getArticles($args = array()) {
    $output = new \StdClass();
    $articles = $this->getNewsPage()->find('template=newsitem');

    if (isset($args['sort'])) {
      $articles->filter('sort=' . $args['sort']);
    } else {
      $articles->filter('sort=-date');
    }

    // Filter by Tags:
    if (isset($args['tags'])) {
      if (is_string($args['tags'])) {
        $args['tags'] = explode(',', $args['tags']);
      }

      if (is_array($args['tags'])) {
        $articles->filter('tags='.implode('|', $args['tags']));
      }
    }

    // Filter by search query:
    if (isset($args['query'])) {
      if (is_string($args['query'])) {
        $query = wire('sanitizer')->text($args['query']);
        $articles->filter("title|name|einleitung|inhalte.text%={$query}");
      }
    }

    // Save the original count of all items before applying limit and offset:
    $output->count = $articles->count;

    // Index of last element of the output:
    $output->lastElementIndex = 0;

    // Apply Limit and Offset:
    $limitSelector = array();

    if (isset($args['start'])) {
      $limitSelector[] = 'start=' . $args['start'];
      $output->lastElementIndex = intval($args['start']);
    } elseif (isset($args['offset'])) {
      $limitSelector[] = 'start=' . $args['offset'];
      $output->lastElementIndex = intval($args['offset']);
    } else {
      $limitSelector[] = 'start=0';
    }

    if (isset($args['limit']) && $args['limit'] >= 0) {
      $limitSelector[] = 'limit=' . $args['limit'];
      $output->lastElementIndex = $output->lastElementIndex + intval($args['limit']);
    } elseif (!isset($args['limit'])) {
      $limitSelector[] = 'limit=12';
      $output->lastElementIndex = $output->lastElementIndex + 12;
    }

    if (!empty($limitSelector)) {
      $articles->filter(implode(', ', $limitSelector));
    }

    // Are there any more posts that can be downloaded?
    $output->hasMore = $output->lastElementIndex + 1 < $output->count;

    $output->articles = $articles;

    return $output;
  }
}

site/templates/services/news_service.class.php

We can call $this->getService('NewsService'); in many components and then use the service's functions. Twack will look for the class in our services-directory and make a singleton-instance out of it. I think, to avoid name-conflicts with the "normal" components its a good convention to add "Service" as a suffix to every service-class.


➡️ Continue with 6: Named components
⬅️ Back to 4: Asset handling