When a plugin stores some information for a player, it usually wants to store more than one piece of data. Even if you only plan to store one datum, who knows if you won't store more in the future? To prevent trouble refactoring (i.e. renaming function/variable names, changing their expected values, etc.) your code in the future, and to store multiple pieces of data effectively, you should use data classes to represent each set of data. Using data objects instead of using a Config object also allows you to support multiple database types. In this thread, I am making a plugin which stores the player's number of joins, total online time and last online date. Therefore, a player's data object should contain these three data, and we make a class like this: (Remember, always create a new file to put a new class in it according to PSR-0, i.e. folder path == namespace and file name = class name + ".php") PHP: class PlayerData{ private $joins; private $onlineTime; private $lastOnline;} In most programs, instead of exposing their variables (actually called class properties) directly, we have getter functions and setter functions, which will change/return the class properties. This allows you to save anything you like in your class properties, as long as you return something valid in your getters. So when you change what you store in the class properties (for example, if you suddenly want to save the online time in milliseconds instead of seconds), other plugins using your plugin's API functions will not be affected if you correct the values in the getter functions. So we are setting the class properties as `private` (i.e. other classes can't change it), and adding getters (I'll leave out setters for the while since what we want is not exactly as simple as setting the value): PHP: class PlayerData{ private $joins; private $onlineTime; private $lastOnline; public function getJoins() : int{ return $this->joins; } public function getOnlineTime() : float{ return $this->onlineTime; } public function getLastOnline() : float{ return $this->lastOnline; }} Then we can write `$data = new PlayerData();` to make a new object that contains these fields. But they don't have default values set; let's add the default values. PHP: class PlayerData{ private $joins = 0; private $onlineTime = 0; private $lastOnline = -1; public function getJoins() : int{ return $this->joins; } public function getOnlineTime() : float{ return $this->onlineTime; } public function getLastOnline() : float{ return $this->lastOnline; }} What about making it like `$data = new PlayerData("username")'`, so that $data will be filled with the data for the played called "username"? It is actually the same as loading the data first then put them into an array. But if we put the code inside PlayerData.php, it looks more organized, because the code for loading player data belongs to the PlayerData class, not anywhere else like event handlers. In high-quality plugins, you shouldn't see code that directly loads data or saves data in command executors or event handlers. We can add action for the `new PlayerData(...)` call by making a method in the PlayerData class called __construct, with a parameter for the username: PHP: class PlayerData{ // These two class properties are not used for changeable variables, but // it is best if we keep a reference to these values so that we can know // what this object is about just by passing this object around. private $main; private $username; private $joins = 0; private $onlineTime = 0; private $lastOnline = 0; public function __construct(MainClass $main, string $username){ // we need to pass the main class instance too so that PlayerData knows which directory to save in // my practice is to always put these lines for defining immutable (cannot be // changed) class properties in the beginning of a constructor, because the // following method calls may use them. $this->main = $main; $this->username = $username; // I am making the path into a function because I know that // we will use it again later, when we save data. // So if we change the file path, we don't need to change two places, just one place. This avoids so many strange bugs $path = $this->getPath(); if(!is_file($path)){ // if the file doesn't exist, i.e. player never joined this server after this plugin is installed return; // do nothing, because we will leave everything to their default values } // load the data in the YAML file into memory! $data = yaml_parse_file($path); // use this instead of `(new Config(...))->getAll()`! $this->joins = $data["joins"]; $this->onlineTime = $data["onlineTime"]; $this->lastOnline = $data["lastOnline"]; } public function getPath() : string{ // Hint: remember to strtolower(), because some filesystems are case-sensitive! return $this->main->getDataFolder() . strtolower($this->username) . ".yml"; } public function getJoins() : int{ return $this->joins; } public function getOnlineTime() : float{ return $this->onlineTime; } public function getLastOnline() : float{ return $this->lastOnline; }} So let's add the save function too: PHP: public function save(){ yaml_emit_file($this->getPath(), ["joins" => $this->joins, "onlineTime" => $this->onlineTime, "lastOnline" => $this->lastOnline]);} You may be asking, why do we need to use objects if we finally have to make it into an associative array like in the last line? This is actually because using associative arrays is only needed for particular formats. For example, if you are storing data using the NBT format (the verbosity is quite pointless though), or in an SQL database, you won't need to convert them into associative arrays, but something else instead. Now, in our main class, we should have an array that is going to be filled with these PlayerData. Remember, our server runs for indefinite time; it could stop anytime, and it could run virtually forever if we design it well. So we should maintain this array (which will be reset every time plugin reloads or server restarts) at a definite size to avoid wasting memory, but not rely on this array forever. In case you do not know how this array can be used, I am going to add a PlayerData object into this array once a player joins, and delete it from the array once the player quits. Why keep it in an array? Because we may want to use this object anytime when the player is online. For example, if we are saving the number of kills of the player, we would add one to the variable every time the player kills. We only do disk I/O when player quits, or periodically if you are scared about server crashes (but not in this thread's scope of discussion). To retrieve the PlayerData from the array quickly, we would index the array using something unique for the player, for example, the username. However, I prefer using player entity IDs for two reasons: Using integers (entity IDs) is faster than using strings (names). But this doesn't consist of a real reason, because premature optimization should not be considered when there are other things to consider about. Player entity IDs are different in different sessions (change after player rejoins). So in case something strange happens and I am editing the player data even after the player has left, I would only modify the old object and not affecting the new object. Wouldn't this make bugs less visible? Yes, but if you don't overwrite the old PlayerData entry, at least you can still find it out from the memory "oh wtf why isn't this deleted already" when you var_dump() your array. So our main class should have this: PHP: class MainClass extends PluginBase implements Listener{ private $playerData = []; // other stuff public function onJoin(PlayerJoinEvent $event){ $player = $event->getPlayer(); $this->playerData[$player->getId()] = new PlayerData($this, $player->getName()); } public function onQuit(PlayerQuitEvent $event){ $player = $event->getPlayer(); // remember to check this! If player quits before he joins the server, or // if he is banned/whitelisted/server full etc., PlayerQuitEvent will fire without PlayerJoinEvent first! if(isset($this->playerData[$player->getId()])){ $this->playerData[$player->getId()]->save(); unset($this->playerData[$player->getId()]); } }} TO BE CONTINUED.
Now I'm going to talk about manipulating the values. It is actually really simple. As I said, we create getter functions instead of editing the class properties directly (like $data->joins = 0;) because we don't want to expose the "internal" values to other plugins directly, but instead through methods where we can do something before the value is modified/returned. Actually, there is no reason why other plugins want to modify the value apart from incrementing the joins, or adding something to the online time, or change the last online time to present. (Actually there can be -- they may want to reset the value, for example -- but let's not put too much thought into this simple example plugin) PHP: public function incrementJoins(){ $this->joins++; } public function addOnlineTime(float $add) : float{ $this->onlineTime += $add; } public function updateLastOnline(){ $this->lastOnline = microtime(true); } Now let's implement these methods in our main class! The joins and update last online are simple. Let me change them like this... PHP: public function onJoin(PlayerJoinEvent $event){ $player = $event->getPlayer(); $this->playerData[$player->getId()] = $data = new PlayerData($this, $player->getName()); $data->incrementJoins(); } public function onQuit(PlayerQuitEvent $event){ $player = $event->getPlayer(); // remember to check this! If player quits before he joins the server, or // if he is banned/whitelisted/server full etc., PlayerQuitEvent will fire without PlayerJoinEvent first! if(isset($this->playerData[$player->getId()])){ $data = $this->playerData[$player->getId()]; $data->updateLastOnline(); $data->save(); unset($this->playerData[$player->getId()]); } } Now... how do we count the overall online time of the player? Hmm... We have to store the join time of the player, and recalculate the online time of the player every time it is checked. But we don't save the join time of the player into the data files. So let's just put a temporary field in the PlayerData class for the join time: PHP: private $main; private $username; private $joins = 0; private $onlineTime = 0; private $lastOnline = 0; private $lastLoadTime; For reasons that you will know soon, I am calling it "lastLoadTime" instead of "lastJoinTime". You will know why soon. I want to set lastLoadTime default to the time the PlayerData is constructed (when the player starts getting online), but because PHP is stupider than Java, we can't set the default class property values in the declaration directly if it is not constant , so let's do it in the constructor: PHP: public function __construct(MainClass $main, string $username){ // ... $this->lastLoadTime = microtime(true);} Since lastLoadTime is an internal value, we don't need to create getters and setters for it. Actually, I'm going to modify getLastJoinTime(), so whenever someone uses getOnlineTime(), it will add the time the player has been online to the onlineTIme: PHP: public function getOnlineTime() : float{ $micro = microtime(true); $this->onlineTime += $micro - $this->lastLoadTime; return $this->onlineTime; } And we don't need addOnlineTime(), so let's delete it. What if someone calls getOnlineTime() multiple times? It will count the time again! So let's update lastLoadTime to current time every time we read from it: (hence why it is called "lastLoadTime" not "joinTime"): PHP: public function getOnlineTime() : float{ $micro = microtime(true); $this->onlineTime += $micro - $this->lastLoadTime; $this->lastLoadTime = $micro; return $this->onlineTime; } Now we have to update the save function too, to use getters instead of reading the values directly: PHP: public function save(){ yaml_emit_file($this->getPath(), [ "joins" => $this->getJoins(), "onlineTime" => $this->getOnlineTime(), "lastOnline" => $this->getLastOnline() ]); } Done! You can find the whole source code of this example plugin on GitHub at https://github.com/SOF3/simple-plugins/tree/master/SessionsExample
It is very easy. There are ways to do this more conveniently, but when you want to add more features to your plugin, it gets less convenient. One of the core spirits of computer programming is to "do it once and automate it forever", so never be lazy when you start writing code. Don't trust Randall for everything! This is not always true! What? You just put all properties you use into the class declaration. You don't need to use a Config at all. And the thread already explains it all!
Well, Nice nice tutorial. But again, Wouldn't mysql as a data provider be better? Then local saving it.
No. You don't need to use MySQL for no reason. If you only need to use the data on this server, you have no reason to spare extra trouble storing data into a MySQL storage.
This is true is many cases, but not always: for example, if you want to add a command to this plugin that lists the players who spent the most time online, using either SQLite or MySQL would make life a whole lot easier!
In conclusion, there are things that using a "specific" "data provider" would be best for the sake of it. But again, depends.
There can always be more than one specific data provider. SQLite and YAML are not the only two storage formats in this world.
Actually I forgot what is to be continued I thought I missed something out, but I am not sure what's wrong now. Any guesses what I didn't add?