logo sykohpath.com

				where code goes to die

Web Development From Scratch, Day 8

After "Create", there is "Read".  Technically we covered part of this yesterday with "get_all_users" in our model, but we need to actually use it - mainly for a login form.  It's quite simple, really.  Use SQL statements to Read from the database.

First, we'll have to actually create the login form.  Nothing too fancy, just a simple "Username" and "Password" setup.

Normally, in an OOP environment, we'd use the same controller for this, but again, since that's a bit later, we're going to create a new controller, called "login.php".  In order to keep things from getting messy, we need to rename our "users.php" to "register.php".  Why didn't we do this earler? oops.  Shoulda planned for that, right?

1) rename controller/users.php to controller/register.php

We need to slightly modify our index.php as well.  Since we access our site by typing in /index.php, and we have that require our controller, how do we make it access our other controller?  /index.php/register would be a rather nice way to do it, right?

There...sort of is...a way to do it without using an actual framework.  We're going to have "ugly" URL's as a result, but there's ways around that, although this is actually beneficial from an SEO standpoint!

1) grab the full URL that was sent: http://<yoursite.com>/index.php/register
2) Parse that: [host]<yoursite.com>, [path]/index.php/register
3) break that up: [0]=> [1]=>index.php [2]=>register
4) If [2] is set, call that "controller", else default to the "index" controller.

Code Sample:
  1. <?php
  2. require "../../includes/db_connect.php"; //modify to your needs
  3. $url = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
  4. $urlParse = parse_url($url);
  5. $urlParts = explode('/', $urlParse['path']);
  6. if(isset($urlParts[3])){
  7.   //check if file exists
  8.   $controller = "controller/" . $urlParts[3] . ".php";
  9.   if(!file_exists($controller)){
  10.    die("Controller not found: " . $controller);
  11.   }
  12. } else {
  13.   $controller = "controller/index.php";
  14. }
  15. require $controller; //pass to controller
  16. ?>

* Your initial database require should not change, if it's working from last time, don't change it to match what I have!
* We use $_SERVER superglobals to craft the full URL.  Assume we're hitting it from http (we should test for https, honestly)
* Parse_url gives us an array of the url...
* ...and we explode that array into the parts.
* I use the third position, since I'm hitting this from http://sykohpath.com/websitetest.com.git/index.php/register ([1]=>websitetest.com.git [2]=>index.php [3]=>register).  If you're at http://<yoursite.com>/index.php, you'll be using position 2 to grab the controller name ([1]=>index.php, [2]=>/<controller>)
* Need to test if that file even exists! If not, error out.

Since we set a default controller to "index.php", we want this page to be the first page that pulls up, and not directly to our register page.  So, let's whip up the following pages:

Code Sample:
  1. <?php
  2. if($urlParts[2] == "" || $urlParts[3] == ""){
  3.   //index.php was not supplied, so modify links to reflect that
  4.   $prefix = "index.php/";
  5. }
  6. require "view/index.php";
  7. ?>

Code Sample:
  1. <?php
  2. if(!isset($prefix)){ $prefix = ""; } //init
  3. ?><html>
  4. <head>
  5. <title>User Management System</title>
  6. </head>
  7. <body>
  8. <?php if($_SESSION["logged_in"]=="YES"): ?>
  9.   Welcome Back, <?php echo $_SESSION['name']; ?>!<br>
  10.   <a href="<?php echo $prefix; ?>logout">Logout</a><br>
  11. <?php else: ?>
  12.   <a href="<?php echo $prefix; ?>login">Login</a><br>
  13.   <a href="<?php echo $prefix; ?>register">Register</a><br>
  14. <?php endif; ?>
  15. <?php print_r($_SESSION); ?>
  16. </body>
  17. </html>

* Since this is a landing page which can be accessed through index.php via http://<yoursite.com>/ - we need to make sure the links are flexible to handle  http://<yoursite.com>/ as well as http://<yoursite.com>/index.php - we do this by checking our url parts that were set in our index.php for the controller checker.
* We have a check to see if the user is logged in.  If they are, show their name, and a link to logout.  If they aren't, show a link to login.
* For testing/learning purposes, a print_r($_SESSION) displays all that was set once the user is logged in. It's an unneccessary line.

After making those changes, try them out.  Go to index.php/test, as well as index.php/register - test should error, while register should pull up the registration form we made yesterday.  Try index.php/ and the new index page we made should pull up.

One last thing, change the form location in view/user_form.php to point to the new location:
<form name="user_registration" action="register" method="post">

Now to move on to the login form!  Not much to the view:

Code Sample:
  1. <html>
  2. <head>
  3. <title>User Login Form</title>
  4. </head>
  5. <body>
  6. <form name="user_login" action="login" method="post">
  7.   <table>
  8.    <tr>
  9.     <td>
  10.      Username
  11.     </td>
  12.     <td>
  13.      <input type="text" name="username">
  14.     </td>
  15.    </tr>
  16.    <tr>
  17.     <td>
  18.      Password
  19.     </td>
  20.     <td>
  21.      <input type="password" name="password">
  22.     </td>
  23.    </tr>
  24.    <tr>
  25.     <td>
  26.      Errors:
  27.     </td>
  28.     <td>
  29.      <b  echo $errors; ?></b>
  30.     </td>
  31.    </tr>
  32.    <tr>
  33.     <td>
  34.     </td>
  35.     <td>
  36.      <input type="submit" name="submit" value="Submit Form">
  37.     </td>
  38.    </tr>
  39.   </table>
  40. </form>
  41. </body>
  42. </html>

Next our controller, a little bit more complicated:

Code Sample:
  1. <?php
  2. require "model/user_model.php";
  3. //check if logged in!
  4. if($_SESSION['logged_in'] == "YES"){
  5.   require "view/index.php";
  6.   exit;
  7. }
  8. //login user
  9. if(isset($_POST['submit'])){
  10.   //setup array to match table fields=>form values
  11.   $form = array(
  12.    "username" => filter_var($_POST['username'], FILTER_SANITIZE_STRING),
  13.    "password" => filter_var($_POST['password'], FILTER_SANITIZE_STRING)
  14.    );
  15.   //secure/encrypt the password
  16.   $salt = "pepper";
  17.   $form['password'] = md5($salt + md5($form['password']));
  18.   //check if user and password matches
  19.   $errors = validate_user($websitetest_db, $form);
  20.   if(is_array($errors)){
  21.    //if so, cram that user into session
  22.    $_SESSION['logged_in'] = "YES";
  23.    $_SESSION['username'] = $errors['username'];
  24.    $_SESSION['name'] = $errors['name'];
  25.    require "view/index.php";
  26.    exit;
  27.   }
  28. }
  29. require "view/user_login.php";
  30. ?>

* This is VERY similar to the register.php controller.  We still sanitize our fields, and then encrypt the password.
* We pass our associative array (containing the username and password) to the validate_user function, which resides in the model.
* We are passed back an array if the user is validated, and we set the fields according to what was passed back - in this case, only set the flag that the user is logged_in, as well as the username and their name.
* On completion, call the index view, which contains a detection for if the user is logged in.
* If we are not passed back an array, it's an error, so simply call the login view again, which displays the error.

We add the following function to the model:

Code Sample:
  1. ...
  2. function validate_user($db_conn, $dataArray){
  3.   $sql = "SELECT * FROM users WHERE username='" . $dataArray['username'] . "'";
  4.   $result = mysqli_query($db_conn, $sql);
  5.   if(!$result){
  6.    die('SQL Error: ' . mysqli_error($db_conn));
  7.   }
  8.   if(mysqli_num_rows($result) > 0){
  9.    $row = mysqli_fetch_assoc($result);
  10.    if($row['password']==$dataArray['password']){
  11.     return $row; //pass user account back
  12.    } else {
  13.     return "Incorrect Password";
  14.    }
  15.   } else {
  16.    return "Username not found";
  17.   }
  18. }
  19. ...

* Ok, "finally" we deal with the "Read" part of CRUD.
* Using SQL, just pull all records that match the passed username. (Note that we haven't put in a duplicate username filter in our registration yet!)
* If there's more than 0 rows returned, it means that a matching record was found.  Otherwise, 0 rows returned means there were no records that match the passed username.
* Compare the pulled password with the passed password - either it matches, or it doesn't.

After all this, we'll have the following in our $_SESSION:
Array ( [logged_in] => YES [username] => testAccount [name] => Test Account )

Now, if we try to login as "test" (Bob Bobkins!) we'll see that it doesn't work...because the password it's checking against is unencrypted.  We'll deal with that one record in a later section (Delete!).

Our login system is almost complete.  We just need to "logout".  Painfully simple!

Code Sample:
  1. <?php
  2. //check if logged in!
  3. if($_SESSION['logged_in'] == "YES"){
  4.   unset($_SESSION);
  5. }
  6. require "view/index.php";
  7. ?>

GOOD NESS.  That was quite a bit for "Read", wasn't it?  Well, a login system is the...I don't want to say "backbone"...it's more like the "kidneys" of a user management system.

Next will be the user "Profile" once they are logged in.  This will cover "Update" part of CRUD.

php, tutorial, crud, update,



Web Development From Scratch, Day 7

Next up: Create Record in Database.

So the first thing is, to cram our database stuff into the model, make a view, and then tweak the controller to call both of them.  All we'll do for the view is make a simple form, which will tell our php script to create a record with the data entered.  We'll also create a second view which displays all the users in the database.  EASY STUFF, I promise.

Code Sample:
  1. <html>
  2. <head>
  3. <title>User Registration Form</title>
  4. </head>
  5. <body>
  6. <form name="user_registration" action="index.php" method="post">
  7.   <table>
  8.    <tr>
  9.     <td>
  10.      Username
  11.     </td>
  12.     <td>
  13.      <input type="text" name="username">
  14.     </td>
  15.    </tr>
  16.    <tr>
  17.     <td>
  18.      Password
  19.     </td>
  20.     <td>
  21.      <input type="password" name="password">
  22.     </td>
  23.    </tr>
  24.    <tr>
  25.     <td>
  26.      Password Confirm
  27.     </td>
  28.     <td>
  29.      <input type="password" name="password_confirm">
  30.     </td>
  31.    </tr>
  32.    <tr>
  33.     <td>
  34.      Email Address
  35.     </td>
  36.     <td>
  37.      <input type="text" name="emailaddr">
  38.     </td>
  39.    </tr>
  40.    <tr>
  41.     <td>
  42.      Full Name
  43.     </td>
  44.     <td>
  45.      <input type="text" name="fullname">
  46.     </td>
  47.    </tr>
  48.    <tr>
  49.     <td>
  50.      Errors:
  51.     </td>
  52.     <td>
  53.      <b  echo $errors; ?></b>
  54.     </td>
  55.    </tr>
  56.    <tr>
  57.     <td>
  58.     </td>
  59.     <td>
  60.      <input type="submit" name="submit" value="Submit Form">
  61.     </td>
  62.    </tr>
  63.   </table>
  64. </form>
  65. </body>
  66. </html>

* This .php file is almost all HTML.  Note that there are no opening and closing PHP tags.
* However, there is PHP in the "errors" area, which displays the contents of the "$errors" variable.
* Make sure to "git add view/user_form.php" so that it will be properly tracked.

Ok, back to our controller, which we need to "fix":

Code Sample:
  1. <?php
  2. require "model/user_model.php";
  3. if(isset($_POST['submit'])){
  4.   //setup array to match table fields=>form values
  5.   $form = array(
  6.    "username" => filter_var($_POST['username'], FILTER_SANITIZE_STRING),
  7.    "password" => filter_var($_POST['password'], FILTER_SANITIZE_STRING),
  8.    "email_address" => filter_var($_POST['emailaddr'], FILTER_SANITIZE_EMAIL),
  9.    "name" => filter_var($_POST['fullname'], FILTER_SANITIZE_STRING),
  10.    "created" => time(),
  11.    "modified" => time()
  12.    );
  13.   //validate our values
  14.   $errors = "";
  15.   if($form['password'] != $_POST['password_confirm']){
  16.    $errors .= "Passwords do not match!<br>";
  17.   }
  18.   if($form['email_address'] != $_POST['emailaddr']){
  19.    $errors .= "Email Address contains invalid characters!<br>";
  20.   }
  21.   //secure/encrypt the password
  22.   $salt = "pepper";
  23.   $form['password'] = md5($salt + md5($form['password']));
  24.   if($errors == ""){
  25.    //add user to the database
  26.    add_user($websitetest_db, $form);
  27.    //get all users from database
  28.    $allUsers = get_all_users($websitetest_db);
  29.    //show all users
  30.    require "view/user_display.php";
  31.   } else {
  32.    //errors found, show form again
  33.    require "view/user_form.php";
  34.   }
  35. } else {
  36.    //show the user registration form
  37.   require "view/user_form.php";
  38. }
  39. ?>

Wow, what the heck happened here!?
* Start off by including our model.
* Check to see if form was submitted. If not, show the user form, otherwise:
1) set up associative array to store form values.
2) use "filter_var" to sanitize the input. It is a VERY BAD IDEA to not sanitize input, since that opens up possibility of SQL injection, among other issues. (see Security below)
3) Check sanitized input with pure fields for email address. Error if fail.
4) Make sure Password matches the Password Confirm. Error if fail.
5) Hash the password with a very simple salting routine.  Salt is hardcoded, added to the password, and then an MD5 algorithm is applied.  See Security below.
6) if there are no errors, add user to the database, get a list of all users, and show the "show all users" view.
7) if there are errors, show the user registration form again, and pass the errors to it to be displayed.

Consider this statement:
$sql = "SELECT * FROM users WHERE name = '" . $_POST['username'] . "'";

As long as a user enters any name into the field, this sql statement works as intended.  However, the world is filled with assholes, so more than likely, someone will attempt sql injection.  All they have to do is enter something like:


Or something more creative, which will end up with the following full sql statement:


Just like that, your entire users table is gone.  Forever.  That's bad.

I mentioned before that storing passwords in plaintext is a bad idea.  Consider the previous SQL Injection.  A simple "'; SELECT password FROM users;" will then show all the passwords, plain as day, to the intruder. BAD.

So, we need to encrypt the password.  MD5 is NOT an encryption algorithm.  All it does is hash what is supplied.  I won't go into detail here, but consider the following:

'word' -> MD5 -> 'c47d187067c6cf953245f128b5fde62a'

So far so good, right?  MD5 can be reversed, however:

'c47d187067c6cf953245f128b5fde62a' -> MD5 -> 'word'

By adding a salt to the password, we introduce a layer of security:

'word' -> MD5 -> 'c47d187067c6cf953245f128b5fde62a' -> 'saltc47d187067c6cf953245f128b5fde62a' -> MD5 -> '758e43ddf53438199bb0f8aa82a604f7'

But wait, reversing that gives us the salt + md5!  Yeah, but try reversing 'saltc47d187067c6cf953245f128b5fde62a'...it fails.  Mainly because hashes are in Hex, 0-9A-F.  If you make your salt into hex as well, it'll really screw up the reverse process.

It's a complex area, so don't worry if you don't understand it.  Just be aware that you need to use some type of encryption for passwords

To see it in action, load up your database browser and browse the records.  Compare the password fields of "Bob Bobkins" vs one of the records put in by our new form (once you're done putting all the files up!).  Poor Bob's password is sitting there in plaintext.  Our encrypted passwords tower over his exposed weakness.  MD5 stands for MAGIC-DOMINATING-...uhh...FIVE.



Ok, what files do we have left?

Ah, the model.

Our old controller from yesterday has been crammed into a get-all-users function for this one:

Code Sample:
  1. <?php
  2. function get_all_users($db_conn){
  3.    $sql = "SELECT * FROM users";
  4.   $result = mysqli_query($db_conn, $sql);
  5.   if(!$result){
  6.    die('SQL Error: ' . mysqli_error($db_conn));
  7.   }
  8.   $userList = array();
  9.   while($row = mysqli_fetch_assoc($result)){
  10.    $userList[] = $row['name']; //store all names into the list
  11.   }
  12.   mysqli_free_result($result);
  13.   return $userList;
  14. }
  15. function add_user($db_conn, $dataArray){
  16.   $insert_fields = implode(",", array_keys($dataArray));
  17.   $insert_values = "'" . implode("', '", $dataArray) . "'";
  18.   $sql = "INSERT INTO users (" . $insert_fields . ") VALUES (" . $insert_values . ")";
  19.   $result = mysqli_query($db_conn, $sql);
  20.   if(!$result){
  21.    die('SQL Error: ' . mysqli_error($db_conn));
  22.   }
  23. }
  24. ?>

* Not much has changed for the "get_all_users" function.  Notice that we need to pass our database connection to the function in order for the database routines to work correctly.  This is normal.
* New function: add_user.  This takes an associative array, crafts it into a sql statement, and then runs the statement (errors if there is something wrong).

The "sql statement crafter" is a bit...odd...I admit. Normally, prepared statements are ideal with set fields - all we would pass is the data.  However, we're not to the OOP point yet, so we have to deal with procedural goofyness.  I mean, an alternative could be:

$sql = "INSERT INTO users (username, password...) VALUES ('" . $dataArray['username'] . "', '" . $dataArray['password'] . "',...)";

I don't like that.  Sure, everything could be crammed into one line that way, but I like to show off my leet programming skills (stop laughing).  We use the "implode" function to turn the array into a comma-separated string.  Note that the values have to be enclosed in single-quotes.


Whew, tiring.  Let's cover the last, and easiest file yet...display all users view:

Code Sample:
  1. <html>
  2. <head>
  3. <title>Users - Display all</title>
  4. </head>
  5. <body>
  6. <ul>
  7. <?php foreach($allUsers as $value): ?>
  8.   <li><?php echo $value; ?></li>
  9. <?php endforeach; ?>
  10. </ul>
  11. </body>
  12. </html>

* Again, no php tags since this is HTML for the most part.
* We use a foreach to go through the list of all users (Array) and output them in an unordered list.

And hey, that wraps up "create"

Tomorrow, we'll have users be able to log-in.  We MIGHT do "update" and "delete", but only if you're good.

php, tutorial, crud, create,



Web Development From Scratch, Day 6

The typical approach to handling databases is called, "CRUD".  This stands for Create, Read, Update, and Delete.  While not all of these will be used for every project, "Read" is mainly used (speaking from experience, so milage may vary).

Creating the database and table has already been covered.  In the MVC tutorial, there was a quick bit about connecting to the database, but I'll go into further detail here.

1) FIRST. Security.

Do "NOT" have your connection settings in a web-accessible directory (such as http://<yoursite.com>/db_connect.php).  Even though everything is "hidden" in hardcoded php ($username="Database";), there are ways these files can be read.  Even something as simple as accidentally setting PHP files to be "off" will cause .php files to display as HTML instead of being compiled first.

So, on your website directory, you have the following setup:
|- /www
|- /includes

/www is the base folder that is loaded when http://<yoursite.com>/ is accessed.  We want to put our db_connect.php file into /includes, and then call it from our web folder.

There's a couple options here - in "php.ini" (if you can access it), you can specify include directories:
* Adjust "include_path" to include that directory. include_path = "<path_to_your_home>/includes" (don't erase what's already in there!  Also, restart the webserver after changing).

If you're unable to edit the php.ini, hope isn't lost:
* In the php script you can use: set_include_path() - although if anyone is able to see this, they now know the direct route to your included file &#40;not that they'd stumble across it anyway if they are able to poke around the files/directories at this point&#41;
* You can alternatively use __DIR__ as include(__DIR__ . '/includes/file.php');
* Alternative to __DIR__: set_include_path(get_include_path() . '/includes');

On your local computer, instead of putting this into "~/Projects/www.websitetest.com/includes", instead, create this file in a new folder in your Projects directory: "~/Projects/includes"


So, with that setup, make "db_connect.php" in your /includes directory:

Code Sample:
  1. <?php $mysql_db = mysqli_connect('<SERVER>', '<USERNAME>', '<PASSWORD>', '<DATABASE_NAME>');
  2. if(!$mysql_db){
  3. die('Unable to connect to Server: ' . mysqli_connect_error() . ' (#' . mysqli_connect_errno() . ')');
  4. }
  5. ?>

* Use "mysqli" instead of "mysql". This is anotehr security issue, plus, future versions (heck, it's already deprecated in current version) will likely remove "mysql" commands.
* This is using procedural over Object oriented style (OOP).  You'll find out the difference, and use OOP in the future, but for now, procedural is fine as you start out.
* mysqli_connect: Use the details from your webhost.  The last option, <DATABASE_NAME>, refers to the database name, which if you followed these tutorials so far, should be "test".  EASY!
* The only variable carried over from our include is "$mysql_db".  This is a link to the database that will be passed into other functions.
* Connection information (server, username, password) is sometimes stored in variables, and then called after the include/require. This is fine (and used commonly in frameworks).  However, "a hacker" could put "echo $server,$user,$password;" at the top of a file, and then BOOM info is exposed, even though they can't access your /includes directory.  It happens, but meh, keep it in mind - no need to be too paranoid about it.
* "die" causes the script to terminate immediately - here, it will show the connection error and error number.  This is fine for testing, but for a "live" environment, it's better to redirect to a "Server is down" page, and not show any error information.

Hey, guess what?  If you FTP to your site, chances are, it'll plop you in your web root directory.  So how the heck do we create the includes directory (and our file!) outside of webroot?

1) Git Bash, and ssh to your site ("ssh <username>@<yoursite.com>")
2) You should be in your home directory, if not: cd ~
3) "ls" and you should see your /www, as well as other miscellaneous folders.
4) Create the /includes folder: mkdir includes
5) cd includes
6) Funky part here. Leave this window open, and open a *new* instance of Git Bash. Yes, you will have two windows open now - one for your local connection (LOCAL:), one for your remote connection (REMOTE:).
7) LOCAL: You should be in your home directory, "cd ~" if not
8) LOCAL: cd Projects/includes
9) LOCAL: "ls" and make sure you're in the right spot - you should see your "db_connect.php" file in this directory (and nothing else!)
10) LOCAL: Copy the file using "scp", which syntax is "scp <file> <username>@<website>:<location>/<file>":
11) LOCAL: scp db_connect.php <USERNAME>@<yoursite.com>:~/includes/db_connect.php
12) REMOTE: "ls" and you should now see your file!


Yesterday, we made the "User" table, and went ahead and made a test record: Bob Bobkins.  So, let's see if we can "Read" first, since it's the primary thing that will be used more often than not.

In our web root directory, have the index.php:

Code Sample:
  1. <?php
  2. require "db_connect.php"; //load config from /includes if pathed in php.ini
  3.   //OR
  4. //require "../../includes/db_connect.php"; //<yoursite.com>/websitetest.com/index.php
  5.   //OR
  6. //require __DIR__ . "/includes/db_connect.php";
  7. require "controller/users.php"; //pass to controller
  8. ?>

* Pick an option for calling db_connect.php.  Go with whatever one works, it honestly doesn't matter, and varies greatly by setup.  This may take trial and error ("~/includes/db_connect.php" worked for me).  After each change, "git push" to your site, and see if the error message pops up ( replace the controller require with an "echo 'success!' to check script progress).  If no error, you're good!

Onto our reading test.  For purposes of just trying to read, we'll test straight from the controller.  Once we can read and understand how that works, we'll move the database information into the model.

Code Sample:
  1. <?php
  2. $sql = "SELECT * FROM users";
  3. $result = mysqli_query($mysql_db, $sql);
  4. if(!$result){
  5.   die('SQL Error: ' . mysqli_error($result));
  6. }
  7. while($row = mysqli_fetch_assoc($result)){
  8.   echo $row['name']; //list all names
  9. }
  10. mysqli_free_result($result);
  11. ?>

* All this will do is display "Bob Bobkins", when successful.
* $sql contains the...sql...statement, pure and unaltered:
  - SELECT = read from table
  - * = all fields, we could name individual fields if we wanted.
  - FROM = designates what table(s) to read "from"
  - users = the name of our table.
  - Simple syntax form: SELECT <fields> FROM <TABLE>
* mysqli_query(connection, sql): is what sends the command to SQL to process, and returns a link to the result.  It doesn't necessarily contain what we need directly.
* mysqli_fetch_assoc(result-link):  THIS is how we get the information from the database.  There's a few ways we can get data:
  -mysql_fetch_array() = get results as an array, defaults to both "numeric" and "associative"
   - Numeric array: elements are represented as numbers.  "0" refers to the first element, which depends on the SQL query and/or the order of the fields in our table.  In this case, likely to refer to "id" field in our table, since it is "first".
   - Associative array: elements are represented by labels.  "name" refers to the "name" field.
   - mysql_fetch_array(result, MYSQLI_ASSOC) is EXACTLY the same as mysql_fetch_assoc(result).
  - This function is put into a loop.  Every time this function is called, it retrieves one "row" from the database.  If we had more test records in our database, this loop would retrieve all rows and display the names.
* $row[field] = the associative array, referring to the field. Try changing this to other field names and get them to display.  For a "fun" way to display all fields and their contents, replace the line with this monster:
foreach($row as $key=>$value){
echo $key, "=>", $value, "<br>;

  - this will display each field and their contents.
  - note that the "password" is showing 1234.  Yes, it's that easy to display a plaintext password.  We'll deal with this a tad later.
* mysqli_free_result(result-link) = ok, technically we don't need this.  It's a good practice to get into, however (so is mysqli_close(database-link)), especially for large scripts and multiple calls.  All it does, is free up memory that is allocated to the result.  Imagine cases with multiple databases with multiple table calls using multipule variables - ugly, right?  It's a common occurrence, however.  It's important to remember memory management - while PHP is a language that handles garbage collection automatically, there's some other languages (C++) that require manual calls, or things can get "rather messy".

Ok, we have "Read" covered.  Tomorrow we'll cram our database stuff into the model, and then start working on an input form, which will reside in our view.

php, tutorial, security, database, include, path,



Web Development From Scratch, Day 5

So far, these tutorials have all been about setting up the *environment* that we'll be working in, and not necessarily the actual coding part of things.  Typically each user will develop their own working environment, but it helps to have some basic tools setup to actually do work.  It's like signing up for a painting class, and on the first day you're shown how to paint a still life, but you're not shown (or given) any brushes, paint, or even a canvas!  From my perspective, this is exactly what happens to people trying to get into web development.

There's plenty of resources out there on how to do certain things in Web Dev, which (hopefully) after this set of tutorials, you'll be able to use your tools and self-teach yourself new things.  So yeah, all that ranting out of the way, because...on to actual coding!  Sort of!  Let's review our environment/tools so far:

1) Folder organization.  Horribly easy to overlook, but organized folders (especially setup as MVC) makes finding files simplified.
2) Text Editor.  I suggested Sublime Text 2.  Setup the file manager to view all folders in the project directory.
3) File Repository.  GitHub is where it's at.
4) A website.  I didn't do a tutorial on this one - there's too many hosts out there to do an actual tutorial on.  HOWEVER, this step is "easy", and actually, the hosting company can more than likely live-chat you through the steps to get your site going.
5) Terminal Client.  We're using Git Bash for this, but this isn't the only option.  A popular choice for SSH (among other things) is to use PuTTY
6) FTP Client.  Yes, we're trying to avoiding using this on purpose.  FTP is so 1990's.  However, it's a good idea to have one installed for files not included in our repositories.  FileZilla works great for this.
5) MySQL.  Using the server path, username, and password, able to work with databases.  Today's tutorial will cover this, however, SQLBuddy or phpMyAdmin is the tool of choice for this part.

Important note:  Almost all of these are free.  You shouldn't have to pay for these tools, except for SublimeText2.  It's worth the cash, but to remain in the spirit of "free", an alternative is Programmer's Notepad (been a while since I used this one!).  Using plain old Notepad is just...it's doable, but there's better tools for the job.  Even a crap one with nothing but a file manager is going to save a ton of headache.

Man, it's like I'm purposely avoiding getting to the coding part...enough!


Yay! We finally get to work with some PHP.  YES.


First, MySQL!  Hopefully you've setup and created the database "test" on your website.  If not, we're going to have problems.  Well, *you're* going to have problems.

The majority of MySQL tutorials go about telling you how to throw something together.  This is a tutorial on getting started in Web Development, however, so the more technical aspects will be left up to you.  The good thing is that for the most part, setups and working with MySQL is rather simple (varies widely on the job!)

We have our "test" database and are ready to immediately jump in and start creating tables and fields all over the place, right!?  WRONG.

1) Write out a database schema.  Yes, WRITE. IT. OUT.  Sure, it's fun to just whip up fields and tables as you need them, but in practice, that's a great way to cause a big mess, especially with larger databases and projects.

So the first step for our schema is to take a general overview of the entire project, and then map out what exactly we need to store into a database.  Our example project is going to be a website membership system.  Thinking about this all at once is a bit intimidating, but work on it one piece at a time, and you'll suddenly have a great system going.

Let's write this out!

Title:  Website Membership System
Purpose: Users can create and manage their profiles and information through a website.
- Login
- Register
- Profile View
- Profile Edit
- Logout
Database: "test"
  id     int(8)
  username   varchar(35)
  password   varchar(50)
  email_address  varchar(50)
  name    varchar(255)
  created   int(11)*
  modified   int(11)*

The reasoning behind each field:
  "id" = the row id. This is a PRIMARY UNIQUE NON-NULL AUTOINCREMENT INDEX.  Scary, huh?  More defining:
   PRIMARY = only one primary field per table, the "boss" of the whole thing.
   UNIQUE = no duplicate values are allowed within this field
   NON-NULL = when a field is empty, it's NULL.  Not even "" is stored, it literally means there is nothing there.
   AUTOINCREMENT = every time a new row is added, increase the counter by one.  MySQL has a counter per field in these cases, so that if you create record #532, delete #532, and then create a new record, it will be id #533 (and you'll have a gap in id's between 531 & 533).
   INDEX = in short, this means to "make it faster".  Do not apply INDEX to every damn field in the table.  Only index high-use fields.
  "username" = the user id that is entered when logging in
  "password" = the password matching the user id.  Note that we'll handle encryption on the PHP side.  NEVER EVER store passwords in plain-text!  Seriously!  NEVER!
  "email_address" = I hope this one is obvious.  By the way, I should note that I like to name fields with underscore separating words.  An alternative is to camel-case, so that it would be "emailAddress".  Whatever you use, BE CONSISTENT.  We use this field to email the user in case of lost-passwords.  If you use this for spam, I will punch you in the face.
  "created" = the timestamp of when the record was created.  It's sometimes nice to know when things are created.
  "modified" = similar to created, but stores timestamp whenever the record is changed.  This is great for monitoring activity.

Let's define a few of these datatypes:
"int" = integer.  It's a number.  The number in parenthesis (8) means that a number up to 8 digits can be stored.
"varchar" = variable character.  a-z, 0-9, symbols, all that fun stuff.

Special note regarding the "int(11)" for "created" and "modified":  We could use DATETIME and TIMESTAMP respectively, but the reason why we're not going to use that here is to avoid converting from PHP to/from MySQL. PHP uses timestamps for everything related to date and time. It's an integer (number) that counts the number of seconds since midnight on 1970Jan01GMT.  That's when the world was created.
  - MySQL has 3 date types: DATETIME, DATE, TIMESTAMP.
   DATE = stores as YYYY-MM-DD
   TIMESTAMP = DATETIME, but automatically updates to the current time every time the record is altered
  - The issue would be that for every database call, we'd have to convert (handled through our PHP or in the SQL query itself).  PAIN IN THE ASS.
  - Although, it comes down to what you need to use these for.  When storing as an "int", you lose all of MySQL's built-in time and date handling procedures.  In this case, we're handling everything on PHP's side, so that's the reason for the int.


Quite a few words for so little so far, right?  The major thing to know about databases is that it's more strategy than storage.  Databases need to be easy to navigate.  It is really easy to complicate a database to the point where not even the creator knows what is going on (common with "let's slap this on" developing).


Ok, here's another tool for your arsenal.  The majority of web hosts supply phpMyAdmin with websites.  You can see if it's already setup for you by appending http://<yoursite.com>/phpMyAdmin - if a login screen appears, it's setup for you!  If not, don't fret.  Go into your website setup...under the database section, your "test" database should be listed.  Depending on the host, they should have a "edit" or "manage" or "something" link by that database.  Click it, and you'll likely be sent to a weird address like http://asgbqiurwegbsqlmanager.server.something/words/numbers/seconds/login.php - PUT THE LINK IT GIVES YOU INTO YOUR BOOKMARKS.



Well, that way you can access the login page directly without having to launch your webhost control panel every time.  Also, this is more than likely the address you'll use when setting up your own database manager (like phpMyAdmin).

Regardless, in your website setup, you should have the following information:
- Server
- Username (it better be different than your website account login name!)
- Password (again, different, and complicated!)

Now, before we proceed, let's think about at least putting a manager on the website ourselves.  Snag SQLBuddy or phpMyAdmin.  Toss either one of these into your webroot subdirectory, such that it is accessible as http://<yoursite.com>/sqlbuddy

Fire that up in your browser and try to login with the details you setup and recorded from your webhost.

Aaaaaand now you should be presented with a fairly-empty looking view of your "test" database!

Looking around, it's not very intuitive what you should do with everything that's presented.  This is the part where you pull out your database schema to work from.

1) Create Table - name "users"
2) Number of fields "7" (or you may add them one at a time, depends on the client you're using)
3) Fill out the fields to match the schema.  Set the Collation as UTF-8.  Hmm, perhaps we should have covered this.  Basically, put everything as UTF-8 for now; again, depends on the job.
4) For "id", make sure you mark it as "PRIMARY", with option "auto_increment".

When complete, and hopefully no errors, you'll be presented with the structure of the table you just created, listing all the tables.  MAGIC.  

1) Depending on the client, click "INSERT".
2) Fill out everything with bogus values. This will be our "test" record:
id: <blank>
username: test
password: 1234
email_address: email@address.com
name: Bob Bobkins
created: <blank>
modified: <blank>

Submit it, and we're done!  SO EASY.  Get used to working with this environment, since it's a no-holds-barred way to work with your database data.

php, tutorial, mysql,



Web Development From Scratch, Day 4

Alright, looks like my SSH request finally went through.  Let's go ahead and set this up for today's lesson (yes I know you have your database "test" created - we'll get to that soon enough!)

First off, from your terminal...oh right, we're in Windows.  Open up Git Bash.  Now, from *this* terminal, type:
ssh <username>@<website>

So, for example, your username to ssh into your site is "Blah", and your site is "www.websitetest.com", your line will appear as so:
ssh Blah@www.websitetest.com

niiiiiice.  Type in your password, and you'll be at a funky looking prompt, which varies *greatly* among hosting servers:

Yeah, weird, right?  Type "ls".  Chances are, there isn't much here.  Probably your "www" folder (or some variant).  "cd" into that, and you'll see all your web files.  This is your "web root" directory, that is, it's the directory that appears when you type in "http://www.websitetest.com" into your browser.  Note that it'll usually load "index.html" first.  Ok, done poking around?  "cd .." or "cd ~" to get to the home directory.

Now, the part I really can't help with here is making sure your host has Git installed and enabled.  Again, since there's many hosting companies, and every one is different, they may or may not have it installed.  A quick test is to type "git" at the command prompt.  If it's installed, it will show you a list of commands, with something like "See 'git help <command>'..." at the bottom.  If so, YAY.  If not, welp:

1) Google your webhost and "git".  See if there's any easy way to get git on there.
2) If not, unfortunately you'll probably have to stick with ftp for now.  (Get a better webhost! I suggest A2 Hosting - they have Git installed for sure!)

Keys.  Sigh.  Now, unless you feel like typing in your password every time Git or you connects via SSH to your server, you're going to have to make authentication keys.

1) If you're still on git bash, and connected to your webserver, type "exit" to disconnect.  
2) Now, just to make sure, type "cd ~" to put yourself back at the home directory.
3) View all files with "ls -al"
4) look for a ".ssh" directory.  If one exists, you'll want to back it up (or if you know you're already using it - say, you already set it up for git, you can use the same keys! - skip to <STEP> if you want to use the same key)
5) You can refer to GitHub SSH Key Guide if you want to generate keys, or to make new ones (don't forget to add them to Git!).
6) Arguable, but leave the passkey blank so that you don't have to type it every time you git/login.
7) Open up "id_rsa.pub" by any means necessary.  Well, you can do this directly through Git Bash.  Type "cat id_rsa.pub".  It'll display the contents of the file.
8) Now, to copy *from* Git Bash, Click the top-left window icon and Edit -> Mark, then drag a selection box over the text (make sure you cover *all* of the file contents!).  Then press Enter.
9) Ok, ssh back into your webserver.
10) At your home directory, "cd .ssh".  If the directory doesn't exist, create it: mkdir .ssh
11) In the directory, type: vi authorized_keys2
12) A really strange-looking setup appears.
13) Press i  (when you do, the bottom of the window should say "-- INSERT--")
14) Paste into the window - with Git Bash, press the "Insert" key to do this.  This should paste the entire "ssh-rsa <bunch of characters> email@address.com" into the top line.
15) Press ESC (removes the "-- INSERT --" at the bottom of the screen)
16) Type :x
17) Press enter (":x" <enter>)
18) You're magically at the prompt again! whew that was scary!
19) confirm the file actually contains what you pasted by typing "cat authorized_keys2"
20) exit
21) Relog back into your ssh server - this time, it should log in without a password (however, it will ask for a passkey if you set one!)

And that's "it".  Again, you only have to set this up once (unless you access from a computer with different keys!)


Ok, now, we have to somehow magically make Git push to your webserver.  This is another one of those "setup once" things.

1) ssh to your server.
2) First, we're going to make a subdirectory on the web root, so that it will be accessible from http://<yourwebsite.com>/websitetest.com.git
3) cd into your webroot, which varies by server.
4) mkdir websitetest.com.git
5) pwd
6) With "pwd", it shows the full path to the current directory.  Remember this!  Again, it varies by server, and will look something like:
7) cd ~
8) Type: mkdir websitetest.com (create the folder within your home directory, different than the ".git" one in webroot)
9) cd websitetest.com
10) git init --bare
11) The home/websitetest.com directory is now initialized with a "bare" repository.  This is separate from your repository on github.com, but we'll be pushing directly to this one.
12) Now, this next part is called the "hook".  Remember the path you got from pwd?  I hope you wrote it down!
13) vi hooks/post-receive
14) Press "i" for insert mode.
15) Enter in the following, replacing <PATH> with the full path you got from "pwd"
GIT_WORK_TREE=<PATH> git checkout -f

16) Press ESC, then :x
17) Verify the file with "cat hooks/post-receive".  If it has what you entered, you're good so far.
18) chmod +x hooks/post-receive
19) The file is now marked as "execute".
20) exit

We're back on the local machine for this next part.  I promise we're almost done.  Seriously.

1) Open Git Bash, if for some reason you closed it within the last 2 seconds of the previous section.  "cd ~/Projects/www.websitetest.com"
2) Now, to setup our remote access and push to our webserver!
3) git remote add web ssh://<username>@<yourserver.com>/websitetest.com
    Note: Make sure it's pointing to the repository, and not the .git directory you made in your web root.
        - if you get "repository not found", you may have to set it up as:  ssh://<username>@<yourserver.com>/~/websitetest.com
4) git push web +master:refs/heads/master
    Note: this pushes the master config and goodies and all that - you only have to type "git push web" for normal use

Note that if you get an error...tough!  Well actually, google the error.  It helps to google your host, git, and error message, such as "godaddy git git-receive-pack:command not found".  I apologize if you're using GoDaddy.  They don't have git installed, and are kind of a pain to get working.  However, I'm nice, and managed to run across this post when I tried that search: GoDaddy git-receive-pack error fix

5) Everything should be happy, but, there's only one way to be sure!
View your work at:  http://<yoursite.come>/websitetest.com.git/
For me, that's: http://www.sykohpath.com/websitetest.com.git/

Hey look at that...I'm Awesome.  Super Awesome.

Now, just to make sure, we have 2 "remotes" setup.  Simply type "git remote" to view all of them.  There should now be one for "origin" and one for "web".  Origin points to your repository on github, while web points to your website.  With this process, you should be able to setup a "dev" and "live" environment.  I'll explain that setup in a separate tutorial.  For now, be happy you can push to git and your website!

And finally from now on - after you commit your changes, all you have to type is "git push origin" or "git push web".

php, tutorial, git, github,



Web Development From Scratch, Day 3

Hey thanks for losing my really long post I did this morning!  siiigh.

After writing out a freakin' novel on setting up your environment for a basic MVC setup, I'll have to cut corners as I try to recreate this post.

1) Create the following file structure in your "www.websitetest.com" directory:
|- /assets
|- /controller
|- /model
|- /view

WHY?  bah just do it. seriously.

MVC is typically reserved for "more advanced" areas, but why not do things right the first way?  Newer programmers will create scripts that contain all their code in one file, and then have that file stuck in the root directory.  Yes, the code "works", but that is not the "correct" way to go about things.

Quick MVC breakdown:  Model, View, Controller.  Breaking it down into what these three actually are:

Model: Database procedures = php + mysql
View: Frontend = html (assets will contain Javascript, CSS, and any other front-end properties)
Controller: Logic = php

With that understanding, let's see it in action.  First off, let's start off with a single-file "spaghetti-code" file.  I mean honestly, this doesn't look to scary, but imagine larger projects where everything is in one file.  Say something breaks in the logic?  Instead of scanning one large file, with MVC, you know to check your controller, which will be a much smaller file with cleaner code.

Couple notes:
* Use "mysqli" functions instead of "mysql" functions.  MySQL has been deprecated due to security issues.
* I'm using procedural style for these examples, since OOP tends to scare off newer programmers (we'll cover this later).
* While I'm using requires here, later on these are unnecessary with better techniques.  Keep this in mind: there's almost always a more efficient way of doing things!  Since it requires some OOP understanding however, let's keep it "simple" for now.


require "db_conf.php"; //load database configuration files

$mysqlDB = mysqli_connect($mysqlServerName, $mysqlUserName, $mysqlPassWord);
    die("Unable to connect: " . mysqli_connect_error());    

function get_all_users($db_conn){
    $sql = "SELECT * FROM users";
    $result = mysqli_query($db_conn, $sql);

    $allUsers = array();

    while($row = $mysqli_fetch_assoc($result)){
        $allUsers[] = $row['firstName'];

    return $allUsers;

$userList = get_all_users($mysqlDB);

            foreach($userList as $value){
                echo $value, "<br>";

Now, for MVC, we'll be splitting this up into multiple files.  Our original index.php becomes nothing more than to load the site configuration (in this case, database settings), and then load up the requested controller.  Later on, you'll be able to choose what controller to load by simply appending options to the URL (h--p://site.com/index.php/users).

    require "db_conf.php"; //load config

    require "controller/users.php"; //pass to controller

Yes, only two lines of code now!  Let's have a look at the controller, which will be the first file called:

    require "./model/user_database.php"; //load model

    $mysqlDB = connect_database($mysqlServerName, $mysqlUserName, $mysqlPassWord);

    $userList = get_all_users($mysqlDB);

    require "./view/all_users.php"; //pass to view

Not scary at all, is it!  This is the "Logic" file, which is the main file for handling the control of the flow.  It loads the database model, calls function to connect to the database, gets all the users, and then passes control to the view.

Now, let's take care of the database functions, which are all under the Model category:

    function connect_database($server, $name, $pass){
        $link = mysqli_connect($server, $name, $pass);
            die("Unable to connect: " . mysqli_connect_error());    
        return $link;

    function get_all_users($db_conn){
        $sql = "SELECT * FROM users";
        $result = mysqli_query($db_conn, $sql);

        $allUsers = array();

        while($row = $mysqli_fetch_assoc($result)){
            $allUsers[] = $row['firstName'];

        return $allUsers;

Notice that the "get_all_users" function is exactly the same.  That's because it was a database function to begin-with, so no change needed.  We put the database connection routine in this file as well, since it's well, a database connection.

Finally, we have the "View":

            foreach($userList as $value){
                echo $value, "<br>";

Again, this part is unchanged.  Previously, it was "tacked on" to the end of the file.  Also note that there was a whitespace before the html in the previous version...oops!

One final note...This is completely optional, and there's arguments either way.  I won't get into gory details, but basically:  You don't, and also shouldn't end your php files with "?>".  WHY?  Several reasons:

1) There can be whitespace after the bracket.  This is the main issue.  ANY whitespace before html output can and will cause problems.  I mean, it's not difficult to make sure there isn't any trailing whitespace.
2) Zend Framework coding standards.  Get used to seeing "Zend" quite a bit in the future.  They require any file that is pure-php to not have the closing tag.
3) This is by design: PHP documentation even states that this tag is optional - useful to leave out for includes(), requires(), and output buffering.

Of course, there's reasons why you should always close the tag:
1) OCD.  Tags were made to be in sets. Open-close.  This applies to just about every other tag on the planet.
2) Can be confusing to others to not having the closed tag in there.
3) There is no 3

You may sense my bias here.  You're not a "bad programmer" if you're using/not using tags.


Ok, so, on your web host service, let's get this setup for tomorrow.  Every host is different, but basically what you need to do is create a MySQL "test" database.  Don't need to bother with anything else in it right now, just get that created.

php, tutorial, mvc,



Web Development From Scratch, Day 2

Ok, so, we got through day one.  We used Git GUI to handle out keys, because for some reason the terminal part is scary.  Welp, time to not be scared anymore.  Technically you can use Git GUI for everything, but it's actually going to be faster to work from the terminal.

1) Open Git Bash.
2) Cower in fear of the terminal window.

Once you're done cowering, you'll need to know a couple basic commands (write these down!)

Change to your home directory
cd ~

Change to Parent directory
cd ..

Change to Root directory
cd /

Change to a subdirectory (replace <NAME> with the folder name, such as "Projects")
cd <NAME>

Navigation is fun, but need to actually be able to see *what* is in the directory:

You'll be using these "all the time", but at the beginning, you'll only be using these to get to your projects directory, and then sitting in that same place while you spam "git" commands.

NOTE: if your "projects" directory is on another drive, or rather, to change drives in general:
cd /<drive>

So "cd /D" would put you at the D: drive.  Again, only for Windows.

Since we didn't do this yesterday, let's go ahead and whip this part out: configure your username and email.

In the Git Bash terminal, set the following:
1) git config --global user.email "<EMAIL ADDRESS>"
2) git config --global user.name "<USERNAME>"

Replace the fields with your values:
git config --global user.name "SyKoHPaTh", for example.  


So, by default, when you open Git Bash, you're in your home directory.  If for some reason you aren't, do the "cd ~" thing, and you're magically back home.

1) Type "ls", and you will see something similar to your explorer-home window, with your user folders.
2) Type "cd Documents", followed by "cd Projects"
3) Another "ls" will show you your lonely "www.websitetest.com" folder.  Don't go into it quite yet.

Setup Git Repo Locally
1) Only have to do this once per repo (unless you screw something up and want to start over hah!)
2) type "git clone git@github.com:<USERNAME>/websitetest.com.git" - replace <USERNAME> with yours, so for me, it's: git@github.com:SyKoHPaTh/websitetest.com.git
    Note: This is also how you can clone other people's repos!
3) This created a new folder named "websitetest.com" next to your "www.websitetest.com".  This is intentional, because a) can't clone into a non-empty directory, and b) good habit to have a "backup" of sorts, by doing...
4) rename "www.websitetest.com" as "www.websitetest.com.backup" (or however you want to name it).  You can do this through explorer, or if you're finally confident with git commands, you can do it through the terminal:
mv www.websitetest.com www.websitetest.com.backup
    Tip:  instead of manually typing out the whole folder name, type in "ww" and then press TAB.  OMG magically filled in for you!  If you press tab and nothing happens, press it again, and it'll show you a list of matches - simply type more of the name you want to get it to complete.
5) Now, rename "websitetest.com" as "www.websitetest.com".  Easy, mind-numbing organization crap we're doing here, right?  Also, what about that awesome index.php file that's in the backup directory?  Just copy that over - through explorer, or again, terminal:
cp www.websitetest.com.backup/* www.websitetest.com

Now, you can skip this part, or if you feel like poking around:
1) Go into your www.websitetest.com folder using the terminal:
cd www.websitetest.com
2) Type "ls", and you'll see your two files: index.php README.md

How does git "know" how to handle this folder as a link to your repository?  New command (flag)!
ls -a
This displays all files, including hidden ones.

1) You'll see a .git folder.  "cd .git" to get into it.
2) "ls" in there, and you'll see all of git's innards.  Do NOT dink around with any of this stuff.  Just be aware  it's there.
3) Finally, "cd .." to get back to where we were.

FINALLY, we can start using git.  For today, we'll just mess around with the local-side, and not server side stuff.

New commands!  You already used "git clone" to get this far, now we need to use git to update and automagically upload our file changes to our repository.

1) git status
2) This shows all files that have changed, and are ready to be dealt with.  Actually reading what it says, we need to "add" our awesome index.php file to the repository.
3) git add index.php
    Note: Alternative: "git add *" will add *everything*.  Be careful doing this around sensitive files!
4) "git status" again, and it now says "Changes to be committed".
5) This next command is the one you'll be using all the fricking time: git commit.  Specifically, you'll be using it as follows:
git commit -a -m "<COMMIT MESSAGE>"
    -a means "all files that are ready to be commited"
    -m means "attach this message to the committed files"
    <COMMIT MESSAGE> is, well, whatever you want!
6) git commit -a -m "First commit, my awesome file!"
7) git status
8) Notice that no files are listed in status now.  This means they're ready to be "pushed" to our server.  You'll see the soon-to-be-familiar "Your branch is ahead of 'origin/master' by 1 commit."
   Note: "origin" is our reference to "<USERNAME>/websitetest.com" repository, while "master" is the "branch" of the repository.  More on these shortly.
9) And finally, the next command you'll be using for the rest of your life: git push.  Technically you want to use it as "git push origin master", but for now, "git push" will work.

Go back to [url=http://www.github.com]GitHub, and under your profile name, go to your "websitetest.com" repository.

1) You'll now see your "index.php" file stored in the repository, with the commit message attached to the side of it.
2) Click on the "index.php" file itself, and it will show you the contents of the file.  In this case, it will say "I'm Awesome", if you filled it out from yesterday.
3) Click "Edit", and OMG, you can edit this file directly through the site!  Type in "Super Awesome!", and put a commit message in the box under the edit area: "Added awesomeness"
4) Click "Commit Changes", and it will take you back to the file contents view.  Click "History".
5) Here, you can see all of the commits attached to that specific file.  This is HIGHLY useful, especially if you "accidentally" delete a large chunk of code and commit it; simply view the previous commits, and your code is still there! (Click "Browse Code" by "First Commit...", and you'll see the file contents before you added "Super Awesome!").
6) Click the giant blue "websitetest.com" to get back to the file view.
7) You'll see your newer commit has replaced the first commit, everything is good so far.


Ok, back in your terminal, type:
1) git pull
2) Notice that it lists your file "index.php" with a number, and + next to it, reflecting 1 line was added (- is 1 line removed!)
3) Open up index.php by any means necessary (hopefully you have SublimeText 2 running, which will detect changes in the file if you already have it open).  Yeah, make sure you have Sublime Text 2 as the default editor for .php files.  Heck, make it the default editor for everything.
4) You should now see Super Awesome added in your file!

See, at this point, consider that now all you have to do, is "git push" from your local computer, and then a "git pull" on your server...with those two commands, you'll update all the files with changes on your server, no messing around with ftp or anything like that!

It can even be made *easier* so that you can type something like "git push production frontend", and it will handle copying everything from your "frontend" branch to your "production" server.  That's beyond the scope of this document right now, but be aware that this functionality exists.


git status
git commit -a -m "message"
git push
git pull

Good enough for today?  Sure!

Actually WAIT.  This next part may take a day or two, depending on your webserver.  You have a website, right?  And it's with a crappy hosting company, right?  Heck, the disclaimer on mine says it can take up to 72 hours for it to be "enabled".  WELL THEN.

This varies greatly by hosting company, but the gist of it is:
1) Go to your hosting company website.
2) Account Settings for your website
3) Click SSH
4) Enable

Chances are you'll have to verify, which is a good thing.  Do it.  Do it now, because tomorrow's lesson will be working with the remote server stuff.

Hey look at that, I forgot to enable SSH on my test hosting server.  That's not awesome.  Welp, doing it now.  If it's not up tomorrow, I'll have to think of a different tutorial.

php, tutorial, terminal, git, github,



Web Development From Scratch, Day 1

SO, you want to get started in web development, eh?  Sure, you can crap out an html page and slap on a "I DID THIS" on the bottom, but that won't even start to get you a job, anywhere, unless you want to get ripped off by people on craigslist ("will pay in stock!").

Long gone are the days of working in notepad, ftping each file, and then f5'ing your way to success.  We're in the future, now, so let's do things appropriate to modern times.

Objective: Really basic starter setup.  I'll be leaving out a ton of the "more advanced" (from a beginner perspective) elements, and just get things up and running as quickly as possible.  Note the date of this post.  This isn't necessarily a living document, so yeah, things will change.  Enough babble, let's go!

Hardest part is the setup.  Good thing is, you only have to do it once (more or less lol).  Follow this EXACTLY omg.

1) Chances are, you're working in Windows. Personally I work in Linux, but Windows is in the majority right now.
2) File Structure.  Do This.  Seriously:
3) Start -> Documents -> Create Folder: "Projects"
4) Go into that folder, and Create another one: "www.websitetest.com"

Sublime Text 2
1) Download Sublime Text 2
2) Install it, and run it.
3) Project->Add Folder to Project
4) Navigate to "Library -> Documents -> My Documents -> Projects".  Add that one, not the subfolder you just created.
5) Now, whenever you create a new folder in your Project folder, it will magically appear in your file browser in SublimeText2.
6) On the left side, you should see your "www.websitetest.com" folder.  Right-click on it, and select "New File".
7) Type in "I'm Awesome", then save it as "index.php"

1) Go to GitHub and create an account.
2) Go through Step 1.  This will install Git on your computer.
2a) Add your SSH Key: Account Settings -> SSH Keys
3) As tempting as it is to use Git Gui, I prefer Git Bash.  Since you're a noob and are afraid of words, we'll go through Git Gui for now.
4) Open Git Gui!
4a) Click on Help, "Show SSH Key"
4b) It's blank.  Or it shows something.  If it shows something, welp, back it up.  
4c) Click "Generate Key".
4d) Press "Enter" for a blank passphrase.  Again.
4e) "Copy To Clipboard"
4f) Back on GitHub.com, click "Add SSH Key"
4g) Title: Name it something like "<computer name> Windows 7 (blank)"
4h) Paste the key, then confirm.
5) Click back to your account, and click "Create Repository"
5a) Name: "websitetest.com"
5b) Set as Public, and check the box "Initialize README"
5c) When that's done, you'll see your little repo box.  Click it!
5d) Notice the contents "https://github.com/<username>/websitetest.com.git".  Remember this, since you'll be accessing it tomorrow in the form of: git@github.com/<username>/websitetest.com.git

aaand that's good enough for one day.  Yep, leaving you hanging here!  Tough.

php, tutorial, sublime text 2, git, github,



Lone Star PHP Conference

Lone Star PHP

June 29 & 30th 2012
Addison Conference Center
Addison, TX

php, conference,



Find Match in Array of Objects

Short one today...

Normally, array_search() is awesome for finding a match within an array:

$array = array( "one", "two");
array_search("one", $array);  //true

But, in dealing with an array of objects (such as a resultset in a framework), there's not a built-in function that handles this (to my knowledge!).  So this function will handle it:

Code Sample:
  1. function search_array_object($needle, $arrayHaystack){
  2. foreach($arrayHaystack as $key=>$objectHaystack){
  3.   foreach($objectHaystack as $value){
  4.    if($needle == $value){
  5.     return $key;
  6.    }
  7.   }
  8. }
  9. }

So if you have something like this:
$arrayObj[0]->text = "one";
$arrayObj[1]->text = "two";
$arrayObj[2]->text = "three";

You'll get:
$key = search_array_object("two", $arrayObj);   // returns "1", the key where it was found




Sitemap XML Generator

Forgot I made this a while back...Ok, you know how there's "free" sitemap.xml generators?  And you know how they limit it to like, 10 links?  And you know how they suck?

Time to fix that.


1) Open "sitexmlgen.php"
2) Change the site to scan in line number 35:  $linklist[0] = "http://www.sykohpath.com/";
3) Place on your site
4) Run the script.  Example: www.yoursite.com/sitexmlgen.php
5) Wait until it finishes, will take long on large sites with many links.
6) When finished, view source
7) Copy ALL the text under "XML IS BELOW" section.
8) Paste into new document "sitemap.xml".
9) Done with generation!  If you don't know what to do with that file, learn to SEO.
10) there is no 10.

And here's the code.  Note that there *is* a 5000 link limit, imposed by sitemap.org:

Code Sample:
  1. <?php /* ---- Information ----
  2. Name: sitexmlgen.php  "sitemap.xml Generator"
  3. Last Updated:  20110512 080000
  4. Page Version: sitexmlgen.php v1.0
  5. Author: SyKoHPaTh
  6. ---- Version History ----
  7.   1.0  Initial Coding
  8. -------------------------
  9.   PURPOSE:
  10.    "crawl" a site and record the links if they are valid.
  11.    Note: this only picks up links between <a> tags.
  12. -------------------------
  13.   TODO:
  14. -------------------------
  15.   LICENSE:
  16.    Modification: OK, but must keep credit line: "SyKoHPaTh (www.sykohpath.com)", and this License.  Any modifications MUST be written in "Version History", with your name and/or handle, and what the modification was.
  17.    Free for public and commercial use.  If you paid for this, you got scammed.
  18. --------------------------
  19. */
  20. /* -------- VARIABLES -------- */
  21. $linklist = array();
  22. $linklist[0] = "http://www.sykohpath.com/";
  23. $sitemap_limit = 50000;  //enforced by sitemap.org, max number of <url> links in one sitemap XML file.
  24.         // there is also a 10MB limit to sitemap XML files, but we're not checking for that here.
  25. /* -------- FUNCTIONS -------- */
  26. function digger($scanlink) {
  27.   //does the work of scanning a page and putting links into an array
  29.   $linkcontents = @file_get_contents&#40;$scanlink&#41;;
  30.   if(!$linkcontents) {
  31.    print "Unable to open: {$linkcheck}n";
  32.    return array();
  33.   }
  34.   $linkinfo = parse_url($scanlink);
  35.   $linkcore = $linkinfo['scheme'] . "://" . $linkinfo['host'];
  36.   $linkcontents_strip = strip_tags($linkcontents, "<a>");
  37.   $linkcontents_mod = preg_replace("/<a([^>]*)href="//is", "<a$1href="{$linkcore}/", $linkcontents_strip);
  38.   $linkcontents_mod = preg_replace("/<a([^>]*)href="?/is", "<a$1href="{$scanlink}/?", $linkcontents_mod);
  39.   preg_match_all("/<a(?:[^>]*)href="([^"]*)"(?:[^>]*)>(?:[^<]*)</a>/is", $linkcontents_mod, $matches);
  40.   return $matches[1];
  41. }
  42. function checklink($linkcheck) {
  43.   //simply checks a link to see if it loads up or not
  44.   $linkcontents = @file_get_contents&#40;$linkcheck&#41;;
  45.   if(!$linkcontents) {
  46.    print "Unable to open: {$linkcheck}n";
  47.    return false;
  48.   }
  49.   return true;
  50. }
  51. /* -------- Initial header thing -------- */
  52. $xmloutput = "<?xml version="1.0" encoding="UTF-8" ?>n<urlset   created with SyKoHPaTh's SiteMap.XML generator  www.sykohpath.com  -->n";
  53. $x = 0;
  54. while(1==1){
  55.   //gen list
  56.   print "Scanning [$x of " . (count($linklist)-1) . "]: " . $linklist[$x] . "<br>n";
  57.   $linkmatch = digger($linklist[$x]);
  58.   //scan list
  59.   foreach($linkmatch as $key=>$value){
  60.    //print $key . ": " . $value . "n";
  61.    //filter bad data
  62.     //check link against $linklist[0]
  63.    if(substr($value, 0, strlen($linklist[0])) == $linklist[0]){
  64.     if(!(in_array($value, $linklist))){
  65.      //push to array
  66.      $linklist[] = $value;
  67.      $xmloutput .= "<url>nt<loc>" . $value . "</loc>n</url>n";
  68.     }
  69.    } else {
  70.     //check if it's a foreign link
  71.     if(!substr($value, 0, 4) == "http"){
  72.      //add scanned linklist to front, and see if it's a valid link
  73.      //cut out everything after the slash:  http://w3dev.millerind.com/parts/index.php?bid=2
  74.      $pattern = preg_replace("/[^/]*$/s", "", $linklist[$x]);
  75.      $value = trim($value); //strip whitespace BAD CODER, BAD!
  76.      $value = preg_replace("/^[/]/s", "", $value); //strip beginning / if there is one
  77.      if(checklink($pattern . $value)){
  78.       $value = $pattern . $value;
  79.       if(substr($value, 0, strlen($linklist[0])) == $linklist[0]){
  80.        if(!(in_array($value, $linklist))){
  81.         //push to array
  82.         $linklist[] = $value;
  83.         $xmloutput .= "t<url>ntt<loc>" . $value . "</loc>n</url>n";
  84.        }
  85.       }    
  86.      }
  87.     }
  88.    }
  89.   }
  90.   //echo "Total links: " . count($linklist) . "<br>n";
  91.   //if nothing new was added, exit loop
  92.   if($x + 1 >= count($linklist)){ break; }
  93.   //if limit reached, exit loop
  94.   if($x > $sitemap_limit - 1){ break; }
  95.   $x=$x+1;
  96. }
  97. //Optional tags for each link.
  98. //<lastmod>" . date("Y-m-d") . "</lastmod>n
  99. //<changefreq>yearly</changefreq>n
  100. //<priority>0.5</priority>n
  101. $xmloutput .= "</urlset>";
  102. print "<br>n<br>n-----------------------------------------------------------<br>n           XML IS BELOW (view source)<br>n           Copy and paste into "sitemap.xml"<br>n-----------------------------------------------------------<br>
  103. n" . $xmloutput;
  104. ?>

php, xml, sitemap,



MyAnimeList Signature Generator

ok, time for a long script!

This generates an image that shows your recent viewing history on My Anime List.  Of course, it's all tuned for me, but it's all easily changeable.  I've seen a few signatures for MAL already, but I didn't like how they worked, exactly.  Some were just plain badly coded (not that mine is 100% perfect), so I decided to write my own (viewable here):

Code Sample:
  1. <?php
  2. /* -----------------------
  3. MyAnimeList.net User History Signature Generator ()
  4. ----------------------- */
  5. $url = "http://myanimelist.net/rss.php?type=rw&u=" . $_GET['u'];
  6. $file = fopen&#40;"$url", "r"&#41;;
  7. //store page contents into buffer
  8. if (!$file) {
  9.   //if we can't create a file MAL is down, so create appropriate message as image instead!
  10.   $image = imagecreate(600,110);
  11.   $textcolor = imagecolorallocate($image, 255,255,255);
  12.   imagestring($image, 5, 85,26, "Unable to connect to MyAnimeList.net", $textcolor);
  13.   exit; //should make this end prettier
  14. } else {
  15.   $buffer = "";
  16.   while (!feof($file)) {
  17.    $buffer .= fgets($file,1024);
  18.   }
  19.   fclose($file);
  20. }
  21. //strip special chars
  22. $buffer = str_replace("n", "", $buffer);
  23. $buffer = str_replace("r", "", $buffer);
  24. $buffer = str_replace("t", "", $buffer);
  25. //parse RSS tags
  26.   //grab all item things
  27. $pattern = "/<item>(.*?)</item>/";
  28. preg_match_all($pattern, $buffer, $match_items, PREG_SET_ORDER);
  29. if(!empty($match_items)){
  30.   $x=0;
  31.   foreach($match_items as $matches){
  32.    //[0] includes item tags, [1] does not
  33.    //title
  34.    $pattern = "/<title>(.*?)</title>/";
  35.    preg_match($pattern, $matches[1], $match_title);
  36.    //description to determine status
  37.    $pattern = "/<description>(.*?)</description>/";
  38.    preg_match($pattern, $matches[1], $match_description);
  39.    //date
  40.    $pattern = "/<pubDate>(.*?)</pubDate>/";
  41.    preg_match($pattern, $matches[1], $match_date);
  42.    //link
  43.    $pattern = "/<link>(.*?)</link>/";
  44.    preg_match($pattern, $matches[1], $match_link);
  45.    //grab only "watching"
  46.    if(strpos($match_description[1], "Watching") !== FALSE || strpos($match_description[1], "Completed") !== FALSE){
  47.     $anime[$x]->title = $match_title[1];
  48.     $anime[$x]->status = $match_description[1];
  49.     $anime[$x]->date = $match_date[1];
  50.     $anime[$x]->link = $match_link[1];
  51.     //optional formatting - will change "title - TV - Completed - X of Y episodes" to "title - X/Y"
  52.     $anime[$x]->status = str_replace(" of ", "/", $anime[$x]->status);
  53.     $anime[$x]->status = str_replace(" episodes", "", $anime[$x]->status);
  54.     //Pre PHP 5.3
  55.      $before = explode(" - ", $anime[$x]->title);
  56.      $anime[$x]->title = $before[0];
  57.      $anime[$x]->status = strstr($anime[$x]->status, " - ");
  58.     //PHP 5.3 only
  59.      //$anime[$x]->title = strstr($anime[$x]->title, " - ", true);
  60.      //$anime[$x]->status = strstr($anime[$x]->status, " - ", false);
  61.     $anime[$x]->status = str_replace(" - ", "", $anime[$x]->status);
  62.     //increment object counter
  63.     $x++;
  64.    }
  65.   }
  66. }
  67. $image = imagecreate(600,110);
  68. $background = imagecolorallocate($image, 0,0,0);
  69. $bgtextcolor = imagecolorallocate($image, 50,50,50);
  70. $textcolor = imagecolorallocate($image, 200,200,200);
  71. $linecolor = imagecolorallocate($image, 255,0,0);
  72. // ---- scrambled text background ---- // (OPTIONAL)
  73.   $characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890~!@#$%^&*()_+-=`[]{}:;'|,.<>/?";
  75.   for($vcntr=-1;$vcntr<60;$vcntr+=2) {
  76.    for($cntr=-1; $cntr<160; $cntr+=2){
  77.     $letter = substr($characters, rand(0,(strlen($characters)-1)),1);
  79.     $spacing = (rand(1,10)+($cntr*4));
  80.     $vspacing = (rand(1,10)+($vcntr*4));
  82.     imagestring($image, 5, $spacing, $vspacing, $letter, $bgtextcolor);
  83.    }
  84.   }
  85. // ---- logo image merge ---- // (OPTIONAL)
  86.   $logo = @imagecreatefromgif("http://www.sykohpath.com/images/sykohpath.gif");
  87.   if($logo){
  88.    //logo was loaded
  89.    imagecopymerge($image, $logo, 15, 12, 0, 0, 50, 50, 100);
  90.    imagerectangle($image, 15, 12, 65, 62, $linecolor);
  91.   }
  92. // ---- generic linkback ---- // (OPTIONAL)
  93.   imagestring($image, 4, 485, 90, "sykohpath.com", $linecolor);
  94. // ---- anime title status lines ---- // (REQUIRED)
  95.   for($i=0;$i<5;$i++){
  96.    if(!empty($anime[$i])){
  97.      //readability lines
  98.     imagerectangle($image, 85, ($i*16)+25, 580, ($i*16)+25, $linecolor);
  99.     imagestring($image, 5, 85,($i*16)+10, $anime[$i]->title, $textcolor);
  100.     imagestring($image, 5, 400,($i*16)+10, "(" . $anime[$i]->status . ")", $textcolor);
  101.     imagestring($image, 5, 480,($i*16)+10, date("Y M d", strtotime($anime[$i]->date)), $textcolor);
  102.    }
  103.   }
  104. // ---- border ---- // (OPTIONAL)
  105. imagesetthickness($image, 2);
  106. imagerectangle($image, 0, 1, 599, 108, $linecolor);
  107. // ---- generate the image (finally) ---- //
  108. header( "Content-type: image/png");
  109. imagepng($image);
  110. // ---- free up resources ---- //
  111. imagecolordeallocate($linecolor);
  112. imagecolordeallocate($textcolor);
  113. imagecolordeallocate($background);
  114. imagedestroy($image);
  115. ?>

php, script, myanimelist, mal,



Handling Fake Tags

muahahah fixed it all...sorta.  dang, I can't read the form text now...whoops.

Ok, here's now to made "fake" bbcode tags, and autoformat numbered code lines (simply remove the Y in the tag, or make up your own tags to whatever you want):

Code Sample:
  1.     //fix html tags for specials
  2.      //faked bbcode
  3.     $text = str_replace("[Ycode]","<code>",$text);
  4.     $text = str_replace("[/Ycode]","</code>",$text);
  5.     $text = str_replace("[Yp]","<p>",$text);
  6.     $text = str_replace("[/Yp]","</p>",$text);
  7.     $text = str_replace("[Ypre]","<pre>",$text);
  8.     $text = str_replace("[/Ypre]","</pre>",$text);
  9.     $text = str_replace("[Ycopy]","©",$text); //copyright symbol
  10.     $text = str_replace("[Yreg]","®",$text); //registered symbol
  11.     $text = str_replace("[Ydeg]","°",$text); //degree symbol
  12.     //numbered code generation
  13.     $pattern = "/[Yncode.*?](.*?)[/Yncode]/";
  14.     preg_match_all($pattern, $text, $matches);
  15.     if(is_array($matches)){
  16.      if(is_array($matches[1])){ //means there are multiple matches
  17.       foreach($matches[1] as $value){
  18.        $replaceString = $value;
  19.        $codeLine = explode("<br>", $value);
  20.        $value = "<ol><label  Sample:</label>";
  21.        foreach($codeLine as $codeRow){
  22.         $value .= "<li><code  . $codeRow . "</code></li>";
  23.        }
  24.        $value .= "</ol>";
  25.        $text = str_replace($replaceString, $value, $text);
  26.       }
  27.      }
  28.     }
  29.     //finally, remove the marking tags!
  30.     $text = str_replace("[Yncode]","",$text);
  31.     $text = str_replace("[/Yncode]","",$text);

Code Sample:
  1. code {
  2. font-family: Courier, monospace;
  3. color:#000;
  4. /*font-style: italic;*/
  5. font-size:10pt;
  6. background: url("http://www.sykohpath.com/images/background.jpg") repeat;
  7. margin: 0px 0px 0px 0px;
  8. padding: 0px 0px 0px 10px;
  9. display: block;
  10. border:1px #c01 solid;
  11. }
  12. ol {
  13. background: #c01;
  14. overflow:auto;
  15. font-family:Verdana;
  16. text-decoration: bold;
  17. color:#eee;
  18. margin: 0px 10px 0px 20px;
  19. padding: 0px 2px 2px 25px;
  20. border: 1px #ffccee dashed;
  21. }
  22. ol li {
  23. background: url("http://www.sykohpath.com/images/background.jpg") repeat;
  24. font-size:10px;
  25. font-family:Verdana;
  26. text-decoration: bold;
  27. list-style-type: cjk-ideographic;
  28. }

php, tags, code,



Random Background Content Generator

New background.  It's randomly generated:

Code Sample:
  1. <?php
  2. $db_img = imagecreate(800,600); //size of image
  3. $background = imagecolorallocate($db_img, 0,0,0);
  4. $textcolor = imagecolorallocate($db_img, 150,0,0);
  5. $linecolor = imagecolorallocate($db_img, 0,0,0);
  6. $characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890~!@#$%^&*()_+-=`[]{}:;'|,.<>/?";
  7. for($vcntr=-1;$vcntr<60;$vcntr+=2) {
  8.   for($cntr=-1; $cntr<80; $cntr+=2){
  9.    $letter = substr($characters, rand(0,(strlen($characters)-1)),1);
  11.    $spacing = (rand(5,10)+($cntr*10));  //x spacing
  12.    $vspacing = (rand(5,10)+($vcntr*10));  //y spacing
  13.    imagestring ($db_img, 5, $spacing, $vspacing, $letter, $textcolor);
  14.   }
  15. }
  16. imagesetthickness($db_img, 5);
  17. header( "Content-type: image/png");  //specify the type of image
  18. imagepng($db_img);
  19. imagecolordeallocate($linecolor);
  20. imagecolordeallocate($textcolor);
  21. imagecolordeallocate($background);
  22. imagedestroy($db_img);
  23. ?>

Save as something like, background.php.  Next, call it in and it's MAGIC...css:

background: #ffffff url("background.php") fixed;

code, php,