run code in 200+ php & hhvm versions
Bugs & Features
<?php // just make this example code not pitch a bunch of errors. :P trait ItsAllFakeAnyway { public function __call($func, $args) { printf("%s::%s(%s)\n", __CLASS__, $func, json_encode($args)); } } /* Whisper TLDR: - Whisper is a time-series database format, like RRD but better. - Each Whisper file contains one or more Archives representing a series of points at varying precisions */ interface WhisperInterface { /*...*/ } class Whisper implements WhisperInterface { use ItsAllFakeAnyway; public function generateArchives() { while($header = $this->readBinFile()) { yield $this->createArchive($header); } } protected function createArchive($header) { return new Archive($header); } } interface ArchiveInterface { /*...*/ } class Archive implements ArchiveInterface { use ItsAllFakeAnyway; public function generatePoints() { while($point = $this->readBinFile()) { yield $point; } } } /* The intention of the WhisperAggregator class is to ingest metrics from many Whisper files and aggregate them into a single file. While you *can* configure the thing that initially ingests the metrics to do this *for* you, I did not do that initially and I have to backfill the aggregations as below. */ class WhisperAggregator { use ItsAllFakeAnyway; public function processWhisper(WhisperInterface $w) { foreach($w->generateArchives() as $a) { $this->processArchive($a); } } protected function processArchive(ArchiveInterface $a) { foreach($w->generatePoints() as $p) { $this->addPointToAggregatedArchive($a); } } } // eg: $a = new WhisperAggregator(); foreach(['a.wsp', 'b.wsp'] as $file) { $a->processWhisper(new Whisper($file)); } $a->writeTo('agg.wsp'); /* This works just fine until I have a case where I need to modify the point data in order to backfill aggregations that were entirely missing at certain points in time, eg a "count" aggregation. So to accomplish this I want to simply change each point value to 1. I've accomplished this as below, but I'm curious if there is an applicable design pattern for something like this that avoids the below extensions. I was considering a Decorator pattern, but I don't know how best to apply a Decorator while also maintaining the interface contract short of writing a passthrough method for every interface method, which would be far more work than extending the class. */ class WhisperShim extends Whisper { protected function createArchive($header) { return new ArchiveShim($header); } } class ArchiveShim extends Archive { public function generatePoints() { foreach(parent::generatePoints() as $point) { $point['value'] = 1; yield $point; } } } // eg: $a = new WhisperAggregator(); foreach(['a.wsp', 'b.wsp'] as $file) { $a->processWhisper(new WhisperShim($file)); } $a->writeTo('agg.wsp');
Output for 5.6.30, hhvm-3.18.5 - 3.22.0, 7.0.28 - 7.3.1
Whisper::readBinFile([]) Whisper::readBinFile([]) WhisperAggregator::writeTo(["agg.wsp"]) Whisper::readBinFile([]) Whisper::readBinFile([]) WhisperAggregator::writeTo(["agg.wsp"])