Closed Xaraknid closed 9 years ago
Merged into the Development branch without testing.
A few thoughts right away, on my first look:
Rather than using an underscore in the autoload to specify the path to the file, I'd rather use namespaces. This way, the actual class name stays short, and if we set up an autoloader properly, it can co-exist with other autoloading methods out there like the short-sighted PSR-0 scheme.
Second, I'm trying to move away from having the user logic extend the core server. Instead, I plan on having the core server be a stand alone object created by the application object. This will isolate classes to allow unit testing, prevent naive overwriting of properties or methods already within the core server, and help the system become more modular and easier to maintain. Thus, rather than extending an abstract class, we'll be enforcing the availability of certain methods through implementing interfaces.
Great work in the right direction. I love the traits. I'm going to use this as the basis of continuing forward.
Just let you know google search you are third behind a tutorial where you are first within :). So you are the second choice, continue the good work.
This aside : Yeah indeed using namespace should be great.
Personally I really love the way it is now. For me it's by far the easier one to use. If you worried that some internal method can be naively overwrited we can simply use FINAL on that method preventing naive overwriting as when the user run the program it will crash anyway so he could use another name for is function and if he indeed want to overwrite a function he could by removing FINAL but that way he do it in purpose.
Also I think extending the core server is a great way. That way only the usefull method for user logic are used. Keep code clean for user without all behind the scene code.
I'm not sure interface is usefull for your project, seem to me a layer of complexity as it only purpose is to declaration of function to be implemented in another class.
For me why i choose this project over ratchet and wrench is I did not need 3 hours to retrace one simple function because it's delocalized, is in multiple place an extend of an extend over multiple folder where the whole simple one function is a meshed up of multiple little function all in different folder.
Anyway I'm not sure where you want to go. The only advice I will give you is keep it simple and do not end up like another ratchet or wrench for me it's strong point is it's simplicity.
Continue your good work !!!
A little question : what prevent you to implement unit test with the current design ?
I'm actually looking into what it would take to start up some unit testing right now. The main pain point with unit testing is that, if using a test framework like PHPUnit, you can't make a mock-up class of all of the functionality if you're extending a base class rather than using a separation of concerns system such a dependency injection... It would be testing with a full featured server running, which may make the tests less than reliable.
After reading your comments, I've decided that it's time for me to start up a new project that uses Websockets again. (The last project I used it for was a work project for a company that I'm no longer with.) I need to stay grounded with what the pain points are in this project.
I actually can't help you with PHPunit i'm clueless with that :). But I found someone in same trouble as you http://stackoverflow.com/questions/18595755/phpunit-and-extended-classes if that can help you in any way.
The reasonning why I like this design I mean by that : application method being upfront ( onmessage,onclose,onopen ). and core application behind the scene.
Is that only usefull method for dev are easily available within testwebsocket.php.
Unfortunately websocket server are an active library by that I mean unlike any other library you can use like DB, cache, etc. These are passive library they do nothing by their own they wai passively that you call them for a function.
In our concern a server work on it's own and we are the one waiting that the server call himself it's own function. More precisely the hearthbeat here was (onmessage,onclose,onopen). So any DB or interaction with the server are made on those function.
There is a solution if you really want to get rid of extending the core function is instead of having upfront those function within testwebsocket or ws_withprotocol we move those behind the scene. Maybe in Application folder.
So the upfront file testwebsockets will only have
$echo = new websockets("0.0.0.0","8080");
some way to specify the application to use
$echo->run();
So user will end up coding is server in phpwebsocket/application/myapplication.php
Personally I prefer having upfront the heartbeat function. Keep phpwebsocket cleaner in my own opinion. But maybe there is a way to have this function available without using extend that way it'll keep those function upfront and will be easier for you to implement PHPUnit.
It seem Ratchet use this method and wrench use behind the scene application.
What I'm looking to do is more along the lines of
#!/usr/bin/env php
<?php
require_once('core/bootstrap.php'); // Includes the absolutely required core files, autoloader, and verifies the sanity of the environment.
class MyChatApplication
{
private $server;
function __construct()
{
$this->server = new \Phpws\Core\Websockets($this);
// The only actually required handler
$this->server->registerHandler('onMessage', array($this, 'myMessageHandler'));
// All below are optional
$this->server->setUserClass('MyChatUser'); // The user class would be a prime candidate for implementing an interface, since the server needs to be able to store and retrieve the socket resource, among other things, and an interface would ensure that the correct methods exist.
$this->server->registerHandler('onConnect', array($this, 'myConnectHandler'));
$this->server->registerHandler('onClose', array($this, 'myDisconnectHandler'));
$this->server->registerHandler('onError', array($this, 'myErrorHandler'));
$this->server->registerHandler('onTick', array($this, 'heartbeat'));
$this->server->setListenPort('8080'); // Not sure what I'd have the default be. Probably 6680 since it's close to IRC, but not between 6660 and 6670, and 80 is a good HTTP related number.
}
public function myMessageHandler($user, $message)
{
$outMessage = $user->getNick() . ': ' . $message;
$connUsers = $this->server->getConnectedUsers();
foreach ($connUsers as $cUser)
{
if ($cUser->getChannel() == $user->getChannel())
{
$this->server->send($cUser, $message);
}
}
}
public function playThatFunkyMusic();
{
$this->server->run()
}
// ... Additional methods snipped for brevity.
}
$app = new MyChatApplication();
$app->playThatFunkyMusic();
Of course, the class could be in a different file, either explicitly loaded in a require_once()
call, or handled by the autoloader... For instance, if the class were in ./apps/mychatapplication.php
(and thus had the namespace Phpws\Apps
:
#!/usr/bin/env php
<?php
require_once('core/bootstrap.php');
$app = new \Phpws\Apps\MyChatApplication();
$app->playThatFunkyMusic();
This is different from how I was originally intending to change things, where the core server would select the application to run based on the URI passed in for the request... However, it would lose its current simplicity and still be just as difficult to extend, so I'm opting to keep the app as the very first part called in the script, and if there were some need for multiple applications on the same server, there would have to be some other system in place to separate the logic (such as if ($user->getConnectionURI() == 'my/expected/path')
or something similar in the onMessage handler.)
On the topic of autoloading: My plan is to implement a system that is similar to the PHP-FIG's autoloading recommendations, but with an extra, "standards" breaking layer that allows it to interoperate with other autoloading systems.
(I can't bring myself to call any of PHP-FIG's writings a "standard" without putting the word "standard" in quotes, because an actual standard is well thought out, thoroughly discussed, and exists to solve a problem rather than simply restrict people to your way of doing things.)
More thoughts on autoloading: https://r.je/php-psr-0-pretty-shortsighted-really.html (PSR-4 still has the exact problem expressed in this article) https://r.je/php-autoloaders-should-not-be-case-sensitive.html
I brainstrom a little to find the shortest and simplest way to handle the handler the way you want :
The simplest way it's a shame how easy it is. Simply by moving the userclass contained handler in same layer of protocol are. So instead of injecting websockets class to userclass we will inject handler to websocket class.
in folder handler <-- developper will edit this file to their liking editing onmessage,onopen,onclose,ontick
Then the testwebsocket.php will be there to load the websocket class, maybe also some configuration and if needed there we specify wich handler class we want to load.
What do you think ?
I like the idea of a router sending messages and events to controllers. I've been mulling that idea in my mind for a while.
I'm reworking some things to make it as easy and as default friendly as possible, so that if a developer doesn't need routing, they won't even know that their messages are going through any sort of router... and making the router easy and direct enough to read that if a developer really needs to dig in and see what's happening, it's evident without having to go through seventy billion layers of abstraction.
Trait are good for current design as everything is on same layer class and we only overwrite method. Trait are like multiple extends in a class but the problem is they work at compile time so we can't use them dynamically.
I think the best course of action will be to divide the big class containning everything into smaller class based on the layer they are working .
Will look like that :
-Core
run() ->eventloop
..............run() ..................................->buffer
..............addwritewatcher()...............cb_read()
..............removewritewatcher()
will eventually lead to the app class made by dev. Contained into an array
->apps['default']->onmessage() ->apps['/echobot']->onmessage()
Permissive mode use default for /* Strict mode kill the connection if 'Get' is not one allowed by server.
I be able to refactoring it this way up to apps level. Need to move send,broadcast and some other method outside websockets, because each apps are isolated class from each other and from eventloop also.
Because of this isolation we will be able to have a list of clients inside each apps for outgoing data.
Also the websockets are no longer an abstract class in this structure.
Right now the loading of this project is simple as that :
$test = new phpwebsocket("0.0.0.0","8080",'libev'); //host,port,eventloop
$test->addapp("/admin",new admintool());
$test->addapp("/echobot",new echobot());
$test->addapp("/chat",new chatsystem());
$test->run();
WARNING : This revision use 'trait' it's now required php >= 5.4. Those who still use php < 5.4 should continue using the former version of php-websocket. I think before merging it you should save the current master into another branch for those still using php < 5.4.
The list of modification in this revision and future tought :
Now php-websocket are modulable using this structure ./ <- the main script to be called ./core <- the core of php-websocket ./eventloop <- the eventloop selector include 2 functionnal ( socket,libev ) soon libevent ./protocol <- All protocol you want to support on the server ( note that is not MANDATORY ) ./extensions <- For future extensions supporting right now very few available not on all browser like permessage-deflate ./toolbox <- The developper toolbox will be shipped empty you will be able to put class / trait to be use in main script
note : you MUST name class / trait by the directory he was in like : core_websockets
the only other information with some value will be agent for logging purpose I guess but are not save right now.