Standards
When using SlenderApi, if you stick to theses standards, it will make your code easier for new developers to jump into, keep everyone on the same page, allow your code base to expand as your project grows, make sure your performance is fast, and keep your code clean and easy to read.
Directory Structure & Files
Most of your code will go in the Controllers and Models directories. For generic helper files you will be using the Helpers directory. In the Controllers directory, create sub directories for your files. That's pretty much it. We wanted to keep this simple, so a lot of the heavy lifting for wiring up the connection of files is done in the background with the SlenderApi framework code.
Controllers //Example for users: create a Controllers/Users directory. Create Controllers/Users/UsersController.php namespace SlenderApi\Controllers\Users; //Set your resource name in your controller //Now you can call /api/users in your API. $resourceName = 'users' //Create methods in your controller. //Now you can call /api/users/register in your API. //Keep your methods to one line of code, put your code in the UserLib class. public function register() { $this->usersLib->register(); } Models //If you have a table named "user", create a model named UserModel. //In the user model, set the table name it references here. parent::__construct('user', $db, $authenticatedUserId); //To use this model, in your library, add this: use SlenderApi\Models as Models; //To call the model, use this: $this->userModel = new Models\UserModel($this->db, null); //You can pass the id as the second parameter //to set the object to a specific record: $this->singleUserModel = new Models\UserModel($this->db, 22); Libraries //Since the register example above is in the usersLib class, //this is where all of your heavy lifting will be done. //This will keep your controller nice and clean. $this->usersLib->register(); //Here is how you would create your users library class. Example: Controllers/Users/UsersLib.php //Make sure and set the namespace namespace SlenderApi\Controllers\Users; //If you need to divide UserLib.php into multiple classes, add more: Example: Controllers/Users/UserLibAuth.php Helpers //Helpers should be used if you have something super generic //that you want all of your libraries to reuse, or if you need //a wrapper for a 3rd party library. Example: Helpers/FileHelper.php or Helpers/StripeHelper.php Naming Files & Directories //All directories should be PascalCase: Controllers/Users //All files should be PascalCase: UserController.php
Code Formatting
It is important that all the developers in a project are on the same page. These are the standards used for SlenderApi for code indentation, naming, brackets, new lines, classes, methods, and syntax. The most important thing is to have a standard for the readability of your code, no matter what the standard is. We thought this was a simple, clean, and logical way to format code. How you name things is up to you, but some options would be to use arrVariable for arrays, strVariable for strings, and so on, so you can quickly tell the type of any variable. Get and Set are used for getting and setting values, such as getValue(), setValue(). There are many more little things you can do for formatting standards, but this should be enough to get you going.
//Here is a slimmed down version of a UsersController class: namespace SlenderApi\Controllers\Users; use SlenderApi\Controllers as Controllers; //The bracket goes on the next line for classes and methods. //The class name is PascalCase. class UsersController extends Controllers\Controller { //4 spaces for indentation //All variable types are defined public function __construct(object $db, Request $request, Response $response) { parent::__construct($db, $request, $response); //Class variables are camelCase //Try to group chunks of code together, always use only one line break $this->usersLib = new UsersLib($this); $this->usersLibAuth = new UsersLibAuth($this); $this->setMethodPermissions(); } //Method names should camelCase public function setMethodPermissions() { //You can format arrays however you want, but try to make the readable //Depending on the use case, you can use camelCase or snake_case. $this->methodPermissions = array( 'save' => array('CR','UP'), 'updatePassword' => array('UP'), 'select' => array('RE'), 'selectColumns' => array('RE'), 'archive' => array('UP') ); } } //Models can be handled in a different way //Tables and fields should be snake_case //If you name your variables associated to database fields in snake_case, //then you can easily tell that variable is associated to the database. public function selectUserByLoginUsername(string $email_address): array { //SQL statement operators should be UPPER CASE //Fields and tables are snake_case $fields = 'id, email_address, user_name, password, first_name'; $cond = 'WHERE email_address = :email_address AND disabled = 0 AND archived_date IS NULL'; //Local variable should camelCase, unless associated to a database field which should be snake_case. $whereValues = array( 'email_address' => $email_address ); return $this->select($cond, $whereValues, $fields); } //Make sure and set your return type to void if nothing is returned in a method public function echoResponse(string $json) :void { //Anything with a bracket inside a method goes on the same line if ($json != "") { echo $json; die; //You can do else as }else{ or } else {, but make sure it's always the same }else{ echo "No variable passed"; } } //Try to use line breaks for readability //Good: $a = 1; $b = 2; $c = $a + $b; return $c; //Bad: $a = 1; $b = 2; $c = $a + $b; return $c;
Refactoring Techniques
As a bonus, here are some refactoring techniques that should help keep your code nice and clean. It is basically the same technique used in different ways. The end result is more reusable code, smaller methods, and a more organized code base keeping higher level code simple, pushing the complexities to deeper levels.
Simplifying returns for if else //Old: public function getValue(int $a): string { if ($a == 2) { $returnValue = 'two'; }else{ $returnValue = 'not two'; } return $returnValue; } //New: public function getValue(int $a): string { if ($a == 2) { return 'two'; } return 'not two'; } Using the simplified if statement to make reusable methods //Old: public function getSomeIntStrings(): string { $a = 1; $b = 3; if ($a == 2) { $aVal = "two"; }else{ $aVal = "not two"; } if ($b == 3) { $bVal = "three"; }else{ $bVal = "not three"; } return "a is ".$aVal." and b is "$bVal."."; } //New: public function getSomeIntStrings(): string { //Notice how much easier this is to read $a = 1; $b = 3; //You now have a generic function that can be reused. //What if you needed to do this 100 times, are you going to write 100 if else statements? //Instead, the logic is extracted and reused. //Take a look at any code you have written, this can probably be applied all over the place. $aVal = $this->getIntTextFromStringCustom($a, 1, "two"); $bVal = $this->getIntTextFromStringCustom($b, 3, "three"); return "a is ".$aVal." and b is "$bVal."."; } public function getIntTextFromStringCustom(int $val, int $valCheck, string $stringName): string { //Another advantage to this, you might write multiple methods similar to this in your class. //If it gets to that point, you could create a StringNumber class and move them there. //Now that all of your string number methods are in the same place, //you might be able to refactor those as well, looking for ways to combine them. if ($val == $valCheck) { return $stringName; } return "not ".$stringName; } You can use this refactoring method for loops too $intStringMap = array( array("1|one"), array("2|one"), array("3|three"), array("4|four"), array("5|five") ); $numbersToCheck = array(1,3,7); $result = $someClass->getSomeIntStrings($intStringMap, $numbersToCheck); //Old: public function getSomeIntStrings(array $intStringMap, array $numbersToCheck): string { $finalString = "Here are some int string results: "; foreach ($numbersToCheck as $numKey => $numVal) { $foundStringVal = ""; foreach ($intStringMap as $mapKey => $mapVal) { foreach ($mapVal as $subMapKey => $subMapVal) { $expIntStrings = explode("|", $subVal); $intVal = $expIntStrings[0]; $strVal = $expIntStrings[1]; if ($intVal == $numVal) { $foundStringVal = $strVal; } } } if ($foundStringVal != "") { $finalString .= "value ".$intVal." equals ".$foundStringVal." "; }else{ $finalString .= "value ".$intVal." not found "; } } return $finalString; } //New: public function getSomeIntStrings(array $intStringMap, array $numbersToCheck): string { $finalString = "Here are some int string results: "; //When nesting loops, it can be very difficult to read what is going on //if there is a bunch of logic inside each loop. //This makes it easier for a developer to figure out what is going on. foreach ($numbersToCheck as $numKey => $numVal) { $finalString .= $this->getIntStringFromMap($intStringMap, $numVal); } return $finalString; } public function getIntStringFromMap(array $intStringMap, int $numVal): string { //Again, this makes it easier to figure out what is happening in the loop. //This method is simple now too, it simply builds your string based on some logic. //Now you can replace the logic with new logic by making a new method if you want to. //May you want to run getIntStringSpecial(). $intString = ""; foreach ($intStringMap as $mapKey => $mapVal) { foreach ($mapVal as $subMapKey => $subMapVal) { $intString = $this->getIntString($subMapVal, $numVal); } } if ($intString == "") { return "value ".$numVal." equals ".$intString." "; } return "value ".$numVal." not found "; } public function getIntString(string $subMapVal, int $numVal): string { //This gets the map values and performs the final logic to build the string. //Now that this code is isolated, it's much easier to read and is now reusable. $expIntStrings = explode("|", $subVal); $intVal = $expIntStrings[0]; $strVal = $expIntStrings[1]; //Looks like we missed checking for a value string in the original. //Because this is isolated, you will find mistakes easier. //We can also expand and do even more checks here if we need to. if (count($expIntStrings) < 2) { return ""; } if ($intVal == $numVal) { return $strVal; } return ""; }