Jump to content

Recommended Posts

I would like to give obj2 some access to obj1 where obj2 is inserted into obj1.

 

Ideally, I would like to do it once in perhaps obj2's constructor, but that will not work as obj1 is not defined when obj2 is defined.  I suppose I could pass obj1 when I call obj2's methods, however, then I need to pass obj1 for every obj2 method which I would rather not do.  Maybe I should set $this->obj2->obj1=$this in $obj1's constructor or use some sort of setter in $obj1?

 

In addition, I don't wish to give $obj2 access to all of $obj1's methods or properties, but just the single method obj1Method2() and wish this method to have access to the properties and other methods in $obj1.

 

I've included the following script to attempt to clarify what I am trying to do.

 

Thanks

<?php

$obj1=new obj1(new obj2());
$obj1->obj1Method1();


class obj1
{
    var $obj1Prop1=123;

    public function __construct($obj2)
    {
        $this->obj2=$obj2;
        //Maybe??? $this->obj2->obj1=$this;
    }
    public function obj1Method1()
    {
        $this->obj2->obj2Method1($this);
    }
    public function obj1Method2()
    {
        $this->obj1Method3($this->obj1Prop1);
    }
    private function obj1Method3($o)
    {
       //
    }
}

class obj2
{
    public function __construct($obj1=null)
    {
        //$this->obj1=$obj1;  //Won't work as $obj1 is not yet defined
    }
    public function obj2Method1($obj1)
    {
        $obj1->obj1Method2();
    }
}

$obj1 is a react client which will initiate a socket to a server, perform a loop to send data to the server, and listen to that server and perform various tasks based on what it hears.

 

Based on data received by listening to the server, the loop frequency can be changed (i.e. not only read-only but modification).

 

I wish to move the task methods out of $obj1 regarding what the client should do based on the data received by listening to the server, and put them in $obj2.  But the loop is in the encompassing object, so somehow I must modify $obj1 (aka client) from the $obj2 which was inserted into it (or consider a totally different approach).

 

I was hoping not for react specific advice, but more general advice when an injected object needs to access the object which injected it.

Sounds like a Command.

 

$obj1 doesn't need to read or write anything related to $obj2. Not directly. $obj1 interprets the response, constructs a "command" that contains the data necessary to perform whatever action is appropriate for the response, then passes it to $obj2 which executes the command. Or you say that $obj2 is itself the command and it gets executed directly (ie, $obj2->execute), cutting out the middleman. What happens from there depends on the command - maybe there's a return value, maybe not, whatever.

 

Still too abstract though, and there is no single answer at this level. How about explaining why $obj1 needs to "modify" $obj2? And what is $obj2? What kind of range of possibilities of responses and actions is there, technically speaking?

If you have some sort of setter method for object 1 that accepts instances of object 2 and vice versa you could setup a link there.

class object1 {
    private $object2
    public function setObject2($obj){
        $this->object2 = $obj;
        $obj->setObject1($this);
    }
}
class object2 {
    private $object1
    public function setObject1($obj){
        $this->object1 = $obj;
    }
}
I agree with requinix though in that everything is too abstract to really give any good advice. There's not any generic solution to giving an object access to just a specific method of another object. The closest generic idea is you just give each object a reference to the other and they both have access to the public methods.

Currently, I have three classes: Client, ClientApp, and Commands.  Maybe I should only have two classes and combine Client and ClientApp?  Commands does various commands and pretty much does its own thing, however, might need to send a message to the server using ClientApp::sendMessageToServer(), and (the whole reason for this post) needs to change the period which Client send data to server (i.e. ClientApp::work()).

 

I feel I am doing some things just wrong.  For instance, in ClientApp's constructor, I create another an object from Commands.  Per dependency injection, shouldn't I create this object in the initial script, and inject it?  But it needs to have access to the object which created it.  And around and around I go...

<?php

require 'vendor/autoload.php';

use React\EventLoop\Factory;
use React\Socket\Connection;
use MyServer\ClientApp;


$logger = new \Monolog\Logger('my_logger');
$logger->pushHandler(new \Monolog\Handler\StreamHandler(__DIR__."/logs/client.log"));

$db = new \PDO("sqlite:".__DIR__."/db/datalogger.db");
$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

$client = new Client(new ClientApp($db, $logger));
$client->start();
<?php
class Client
{
    protected $app;

    public function __construct(ClientApp $app)
    {
         $this->app = $app;
    }

    public function start()
    {
        $host = $this->app->config->urlDestination;
        $port = $this->app->config->portDestination;

        $loop = Factory::create();
        $app = $this->app;

        $this->server = @stream_socket_client("tcp://{$host}:{$port}", $errno, $errstr, STREAM_CLIENT_ASYNC_CONNECT);

        // periodically check connection to the server and recconect if connetion is lost
        $loop->addPeriodicTimer($this->app->config->reconnect_interval, function() use ($loop, $host, $port, $app) {
            if($this->server) {
                if(!isset($this->socket)) {

                    echo 'Connection successful!' . PHP_EOL;

                    $this->socket = new Connection($this->server, $loop);
                    $app->onConnect($this->socket);

                    $this->socket->on('data', function($data) use ($app) {
                        dm('data: '.json_encode($data));
                        $app->onMessage($this->socket, $data);
                    });

                    $this->socket->on('close', function() use ($app) {
                        $app->onClose($this->socket);
                        $this->socket->close();
                        unset($this->socket);
                        $this->server = false;
                    });
                }
            }
            else {
                echo 'reconnect...' . PHP_EOL;
                $this->server = @stream_socket_client("tcp://{$host}:{$port}", $errno, $errstr, STREAM_CLIENT_ASYNC_CONNECT);
            }
        });

        // periodically send data to server
        $loop->addPeriodicTimer($this->app->config->work_interval, function() use ($app) {
            $app->work();
        });

        echo 'Start Client ...' . PHP_EOL;
        $loop->run();
    }
}
<?php

namespace MyServer;

class ClientApp
{

    private $connect = false, $db, $logger, $commands;
    public $config;

    public function __construct($db, $logger)
    {
        $this->db = $db;
        $this->logger = $logger;
        $this->config=$this->getConfig();
        $this->commands = new Commands($this->config, $db, $logger, $this);
    }

    public function onConnect($conn)
    {
        dm("onConnect");
        $this->connect = $conn;
        $this->sendIdentifyCommand();
    }

    function onMessage($conn, $msg)
    {
        $message = json_decode($msg);
        if($message && $message->cmd && $message->data && method_exists($this->commands,$message->cmd) && $message->cmd!='__construct') {
            $this->commands->{$message->cmd}($message->data);
        }
        else {
            dm('Invalid message :'.$msg,true);
        }
    }

    public function onClose($conn)
    {
        dm("onClose");
        $this->connect = false;
    }

    public function work()
    {
        // Collect data and send it to the server
    }

    public function sendMessageToServer($msg)
    {
        if($this->connect) {
            $this->connect->write(json_encode($msg) . PHP_EOL);
        }
    }

    private function sendIdentifyCommand()
    {
        $this->sendMessageToServer([
            'cmd' => self::CMD_IDENTIFY,
            'guid' => $this->config->guid,
            'data' => true,
        ]);
    }
}

Does look like Client and ClientApp should be merged together, since Client really just has the one function to begin working. That would solve the problem, right?

 

Okay, I will merge the class.  Thank you.

 

No, the problem is not solved, just not committed twice.

 

I wish to inject some object into another object where the injected object needs to be able to access some capabilities of the injecti (is that a word?  Maybe I should use "recipient"?) object.  But the injecti/recipient object does not exist when creating the injected object.

 

Sorry for being obtuse.

a) Use multiple different Command objects, where each represents a distinct "command" and thus behaves differently, and provide a method in Client to change the loop frequency.

public function changeFrequency($interval) {
	// ...
}
// switch(type of command to execute) {
//	case Change Frequency:
//	case Something Which Entails Changing Frequency:
		$command = CommandThatChangesFrequency($this);
// ...
// }
$command->execute();
class CommandThatChangesFrequency implements ICommand {

	public function __construct(Client $client) {
		// ...
	}

	public function execute() {
		$command->changeFrequency($whatever);
	}

}
b) Same, except you keep the unified "Commands" thing and have to pass it every bit of data that could possibly be relevant.

class Commands {

	public function __construct($foo, $bar, Client $client, $baz) {
		// ...
	}

	public function execute() {
		// switch (whatever the command is supposed to do) {
		//	case command needs to change frequency:
				$this->client->changeFrequency($whatever);
		// ...
		// }
	}

}
c) Something else.

 

Whatever you do, Client has a public method to change the polling frequency. Then you pass Client to whatever needs it.

 

So just typical object-oriented programming.

Thanks requinix, but can we back up a bit.  How is Client injected into Commands?

<?php
$db = new \PDO("sqlite:".__DIR__."/db/datalogger.db");
$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$logger = new \Monolog\Logger('my_logger');
$logger->pushHandler(new \Monolog\Handler\StreamHandler(__DIR__."/logs/client.log"));


$commands=new Commands($client, $db, $logger); // Oops!  $client yet doesn't exist!
$client = new Client($commands);
$client->start();


class Commands {
    protected $db, $logger, $client;
    public function __construct(Client $client, $db, $logger) {
        $this->client=$client;
        $this->db=$db;
        $this->logger=$logger;
    }
    public function foo(){}
}


class Client {
    protected $commands;
    public function __construct(Commands $commands) {
        $this->commands=$commands;
    }
    public function bar(){
        $this->commands->foo();
    }
}

Good question. Worst case you call $commands->setClient or $client->setCommands, but I'm sure there's a better way.

 

What does Commands actually do? Is there some advantage to using this one class instead of creating distinct "Command" objects as needed at runtime?

Good question. Worst case you call $commands->setClient or $client->setCommands, but I'm sure there's a better way.

 

What does Commands actually do? Is there some advantage to using this one class instead of creating distinct "Command" objects as needed at runtime?

 

Yea, I considered your "worst case" options, but as you suggest, they just seem wrong.

 

Commands typically updates a database.  A PHP implemented cron access that database upon initial runtime, and stores it in memory.  When a command is received, not only is the DB updated, but the memory variable is updated as well.  Commands also updates the time period of the cron (as well as the DB).

 

Why I can create distinct Command objects at runtime?  At first, I was going to say "I can't because I am using PHP as a service so a new distinct object is not created upon each request".  But maybe I should rethink this.  Maybe?

The as-a-service thing is irrelevant. This is a question of code design: either you

 

a) Use one big class that can do every possible thing, requiring that it knows all possible information ahead of time, and call methods on it,

class Commands {

	public function __construct($everything, $and, $the, $kitchen, $sink) {
		// ...
	}

	public function updateDatabase($foo, $bar, $baz) {
		// update the database
	}

	// public function doAnotherDifferentThing(???) {
	//	...
	// }

}
if (/* need to update database */) {
	$this->commands->updateDatabase(...);
}
or

b) Use multiple small(er) classes that each do one specific thing, only needing whatever information is relevant at the time it's created, and have it be "executed" or something

class UpdateDatabaseCommand implements ICommand {

	public function __construct($db, $foo, $bar, $baz) {
		// ...
	}

	public function /* ICommand:: */ execute() {
		// use $this->db to update the database
	}

}
if (/* need to update database */) {
	$command = new UpdateDatabaseCommand($this->db, ...);
}
// execute $command
// updating the database requires that $db thing
// you could hold onto it, or put it into a factory and hold onto that instead

class DatabaseCommandFactory {

	public function __construct($db) {
		// ...
	}

	public function createUpdateCommand($foo, $bar, $baz) {
		return new UpdateDatabaseCommand($this->db, $foo, $bar, $baz);
	}

}
if (/* need to update database */) {
	$command = $this->dbcommandfactory->createUpdateCommand(...);
}
// execute $command
Whether you can perform multiple actions/commands per "request" doesn't actually affect the code.

 

 

The difference comes in when you add Client into the mix. The big unified class wants the Client ahead of time but the Client needs the unified class too, while the single class you don't do it ahead of time.

 

Of course now that I write it out, there's no particular reason why everything has to be set in the constructor. You're calling some method on Commands - pass the Client at that time.

Edited by requinix

I am sure I will eventually have a "oh yea" moment, but I am not there yet.

 

This "client service" is effectively a endless loop.  So with your second multiple small classes solution, maybe I first need to do UpdateDatabaseCommand, then I later need to do SomeOtherCommand, and then I need to do UpdateDatabaseCommand again.  I've already created the UpdateDatabaseCommand object.  I guess I "could" just create it again, but it seems excessive, and I am not sure how PHP deals with memory this being an endless loop and all.  As an alternative, I guess I could store the newly created objects in another object, and check if they are set before creating?

if (/* need to update database */) {
    $command = new UpdateDatabaseCommand($this->db, ...);
}
// execute $command

The command objects only live as long as they're needed. Once executed, or stored, or dealt with in whatever way you using them for, they're destroyed and cleaned up. They aren't persistent objects like your current Commands - ephemeral, created when needed and destroyed when handled.

 

For example, this UpdateDatabaseCommand thing would go like

1. Client receives whatever request.

2. Client interprets request to mean to update the database according to whatever.

3. $command = new UpdateDatabaseCommand(with whatever information is needed to update the database)

4. $command gets executed or whatever.

5. You could unset($command) so then it gets cleaned up immediately, but really you can just ignore it and reassign whatever new object to that variable the next time through the loop.

 

Six. Six of them. Probably could fit more in there.

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • 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.