Jump to content

NotionCommotion

Members
  • Posts

    2,446
  • Joined

  • Last visited

  • Days Won

    10

Everything posted by NotionCommotion

  1. Thank you. Well said and sunk home. Contravariance (and covariance) relaxes the interface precision, but solely for the class's customer application's benefit and not the class itself, and the given class actually has to fully implement the status quo and then do more. While I technically knew this was the intent, I wasn't really thinking this way. Am I on track? Agree, it is too abstract. Back to dealing with displaying real time and historical data in some sort of chart format and refactoring how data is retrieved in order to popular a given chart. Every chart has an ID (which is used to request the chart), name, title, etc as well as one or more series (let's focus on the series). Each series will have a name and other associated data as well as multiple data nodes where each data node has a value, name, etc. There are different types of charts which have different data requirements: Line Chart: The series has sequence of data nodes and all series for a given chart have the same number of data nodes. Categorical Chart: Uses an X/Y format where a common sequence of categories is associated with each series and all series for a given chart have the same number of data nodes. Pie Chart: Each series for a given chart can have different number of data nodes. Gauge Chart: Each series only has one data node. There are two basic ways values for the charts are generated: Each series has multiple data nodes where each's value is based on a given physical parameter current values or a given physical parameter aggregate values of historic data (mean, max, etc). Each series is only associated with a single parameter, but multiple data nodes are obtained by grouping the parameter on a given time interval. There are also different derivatives of these such as grouping based on a sample size which is applicable for a line chart, or on either a given time duration (seconds in a day, week, month, etc) or on a time unit (whole days, weeks, months, etc) which is applicable for the different chart types. The database schema utilizes a base chart table with one-to-one relations to a subtable for each chart type. Multiple charts are typically presented simultaneously, and I am querying the base table with left joins to subtables (better performance, but granted does not obey open-closed principle), and handling the aggregate values of historic data as well as the group by time requirements elsewhere with a different query. Each chart type class extends an abstract Chart class and each chart type series will extend an abstract ChartSeries class or maybe just implement an interface. So, I query the database, get a record which defines a given chart's type and associated meta data and create a chart object. I also get multiple records which define the various chart series and send this data to the applicable recently created chart object's addSeries() method, and similarly get additional records which define the various series data nodes. Each record from the query provides data for the chart, series, and data node, but I just skip creating a chart or series if it was created using a previous record. For a pie chart, I just get the chart ID, name, JSON options data, timezone, etc and pass it to a PieChart's constructor to create the chart object, then get just the series ID and name and call $series=$chart->addSeries(int $id, string $name), and then get the data node data such as ID, label, and data associated with the physical parameter and send it to $series->addDataNode(int $id, string $label, Parameter $parameter). But for a group by time chart, I also get time meta data such as the total chart's time duration and feed it to a TimeChart's constructor to create the chart object. Then for each series, I also get additional time meta data such time offset in the past and call $series=$chart::addSeries(int $id, string $name, TimeObject $timeOffset), and then the same as for a pie chart call $series->addDataNode(int $id, string $label, Parameter $parameter). Note that the database will only return a single data node record for these types of charts and I've considered not implementing TimeChart::addDataNode() and instead including this information in TimeChart::addSeries(), but I feel it is cleaner being consistent with the other chart types (agree?). So, for all chart types except time grouping charts, I have method addSeries(int $id, string $name), but for time grouping charts I need method addSeries(int $id, string $name, TimeObject $timeOffset). There are also some other minor differences between different chart types, but I feel if I get this right, the others will fall into line. Maybe I am still not thinking correctly and appreciate any advice you are willing to provide. Thanks
  2. I have an interface with method: public function foo(string $arg1, string $arg2); With PHP7.4, I can have a class relax the type without errors such as Dog::foo(string $arg1, $arg2) ($arg2 is not required to be a string) I incorrectly thought I could even relax the constraint more such as Dog::foo(string $arg1){} and not even pass $arg2 because extra arguments which exceed a method's placeholders are ignored without error but I received error: Declaration of Dog::foo(string $arg1) must be compatible with AnimalInterface::foo(string $arg1, string $arg2). Are my only options to include $arg2=null in the relaxed methods and label the methods something $dummy1, $dummy2..., or pass them in an array (which I don't wish to do)? Is passing extra parameters to the same method as I am doing considered bad practice, and if so what is considered the correct way? Thanks <?php declare(strict_types=1); ini_set('display_errors', '1'); echo "PHP Version: ".PHP_VERSION.PHP_EOL; // => PHP Version: 7.4.1 interface AnimalInterface { public function foo(string $arg1, string $arg2); } abstract class Animal implements AnimalInterface { protected string $name; public function __construct(string $name) { $this->name = $name; } protected function test($method, $arg1, $arg2) { echo $method.' '.$this->name.' '.gettype($arg1).' '.gettype($arg2).PHP_EOL; } } class Cat extends Animal { public function foo(string $arg1, string $arg2) { $this->test(__FUNCTION__, $arg1, $arg2); } public function bar(string $arg1, string $arg2) { $this->test(__FUNCTION__, $arg1, $arg2); } } class Dog extends Animal { //public function foo(string $arg1){} //generates an error public function foo(string $arg1, $arg2=null) { $this->test(__FUNCTION__, $arg1, $arg2); } public function bar(string $arg1) { $arg2=$arg2??null; $this->test(__FUNCTION__, $arg1, $arg2); } } $kitty = new Cat("Ricky"); $doggy = new Dog("Mavrick"); $kitty->foo('arg1', 'arg2'); $doggy->foo('arg1', [1,2,3]); //$kitty->bar('arg1', (int)222); //will error using strict mode only $doggy->bar('arg1', (int)222); $kitty->bar('arg1', 'arg2'); $doggy->bar('arg1', [1,2,3]);
  3. Yes only if you are including the constructor method in the "some method" group. The reason I think I should be doing so is my original statement "I have an application which receives multiple requests at once and it advantageous to process them as a group."
  4. Sure, the controller, however, I need to simplify the association between the request and response. I have two parts of the application which need to know the association and there are many requests which are processed in multiple groups and the process is complicated. I could use SplObjectStorage, however, doing so would require many updates which isn't perfect. My thought was create a response in one part of the application and save a reference to it, include that response in the request, process the requests and when each is complete, update each request's individual response. Another thought is to basically do the same but use class RequestResponsePair which holds the two.
  5. The only potentially reason I could see of containing the request in the response is allows the response to see the request but I surely would want to put a wrapper around the request to prevent modification and will not bother with it. Agree it is tightly coupled, but why do you feel it shouldn't be? Maybe I am wrong, but my thought is there can never be a response unless there is a request and.... Never mind, I think you are correct.
  6. I have an application which receives multiple requests at once and it advantageous to process them as a group. I am thinking I should be doing something like the following, however, as I just came up with it on my own, would appreciate a critique. Is this a common design pattern or should I be doing something differently? Note that I have three questions asking specific questions in the below code (look for the comments with question marks). Thanks <?php class Request { private $response, $data; public function __construct(array $data) { $this->data=$data; return $this->response=new Response($this); } public function getResponse():Response { return $this->response; } public function updateResponse($results):self { $this->response->setResults($results); return $this; } } <?php class Response { private $request, $results; public function __construct(Request $request) { //I don't really see the need for this. Any need? $this->request=$request; } public function setResults($results):self { $this->results=$results; return $this; } public function getResults() { return $this->results; } } <?php class Container implements \Iterator { private $position = 0; private $requests, $response; public function __construct(WorkerObject $worker, array $inputArray) { $this->requests=[]; $this->responses=[]; foreach($inputArray as $i =>$inputData) { $this->requests[$i]=new Request($inputData); $this->responses[]=$this->responses[$i]->getResponse(); } //Should WorkerObject be responsible to execute Request::updateResponse() method? $worker->process_requests(...$this->requests); //Or should I iterate over my requests upon completion and execute updateResponse() method? } public function __construct() { $this->position = 0; } public function rewind() { $this->position = 0; } public function current() { return $this->responses[$this->position]; } public function key() { //Will this work? return $this->requests[$this->position]; } public function next() { ++$this->position; } public function valid() { return isset($this->responses[$this->position]); } } <?php //... $container=new Container(new WorkerObject, $inputArray); foreach($container as $request => $response) { //... }
  7. I wish to return the object in an array with the highest index where its index falls between between integers, and return null should one not exist. For instance, with the following and a min and max of 10,000 and 15,000, it should return OBJ4, and with a min and max of 15,000 and 20,000 it should return NULL. Any thoughts? Thanks function getObj(int $min, int $max):?OBJ { $list=[ 12314=>'OBJ1', 321=>'OBJ2', 42142=>'OBJ3', 14314=>'OBJ4', 123=>'OBJ5', 13314=>'OBJ6' ]; return getIt($list, $min, $max); }
  8. The classes with the name of the first query column are located one directory above the file making the query. Tried use aliases as well as trying to include the name space in the first column of the query but no luck. Thanks $stmt->fetchAll(\PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE);
  9. Fair enough. Sometimes a little more art than science...
  10. I found it helpful to configure all my objects in one or several bootstrap scripts. Before doing so, I would often find myself forgetting that I already had written some class which is located deep in some script and recreating it for some other need (granted, composer definitely helped in this regard, but using it wasn't always applicable). <?php $c=new Pimple\Container(getMySettings()); $c['pdo'] = function ($c) { $db = $c['settings']['mysql']; return new \PDO(bla); }; $c['foo'] = function ($c) { return new Foo( $c->get('pdo') ); }; $c['bar'] = function ($c) { return new Bar( $c->get('foo') ); }; It is all good until I find myself needing to create some new instance of some class instead of just passing some common instance to whoever needs it. class Foo { private $pdo; public function __construct(\PDO $pdo) { $this->pdo=$pdo; } public function getSomething():SomeClass { return new SomeClass($this->getAllDataFromDB($GET['id'])); } } class SomeClass { private $arr; public function __construct(array $records) { foreach($records as $record) { $arr[]=new SomeOtherClass($record); } } } I've read that one shouldn't create new objects in a given class's constructor, and while I could instead do so in some class's method and pass the object to the given class's constructor, to me it seems to really be the same thing. The three issues I have with this scenario are: Needing to remember that these classes exist as they are hidden deep in some classes. Makes it more difficult to replace one of the classes with some other class in the future. Some duplication of code. Are there other compelling reasons not to do so? Is it worth the effort to do differently? If so, how would you recommend doing so? My thoughts would either to use Pimple's factory() method are create my only factory which also relies on anonymous functions. Maybe something else? $c['someClass'] = $c->factory(function (array $record, Container $c) { return new SomeClass($record); }); Thanks
  11. Looks like Guzzle's approach is to either use CURLOPT_WRITEFUNCTION to write to a stream or CURLOPT_FILE to write to a file. if (isset($options['sink'])) { $sink = $options['sink']; if (!is_string($sink)) { $sink = \GuzzleHttp\Psr7\stream_for($sink); } elseif (!is_dir(dirname($sink))) { // Ensure that the directory exists before failing in curl. throw new \RuntimeException(sprintf( 'Directory %s does not exist for sink value of %s', dirname($sink), $sink )); } else { $sink = new LazyOpenStream($sink, 'w+'); } $easy->sink = $sink; $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) { return $sink->write($write); }; } else { // Use a default temp stream if no sink was set. $conf[CURLOPT_FILE] = fopen('php://temp', 'w+'); $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]); }
  12. Are you thinking of something like the following? $obj = new Curl(); $iterator=$obj->query('bla'); $jsonMachine=new JsonMachine($iterator); foreach ($jsonMachine as $key => $value) { var_dump($value); } class Curl { public function query(string $url) { $iterator = new CurlBytes(); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, string $str) use ($iterator) { $iterator->append($str); return strlen($str); }); curl_exec($ch); curl_close($ch); return $iterator; } } class CurlBytes implements \IteratorAggregate { private $string = ''; private $chunkSize; public function __construct($chunkSize = 1024 * 8) { $this->chunkSize = $chunkSize; } public function append(string $string) { $this->string .= $string; } public function getIterator() { $len = strlen($this->string); for ($i=0; $i<$len; $i += $this->chunkSize) { yield substr($this->string, $i, $this->chunkSize); } } }
  13. Thanks Thanks, I will change paths and not try to force cURL to expose a stream. <?php class Curl implements DriverInterface, QueryDriverInterface { public function __construct($dsn, $options = []) { //... stream_wrapper_register("curl", "CurlStream") or die("Failed to register protocol wrapper"); } protected function execute($url, $curlOptions = []) { $this->lastRequestInfo = null; $ch = curl_init(); foreach ($curlOptions as $option => $value) { curl_setopt($ch, $option, $value); } $stream = fopen("curl://CurlStream", "r+"); curl_setopt($ch, CURLOPT_URL, $this->dsn . '/' . $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_BUFFERSIZE, 256); curl_setopt($ch, CURLOPT_FILE, $stream); curl_exec($ch); $this->lastRequestInfo = curl_getinfo($ch); if (fstat($stream)['size']) { // in case of total failure - socket/port is closed etc throw new Exception('Request failed! curl_errno: ' . curl_errno($ch)); } curl_close($ch); return $stream; } } class CurlStream //implements SomeInterface? { //Reference https://stackoverflow.com/questions/1342583/manipulate-a-string-that-is-30-million-characters-long/1342760#1342760 private $buffer, $position, $varname; //Not sure about $position, $varname public function stream_open($path, $mode, $options, &$opened_path) { //$path: curl://CurlStream, $mod: r+, $options: '', $opened_path: 0 $url = parse_url($path); //["scheme"=>"curl","host"=>"CurlStream"] $this->varname = $url["host"]; $this->position = 0; return true; } public function stream_write($data) { // Extract the lines ; on y tests, data was 8192 bytes long ; never more $lines = explode("\n", $data); // The buffer contains the end of the last line from previous time // => Is goes at the beginning of the first line we are getting this time $lines[0] = $this->buffer . $lines[0]; // And the last line os only partial // => save it for next time, and remove it from the list this time $nb_lines = count($lines); $this->buffer = $lines[$nb_lines-1]; unset($lines[$nb_lines-1]); // Here, do your work with the lines you have in the buffer //var_dump($lines); echo '<hr />'; return strlen($data); } //Not sure about the remaining methods. //Reference https://www.php.net/manual/en/stream.streamwrapper.example-1.php public function stream_stat() { return ['size'=>strlen($this->buffer)]; } public function stream_get_contents() { return $this->buffer; } public function stream_read($count) { $ret = substr($GLOBALS[$this->varname], $this->position, $count); $this->position += strlen($ret); return $ret; } public function stream_tell() { return $this->position; } public function stream_seek($offset, $whence) { switch ($whence) { case SEEK_SET: if ($offset < strlen($GLOBALS[$this->varname]) && $offset >= 0) { $this->position = $offset; return true; } else { return false; } break; case SEEK_CUR: if ($offset >= 0) { $this->position += $offset; return true; } else { return false; } break; case SEEK_END: if (strlen($GLOBALS[$this->varname]) + $offset >= 0) { $this->position = strlen($GLOBALS[$this->varname]) + $offset; return true; } else { return false; } break; default: return false; } } public function stream_metadata($path, $option, $var) { if($option == STREAM_META_TOUCH) { $url = parse_url($path); $varname = $url["host"]; if(!isset($GLOBALS[$varname])) { $GLOBALS[$varname] = ''; } return true; } return false; } }
  14. If a response is large, json-machine is used to decode the string instead of json_decode(). Based on the application's configuration, $this->stream can either be set using Guzzle or just PHP's cURL library. I've got the Guzzle version working, but not the cURL version. private function getParsedResults() { if(is_null($this->parsedResults)) { if(fstat($this->stream)['size'] > self::MAX_JSON_DECODE) { $this->parsedResults=[]; foreach (JsonMachine::fromStream($this->stream) as $key => $value) { $this->parsedResults[$key] = $value; } } else { $this->parsedResults=json_decode(stream_get_contents($this->stream), true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \InvalidArgumentException('Invalid JSON'); } } $this->validate($this->parsedResults); } return $this->parsedResults; }
  15. The following will create a PHP stream. $response = $this->guzzleHttp->get($url); $stream = \GuzzleHttp\Psr7\StreamWrapper::getResource($response->getBody()); How can I return a stream using just PHP's native cURL library?
  16. I gave json-machine a try and it works great. You just pass it the steam and an optional json pointer and iterate over the elements.
  17. Thanks kicken, Not sure what will be enough memory, but will give it a try. It is for a one-time use application to fix some data. Thanks requnix, Ever try doing it? If so, did it work well and relativity simple? Looks like there might be a few other options. Haven't tried but will and will let you know if they work. https://github.com/pcrov/JsonReader/wiki/JsonReader-API#psr7streampsrhttpmessagestreaminterface-stream-void https://github.com/salsify/jsonstreamingparser with https://github.com/cerbero90/json-objects https://github.com/halaxa/json-machine Another one: https://github.com/MAXakaWIZARD/JsonCollectionParser. Also uses https://github.com/salsify/jsonstreamingparser
  18. I am using Guzzle as a HTTP client, and the following script results in the following error: $response = $this->httpClient->request('GET', "http://$this->host:$this->port/query", ['query' => $data]); $body = $response->getBody(); $rs=json_decode($body, true); Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 136956008 bytes) in /var/www/vendor/guzzlehttp/psr7/src/Stream.php on line 80 What are the work arounds? Instead of trying to convert it into an array all at once, how can I do so in pieces? I know the expected format so it seems I will need to read just the appropriate amount of bytes and then decode parts at a time. Seems like a pain. Are there any classes designed to do so, or can any of the following Guzzle built in methods be used? Thanks Guzzle Response methods: __construct getStatusCode getReasonPhrase withStatus getProtocolVersion withProtocolVersion getHeaders hasHeader getHeader getHeaderLine withHeader withAddedHeader withoutHeader getBody withBody Guzzle Body methods: __construct __destruct __toString getContents close detach getSize isReadable isWritable isSeekable eof tell rewind seek read write getMetadata
  19. I've never head the term "custom attributes", but suppose I do so. User A is named Bob, is 5 feet tall and 38 years old, and has special skills: has a hypnotism rate of 45 neurons per second, and a clairvoyance rating of 5 / 10. User B is named Jill, is 6 feet tall and 28 years old, and has special skills: has a rank of 3 of understanding of quantum physics and a shoe size of 25 and a half meters. User C is named Tom, is 4 feet tall and 12 years old, and has special skills: has fourteen years worth of towel folding experience User D is named Jane, is 5 feet tall and 18 years old, and has special skills: can eat twenty three beer battered brauttwerst in 48 seconds.
  20. Thank you Kicken, Your understanding is for the most part accurate. Did I correctly capture the reasons why you said "In general I'd say your common object should only contain the common properties"? Requires table inheritance if given common object is injected with specific object of different class/table or there is type of common object which doesn't require an injected object. Consistent with one:many Can easier be convert to a one:many
  21. Thanks Zane and maxxd, I am trying to keep track of user provided configuration settings of something. I might be wrong, but it seems like it could be just about anything, so I used cars and motors as an example, but did a poor job communicating that they were user settings. Keeping with my car example, user settings might be current mileage, serial number, color (not necessarily from the factory), and even spark plug torque (assuming cars still have spark plugs). The point is that the user provides settings for their particular something and these settings are saved (latest settings only and no audits required) in the DB. No issue yet and I have done this often. But then there are multiple variations of this something which require their own class and these classes have some properties common to all of them, but have other properties unique to their particular variation. In the past, I've used inheritance, but I am now trying to default to injection when possible, and thus created a single class which contains all the common properties and then inject some small object which represents the other properties unique to each particular variation. Data associated with each instance of this common object as well as the injected object is random user provided data, and therefore it seemed to me that I needed some one-to-one relationship between the two. Agree? Ditching the car/motor analogy, the same question is "If a common object contains a single unique specific object, what reasoning should one use to determine whether the common table should contain specific entity ID or whether the specific table should contain the common entity ID?" Thanks
  22. Yes, each GatewayDataSource gets its own unique gateway, and "linking" as you say was my intention. Would you agree this is a one-to-one relationship between source and gateway, and gateway to device? Is there some de facto standard where to locate the FK for these two links, or are there application specific details one must consider? For instance, to quote Zane, "Cars contain a motor. Motors do not contain cars. Therefore, put a column for motor in the Cars table and link through the various motor ids" makes sense, however, if some app was all about artificial brains, it might make more sense that a brain has a host (aka body), and one should do differently. PS. No, I am not creating a brain and maybe I should strike this obscure example! Modbus uses an 8 bit address to identify a device on the network and 16 bit registrars to locate some specific data in the device. Bacnet uses a 32 bit value to identify the device and another 32 bit value to represent something in the device (it actually uses ID and type, but I think this can be disregarded for this discussion). These 3rd party modbus and bacnet devices are not managed by my application and their associated identifying addresses are set outside of the application. A 3rd party modbus device is interfaced to by one of my ModbusGateways and a 3rd party bacnet device is similarly interfaced to by one of my BacnetGateways. Since ModbusGateways and BacnetGateways are also modbus devices and bacnet devices respectively and there are requirements to keep addresses for both unique on a given network, I created these ModbusDevice and BacnetDevice entities/tables which are used for both 3rd party devices and gateways alike. A device associated with a gateway can be deleted by the user and removed from the database, however, the devices in my application associated with some 3rd party controller should not be deleted from the database but only tagged as deleted. If a deleted 3rd party device is later added back as identified by its unique address, then the same PK associated with the specific data points in the device must be used as it is also used as a natural key by a historian. Hope I didn't totally confuse you (I certain confused myself ) Thank you
  23. Maxxd, where can I get a Honda with a Hemi or a V8? Been looking for one, but they don't seem to be available I see your and Barand's point and do not disagree for the common attributes, but it does not address the specific attributes such as the engine serial number or the torque I cinched down the spark plugs which make it unique. In hindsight my abstract example was flawed as all of the properties were common which should utilize a many-to-many except for the serial number, and apologize for the poor example. For my real application, I have device identifier, timeout settings, buffer size, etc, for some types of devices and other similar attributes for different types of devices, which are all specified by the user and used to configure each device. Maybe I should have included these attributes in some parent table/entity, however, injection brought some flexibility and simplification which leaves me with these one-to-ones.
  24. And a couple of unique constraints can be used to limit to one on both sides. Then, getters and setters will need to filter to the first entry in each collection. But why bother?
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.