Jump to content

Most Liked Content


#6 Just starting HTML/CSS?

Posted by Kyek on 28 February 2010 - 07:41 AM

What's all this now?
HTML and CSS are the standard (very simple) languages to make web pages. HyperText Markup Language defines the structure of the page and its content, while Cascading Style Sheets determine how it all looks and how it gets arranged on the page.

What about XHTML? DHTML? How many kinds of HTML *are* there?
There's a simple language known as XML ("eXtensible Markup Language") that's very similar in looks to HTML, but has stricter rules that make it easier for computers to read. XHTML is simply the idea behind applying some of those strict rules to HTML so that your code gets even cleaner. Many developers prefer XHTML, but that's something to worry about only once you've learned this stuff.

DHTML stands for Dynamic HTML and is simply HTML with Javascript. Real web developers laugh at people who say DHTML, because generally the only ones who use that term are the people who don't really understand what it is. It's just Javascript. You'll learn that a bit later :)

How to get started!
There are GUI point-and-click editors like Dreamweaver and iWeb to make websites, and in a pinch, they can be useful to see how a certain design might be coded. But there's one thing you should know, right off the bat:

Real web developers do not use point-and-click programs like Dreamweaver. They write their own code.

So if you're serious about learning this stuff, grab your favorite text editor, sit down with a beginner's tutorial, and get rolling! You'll find some really awesome guides below, so follow them as best as you can, then try your hand at making something original. When you run into a problem, come back here! Search the forum, and if you can't find what you need, ask away.

Guides
When it comes to web guides, there's no beating Tizag.com. But W3Schools.com has some good primers as well -- so take a look at both, and go with what you like!

Tizag's Beginner HTML Tutorial
Tizag's Beginner-to-Advanced CSS Tutorial

W3Schools' HTML Tutorial
W3Schools' XHTML Info
W3Schools' CSS Tutorial
W3Schools CSS Reference Sheet <-- This thing's a lifesaver!


#7239 Your MySQL Code Sucks.

Posted by Kyek on 20 March 2010 - 10:15 PM

So from the sounds of most threads in the PHP forum here, all folks know how to use is the mysql_connect family of functions when it comes to talking to a MySQL server. And that sucks. Here's why:

  • The mysql_ library was meant only for MySQL versions earlier than 4.1. Most MySQL installations are using version 5.1. 5.5, at the time of this writing, is in preview stages.
  • MySQL 4.1 was released in 2004. It's currently 2012. The mysql_ library has been obsolete for EIGHT YEARS.
  • The mysql_ library isn't object-oriented. Including it in modern-day PHP produces very messy code that's impossibly hard to manage.
  • The library is slow. Not going into specifics, much of the processing for the mysql_ library is done inside of PHP itself instead of being performed natively, which is a huge performance hit. Additionally, using mysql_ forces you to concatenate lots of strings to do what you need to do, which is another performance hit.
  • The library is insecure. Cryptic functions like mysql_real_escape_string had to be written into it solely to help programmers write code that wasn't completely open to hackers, and even WITH that most sites are still at risk.
  • You don't have access to a huge (huge!) number of really awesome MySQL 4.1+ features when using this antiquated library.

OMG Kyek, if the mysql_ commands suck so hard, why do all the PHP guides still use it?
I would LOVE to know. Some guides were written back when pre-4.1 MySQL was still in popular use, so that I can understand. But all these new guides that still use it just have no excuse. Either the author didn't know any better, didn't want to take the time to learn the better libraries, or for some reason thought that it was easier for people to learn the old stuff. All of these reasons are bad.

So what should I use?
There are a handful of alternatives to these commands, but currently the two most popular libraries are PDO ("PHP Data Objects") and MySQLi. MySQLi was first, and took over where mysql_ left off. It's a very good library, but once PDO was released, there was almost no reason to use it. You can look up all the details if you're interested, but there are three big, main reasons I say PDO is the better library to use:
  • It supports a whole range of SQL servers. In addition to MySQL, it will connect to PostgreSQL, MSSQL, Oracle, DB2, and a range of other popular SQL servers -- and you use the exact same functions and objects to access the database no matter which type of server you're connecting to. That means that if you ever need to move to a different server with a different type of SQL on it (moving from Windows to Linux or vice-versa is a really common one), porting your code to work on the new platform is a SNAP.
  • PDO puts MUCH more emphasis on object-oriented use than MySQLi does. Using PDO makes it a lot harder to write bad code, and that's a very good thing :).
  • When writing queries with PDO, you don't have to specify the "type" of your variables. With MySQLi, you do. This is a pain in the neck and puts a lot of limitations on you if you're trying to write a database wrapper. Even if you have no idea what a wrapper is and what it's for now, trust me -- you'll thank yourself in the future when you do.

Ok, I'm sold. Where do I start?
The concept of PDO is similar to the mysql_ methods that many of you are already used to. You still connect to a database, you still send queries, you still parse through the results. It's just that the code you use to do these things is a little bit different, especially since we're going to be using Objects.

If the idea of using Object Oriented stuff scares you, relax :) It's very very easy to pick up, and hopefully you'll see the benefit of using these types of concepts in the rest of your code too :)

Let's connect to a database!
To connect to a MySQL database, you do this:
// Fill in all the info we need to connect to the database.
// This is the same info you need even if you're using the old mysql_ library.
$host = 'localhost';
$port = 3306; // This is the default port for MySQL
$database = 'myDatabase';
$username = 'myDatabaseUser';
$password = 'myDatabaseUserPassword';

// Construct the DSN, or "Data Source Name".  Really, it's just a fancy name
// for a string that says what type of server we're connecting to, and how
// to connect to it.  As long as the above is filled out, this line is all
// you need <img src='http://webdevrefinery.com/forums/public/style_emoticons/default/smile.gif' class='bbc_emoticon' alt=':)' />
$dsn = "mysql:host=$host;port=$port;dbname=$database";

// Connect!
$db = new PDO($dsn, $username, $password);

That's it! Really no different from the mysql_connect method you might be used to. The only difference is that DSN bit, which we need since PDO has to know what type of database we're connecting to.


Your first query
So, we have a website all about fish recipes. A bunch of recipes for trout, a bunch of recipes for haddock, a bunch for tuna, and on and on and on. There's a search form on this website that lets people search by fish type and their favorite chef that made the recipe, and it hands back the first 20 recipe names it finds. What we need to do is write code that will query the database and return the results.

I'm going to assume you know some basic SQL, here -- for example, what a "SELECT" statement looks like. If you don't know that yet, go brush up on some SQL and come back :)

Here's our code. This is going to be crazy, but don't freak out -- I'll go through this line-by-line:
$statement = $db->prepare("SELECT recipe_name FROM recipes WHERE fish_type = ? AND chef_name = ? LIMIT 20");
$statement->execute(array($_POST['fish'], $_POST['chef']));

while ($result = $statement->fetchObject()) {
    echo $result->recipe_name;
    echo "<br />";
}

OMG KYEK WTF IS "PREPARE" AND "STATEMENT" AND THAT ARROW LOOKING THING LIKE -> WTF IS THAT OMG I DON'T UNDERSTAND THE WORLD IS FUCKING MELTING I TELL YOU MELTING HOW CAN YOU POSSIBLY PACK SO MUCH HATE INTO FIVE LINES OF CODE
Let's walk through it, shall we? :)
$statement = $db->prepare("SELECT recipe_name FROM recipes WHERE fish_type = ? AND chef_name = ? LIMIT 20");
Of our few lines of code up there, this one's the hardest to understand. So let's go through step by step. First, we're starting with "$statement =", so you know that whatever the rest of the line produces will be dropped into a variable called $statement. That part's easy :)

Next, we're talking to $db. With mysql_connect, once you connect to a database, the library magically "knows" about that connection in the future. Object-oriented code doesn't work that way. If you look up at our connection script above, you'll see that we're setting $db equal to a "new PDO" -- that's an "object", or an all-wrapped-up block of code that has its own variables and functions and all kinds of cool stuff inside of it. So to interact with that stuff, we need a way of telling PHP "I want to get inside of $db and use something in its package". That's what our little arrow -> is for :) "$db->" says "whatever comes next is something inside of the $db object".

And the "prepare" function we're calling is one of those things inside our $db object :). This is where it starts getting different from what you're used to. With PDO (and MySQLi, in fact), you "prepare" an SQL query before you use it. Not only does this allow the query to run faster, it lets you specify "blank" spots where you can fill in variables later. Notice the two question marks in that string? When we execute this prepared statement, we're going to pass in two variables that PDO will automatically fill in there -- and there is NO NEED for any escape_string or addslashes hacks! A query like this is called a Parameterized Query, and is totally, 100% safe from all SQL injection attacks.

$statement->execute(array($_POST['fish'], $_POST['chef']));
In this line, we're taking the $statement object that we created in the last line, and telling that statement to run. Since we have two question marks in the prepared query, we need to supply an array of two variables in the execute() function. The first question mark is where the fish type needs to go, so we're dropping in $_POST['fish'], our form field for the fish type. Similarly, we're dropping in $_POST['chef'] to put the user's chef input in place of the second question mark of our prepared query.

With this line, the query runs :) The results are saved inside of that same $statement object we're using, as you'll see in this little block of code:
while ($result = $statement->fetchObject()) {
    echo $result->recipe_name;
    echo "<br />";
}

If you're familiar with the mysql_ library, you'll recognize that first line. That $statement->fetchObject() function returns the next result in the group of results MySQL returned. When we reach the end of the list, that function returns false, so the loop ends. What we're doing here is setting $result equal to whatever $statement->fetchObject() gives us right there inside that 'while' statement, so that inside the loop, $result will be the current result we're working with.

Now the magic is almost over :) All we're really doing inside the loop is echoing out $result->recipe_name, which would be just like saying $result['recipe_name'] to most of you folks who are used to the mysql_ commands. You can tell PDO to pass the results back inside of an associative array like that, but getting them as an object is a little faster. If you're feeling a little on the wild side, PDO supports a few other ways to get the results too -- like updating a variable of your choice with them automatically ;-)

This is only the very tip of the iceberg!
PDO is capable of so many more things, from different ways of running parameterized queries to different ways of grabbing results to allowing you to execute the same prepared statement more than once with different variables each time. Some really, really cool stuff :) This guide is meant to get you started with PDO and show you how each of the pieces works. If you're ready to learn more, all you need to do is visit PHP.net's official PDO documentation.

There's one last thing you should know
I mentioned above that one of the benefits of PDO was that it can switch to different types of SQL servers. What you should know in advance is that the SQL language is a little different from server type to server type. MySQL supports different SQL commands than PostgreSQL which supports different commands than MSSQL. While PDO itself will connect to a range of databases, your queries might need to be tweaked in order to run on them.

If you're looking to write code that can run on any SQL server type without being tweaked or rewritten, you need a database wrapper library that has the ability to "translate" your SQL queries so they'll work on any of these servers. You could do a lot of research and attempt to write your own... or, you could use Hydrogen ;-). </shameless plug>


Go forth and code better!


#24572 How To Make a User Account System

Posted by Kyek on 13 June 2010 - 07:46 PM

How To Make a User Account System
Written by Kyek
June 13, 2010


So you want user accounts on your site, huh?
Awesome. User accounts let you do so much more with a site, but they're also one of the most easily-exploited pieces of most websites. While most people familiar with a web language could whip up a user login system, writing one that can't be easily compromised by hackers, script kiddies, or a banned user with a grudge is far more difficult. This guide will walk you through the concepts of how to build a system that can be as loose or as secure as you want it to be, and is safe for not only your website, but even moreso your website's users. This system will be fairly advanced -- it's similar to what I used on Appulous as well as a handful of other websites. To date, it's not been broken.

Want to build your own login system? This guide is for you.
If you're looking for free code to plug into your website, turn back now. I'm assuming you have a good working knowledge of PHP and MySQL, and the drive to build and test your own system. I'll tell you what cookies to set, what to store in a session variable, what information you can cache, and how to generate secure checksums, but it's up to you to actually know how to program all of that. Sound super scary? Try starting off with something a bit easier, and work your way up to this :).

Let's talk features!
Here's what your new account system will be capable of:
  • Registering new users with or without E-mail verification
  • Logging users into your website with a "Remember Me" function (to allow them to auto-login when they visit your page in the future)
  • Assigning users to groups, with each group having a certain permission set.
  • Allowing you (the admin) to override certain permissions for specific users (to allow, for example, "Frank" -- in the normal member group -- to ban other members)
  • Allowing you to ban:
    • Individual accounts
    • Anyone who tries to sign up with a certain E-mail address (or partial E-mail address)
    • Anyone who logs in with a certain IP address, or with an IP address that falls into a range of banned IPs
  • Allowing users to be "signed in" (have auto-login set up) on a certain number of different computers at one time. You get to specify the number.
  • Allowing users to click a link and type their password to force all of their logged-in computers to log off. Useful for people who accidentally clicked "Remember Me" on a public computer.
  • All passwords are salted and encrypted on the server, so users are safe even if a hacker gets a copy of your database.
  • Cookie theft prevention. If one user steals another's cookies and attempts to hijack their login session ... they can't. And you can detect this and ban their IP if you so wish.
  • And more stuff that I might add to this list later :)

Let's start with the database tables!
You're going to need five (yes, five!) database tables for this bad boy. Here's an outline of each table and its fields:

users
  • id bigint, unsigned, auto-increment PRIMARY KEY
  • username varchar, 32 characters (or however long you want usernames to be) UNIQUE INDEX
  • email varchar, 80 characters (or however long you want the E-mail to be) UNIQUE INDEX
  • group_id bigint, unsigned
  • salt varchar, 5 characters (or however long you want the salt to be)
  • passhash varchar, 32 characters (the length of our hash)
  • perm_override_remove bigint, unsigned
  • perm_override_add bigint, unsigned
  • reg_date datetime, or however else you like to store the date
  • last_login_date datetime, or however else you like to store the date
  • reg_ip unsigned int
  • last_login_ip unsigned int
  • must_validate boolean/tinyint

autologin
  • id bigint, unsigned, auto-increment PRIMARY KEY
  • user_id bigint, unsigned INDEX
  • public_key varchar, 32 characters (or the length of your hash) INDEX
  • private_key varchar, 32 characters (or the length of your hash)
  • created_on datetime, or however else you like to store the date
  • last_used_on datetime, or however else you like to store the date
  • last_used_ip unsigned int

groups
  • id bigint, unsigned, auto-increment PRIMARY KEY
  • group_name varchar, 32 characters or the max length of your group names)
  • permissions bigint, unsigned

forced_group_ips
  • id bigint, unsigned, auto-increment PRIMARY KEY
  • group_id bigint, unsigned
  • ip_low unsigned int INDEX
  • ip_high unsigned int INDEX
  • created_on datetime, or however else you like to store the date
  • notes varchar 255 characters (or enough characters for you to make a quick note on why this IP or IP range is banned)

banned_emails
  • id bigint, unsigned, auto-increment PRIMARY KEY
  • email_regex varchar, 120 characters (or whatever you need -- 120 has been a good number for me)
  • created_on datetime, or however else you like to store the date
  • notes varchar 255 characters (or enough characters for you to make a quick note on why this email is banned)


Explanations for the above fields!
Most of what you see up there is pretty straightforward. But here are a few notes on the parts that might not be:
  • All IP addresses should be stored in integer form. So instead of, say, "194.247.44.146", you would be storing 194*256^3 + 247*256^2 + 44 * 256^1 + 146, which is 3270978706. PHP has native functions that convert between human-readable-IPs and integer IPs, don't worry :) They're ip2long() and long2ip. Not only does this format save space, they let you run comparison operators within mysql -- which will be super useful to determine if an IP falls within a banned range. The highest possible IP address is 255.255.255.255. Convert that and you get 4294967295, which is the maximum value for an unsigned int. Hence, ip fields are unsigned ints.
  • I use unsigned bigints for my 'id' fields, the primary key in each of my mysql tables. Before you criticize that an unsigned bigint is WAY larger than anyone would possibly need and that I'm wasting space, keep in mind that an extremely common attack on login-based sites is to flood the tables in an attempt to expire the available primary keys. An unsigned bigint is unreachable through this method. 4.2 billion for an unsigned int, however, might be attainable if you're a big enough target. Better safe and marginally more disk-heavy than sorry.
  • Never heard of a salt? read this. I'll be using md5 hashing for all the hashes here, which is a bad idea for passwords -- unless you salt them. md5 is a few orders of magnitude faster than other hashing algorithms, so using that is a small but important step to keeping your login system lightweight.
  • You can do permissions in many different ways. Recently, I've switched to a 64-bit binary method, which can be stored in an unsigned bigint. Change those fields appropriately if you'd rather track permissions differently. See the Permissions section below on what these fields are for and how to use them.
  • I am passionate about being able to track, neutralize, and punish trolls, hackers, and banned folks trying to make new names. As such, I attach an IP address and a borderline-paranoid timestamp to every single piece of data where it applies. This allows me to build ridiculously strong autodetection systems for people who shouldn't be on my site, and it's paid off in the past. You can omit many of these fields if you want to, but if you hate the idea of other people winning, then I suggest keeping them :)
  • The autologin table has both public_key and private_key fields. This does not imply encryption ;-). Read on for how that works.

How to use it!
Before you launch:
  • Determine your permissions scheme and write a list of constants that defines which permission is which. For much more detail about this, read the section on Permissions below.
  • Create an entry in the groups table for every group. One for banned users, one for users that must validate their E-mail addresses (if you choose to do that), one for guests, one for normal members, one for moderators, etc. Set their permissions accordingly.
  • Make entries in the banned E-mails table if you so desire. Do yourself a favor and use an honest-to-goodness regex string, like this: .*@mailinator.com (to block mailinator). If you want to make your interface easy and make * the wildcard character there, that's fine -- but remember to convert it to (.*) before inserting into mysql.
  • Throw session_start() at the top of any page (or any controller, if you're using the MVC pattern) that will have any user account information on it. Don't do this to every page indiscriminately -- chances are you don't want to open a session when someone looks at your RSS feed, for example.

When someone registers, you...
  • Check the database for an existing username or e-mail address (using 'like' so it's case insensitive!)
  • Check the E-mail address against every RegEx string in the banned_emails table. If you get a match, throw up an error message.
  • Generate a random five-character (or more) string for the salt
  • Hash the salt, hash the password, and hash them together. Like this: md5(md5($password) . md5($salt)). If you're super awesome, you could detect in both your login and registration form if javascript is enabled, and if it is, md5 the password before sending it to the server. Then it's just md5($passhash . md5($salt)).
  • Convert the user's IP address to an integer with ip2long($_SERVER['REMOTE_ADDR'])
  • Validate form fields (as you always should)
  • Insert it into the database!

Want the registering user to have to verify his E-mail address?
  • Set must_validate to 1.
  • Generate a key. This key should be an md5 hash of the e-mail address and the salted password hash concatenated together. Why these two things? Because if any single one of them changes before the validation link is clicked, you don't want the old validation link to work.
  • Send an E-mail to the user. In this E-mail, you need a link to your login page, which includes the validation code you just generated (in a GET variable or otherwise). Having the user log in to verify the E-mail is absolutely the most secure way of doing E-mail verification -- so read on to the login section for instructions on how to do this.

When someone tries to log in:
  • Look at the username first. Attempt to pull in a row of data with a username 'like' what was submitted. If there is no data, login failed. Otherwise, continue.
  • Using the salt pulled from the database for this user, reconstruct the passhash. Compare it to the passhash pulled from the database. If it doesn't match, login failed. Otherwise, continue.
  • Are you requiring E-mail addresses to be verified? If no, skip these indented steps :). Otherwise:
    • Check to see if must_verify is 1. If not, no verification needed! Jump out of this block and continue :) If so:
    • Check to see if a verification code has been passed in with the URL. No? Login failed, offer to send another E-mail. Otherwise:
    • Reconstruct the verification code with the stuff you pulled from the database and see if it matches the code in the URL. No match? Verification failed, offer to send another E-mail. Otherwise:
    • Change must_verify to 0, change the group id (if appropriate), and continue the usual login process :)
  • Set a session variable named "USER_ID". Believe it or not, the value of this variable should be the id of the user logged in.
  • Paranoia time! Set any additional session variables with things you want to make sure are unchanged from page load to page load. One of these should definitely be the entire user agent string, because if someone logs in with one useragent then loads a page with another useragent, chances are that someone stole their session cookie. Some folks like to check the IP address as well, but note that this can cause problems with some ISPs and mobile devices that change IP addresses between requests.
  • Is "Remember Me" checked? If not, woo we just logged in! Redirect to whatever page you like and have fun :) If it IS checked... continue :)
  • Generate a unique "public" key to be used for the autologin process for this computer. Some folks use the automatically generated PHP session ID, but I like to use something different in case the session manager is changed. Concatenate the E-mail address with the current date and time and md5 it, if you want. The data isn't important -- you just need a long, pseudo-random string that people can't possibly guess.
  • Generate a "private" key based on information that should ALWAYS be the same when this autologin is used. If any piece of data involved in the generation of this string changes, the autologin will fail. So you never want to use the IP address, since that can change constantly for some users. Using the whole useragent string is borderline paranoid, but good. You could extract just the browser type and OS for a less paranoid solution. Either way, ALWAYS INCLUDE THE USER'S CURRENT SALT! So if you're not sure what to do here, concatenate the salt with the user agent, md5 it, and call it a day.
  • Select the count of all rows in the autologin table with the user_id of the current user. If that number is equal to the maximum allowed number of simultaneously logged-in computers (you pick this number), then delete the one with the oldest last_used_on date.
  • Insert a new row into the autologin table. Fill out all the fields with the data we have, but last_used_on and last_used_ip should be NULL.
  • Set a cookie named "publickey" with the user id concatenated with the public key we just generated.
  • Redirect to a new page!

When someone logs out:
  • Unset the session variable USER_ID. Optionally, you can session_destroy(), but note that this will also kill any session data your site sets that's not associated with the user account.
  • Delete the publickey cookie if it exists.
  • Redirect to a new page :)

When someone loads a plain old normal page that you need the user account for:
  • Call session_start(). (You're already doing this, right? Right? :D)
  • Check to see whether the session variable USER_ID exists.
  • USER_ID exists:
    • Check any other information you have saved against the current request. At minimum, you should have the useragent saved in a session variable. Check it against the current useragent. If it's different, logout. Optionally, redirect to an error page. If it's a match, continue.
    • Query all the user data you need from the database, using the User ID in the session variable.
    • Convert the user's IP address to an integer. Run a query against the forced_group_ips table to select a row in which ip_low is less than or equal to the current IP, and ip_high is greater than or equal to the current IP. Join the groups table on the group_id so that you get the group's permissions. If you get a result, use this group's permissions instead of the ones for the user's group and the user's overrides. For more information, see "The Permissions System" below.
    • You're all set, and you should have everything you need :) Be sure to read the section on how to calculate permissions.
  • USER_ID does not exist:
    • Check for the 'publickey' cookie. If it doesn't exist, query the database for the Guest group permissions (or just hardcode these permissions in your app), and you're all set. If the cookie exists, continue:
    • Verify that the cookie is, at minimum, 33 characters. Why? Because if you remember from above, we concatenated the user ID number with the 32-character md5 hash, so a valid cookie should be greater than or equal to 33 characters. If it's less, go to the last step and treat it like this cookie didn't exist. Otherwise, continue.
    • Split the last 32 characters off of the cookie. Now you have the actual public key you generated before, as well as the user ID. Query your autologin table for a row matching this User ID and publickey. If no such row is found, go back and do what you'd do if the cookie didn't even exist. If a row WAS found, continue:
    • Run the exact same function you used to generate the private key back when you created it the first time. Compare the result with the private key you got from the database. If they don't match, you know what to do ... go back and treat the current user as a guest. If they DO match, continue:
    • At this point, treat the user as though they just submitted a correct username and password. Set the USER_ID session variable, set the useragent session variable (and any others that you might be using to make sure there's no session-thievery going on), update the last_login_ip and last_login_date in the user table, update the last_used_on and last_used_ip on the autologin row that you found, etc.
    • Convert the user's IP address to an integer. Run a query against the forced_group_ips table to select a row in which ip_low is less than or equal to the current IP, and ip_high is greater than or equal to the current IP. Join the groups table on the group_id so that you get the group's permissions. If you get a result, use this group's permissions instead of the ones for the user's group and the user's overrides. For more information, see "The Permissions System" below.
    • Load the requested page as usual :). The user is now logged in. Be sure to read through the section on how to calculate permissions!

To ban/inhibit an IP or IP range:
  • Decide on a group to force on a user matching the IP(s). Usually this is either a "Banned" group (with few or no permissions), or a group that at least prevents some sort of abuse-- a "read-only" group, a group that has no search permissions, etc.
  • Convert the IP address you want to ban into an integer with ip2long(). If you want to ban a range, convert the lowest and highest IP addresses to integers.
  • Insert a new row into the forced_group_ips table. Use the group ID you selected, and set ip_low to the lower-boundary IP in the range, and ip_high to the upper boundary. If you're banning a single IP address, set the low and high to that same address.
  • Now you can check to see if an IP is banned by converting it to an integer and running a query to find any rows in the table with ip_low <= the IP in question, AND ip_high >= the IP in question. No need to do separate searches to see if it falls within a range or anything like that. If you get a result from this query, the IP address is in the table.

When someone forgets their username or password:
  • Ask for their E-mail address and E-mail address only. Keep asking until you get an E-mail that matches one of them in the database. When you get a match, retrieve that user's information.
  • Construct a hash with any information you need to remain exactly the same between the time the user requests their username/password, and the time they click the Password Reset link. I recommend concatenating the user ID, username, E-mail address, and salt, then md5'ing that.
  • Send an E-mail to the address, giving the username (no need to protect that) and a link to a page that allows the user to set a new password. Include the user's ID as well as the hash as part of this URL, as a GET variables or otherwise.
  • When the user loads the password reset page and attempts to submit a new password:
  • Load the information for the user ID in the URL.
  • Reconstruct the above hash, verify it matches the hash in the URL. If not, error out. If so, continue:
  • Generate a new salt. This will prevent any computers set to auto-login from.. well, auto-logging-in. When you're changing the password because it's been forgotten, this is always a good idea.
  • Generate a new salted passhash (like you did during registration), and update the row in the users table with this and the new salt.
  • Your call on whether to log the user in at this point, or redirect them to a login page on which to use their new password.

To allow a user to cancel any autologins on other computers:
  • Just ask for the user's password. When you get it, verify that it's the real, working password.
  • Generate a new salt.
  • Generate a new passhash using the new salt.
  • Update the user's row in the user table with the new salt and passhash.


The Permissions System
How it works
Each member of your site is assigned to a "group", and each group has a set of permissions assigned to it. 99% of the time, that's all you'll need. But there are those few rare cases where you need to give special permissions to just one user, or take one away. It's a waste to create a whole new group just for one person every time this scenario comes up, so this permissions system allows you to assign "overrides" to specific users.

Each user has a perm_override_remove and perm_override_add column, where you specify which permissions you want to either take away or add to the permissions from this user's group. Now, each time a user loads a page, we get their group permissions, removed permissions, and added permissions, and mash them all together to find that specific user's permission set.

How permissions are stored
In this system, each permission field is a 64-bit number. For the uninitiated, a 'bit' is a 1 or a 0 -- on or off. They can be combined to make bigger numbers. 0100, for example, is four. 0101 is five. If you're extremely interested in how binary works, Google has much to teach :). For now, just know that you have 64 on/off switches for your permissions -- and if you need more, you can use a bigger bigint ;-).

So what you need to do is come up with a list of simple on/off permissions. Be very small and specific with these-- so rather than IS_MODERATOR, for example, a forum might use DELETE_OWN_POSTS, DELETE_OTHERS_POSTS, POST_NEW_THREADS, POST_NEW_REPLIES, READ_THREADS, etc. Once you have that list, write a very simple PHP class that lists each permission as a constant, equal to a single bit (1, 2, 4, 8, 16, 32, 64, 128, 256, and keep multiplying by 2 from there). Here's a very quick example:

<?php

class UserPermissions {

	const READ_POSTS = 1;
	const POST_NEW_THREADS = 2;
	const POST_NEW_REPLIES = 4;
	const EDIT_OWN_POSTS = 8;
	const EDIT_OTHERS_POSTS = 16;
	const DELETE_OWN_POSTS = 32;
	const DELETE_OTHERS_POSTS = 64;
	const MOVE_THREADS = 128;
	const SPLIT_THREADS = 256;
	const MERGE_THREADS = 512;
	const BAN_USERS = 1024;
	const WARN_USERS = 2048;
	const ACCESS_ADMIN_PANEL = 4096;
	// And so on and so on
	
	protected $perms;
	
	function __construct($permissions) {
		$this->perms = $permissions;
	}
	
	function hasPermission($perm) {
		return ($this->perms & $perm) === $perm;
	}
}

?>

There are three main elements, here. You have the list of constants, which I explained above. Keep multiplying the value by two for each new constant. The significance of those numbers is that they can all be represented with a single "on" bit. 1 is 1. 2 is 10. 4 is 100. 8 is 1000. 16 is 10000. 32 is 100000 -- and so on. This just tells our system which bit of that huge 64-bit number belongs to which permission.

Followed by the constants is the constructor. This function just lets us say $perms = new UserPermissions($permissionFromDB); to get a new UserPermissions object. And the whole reason it helps to have a new object like that is because of...

The next function, hasPermissions, checks the stored permission set against one of our constants. So to see if a user can edit his own post, it's just $perms->hasPermission(UserPermissions::EDIT_OWN_POST); -- and you'll get back true or false. For the record, that function uses a "bitwise AND" to figure that out. It will help if you take a minute to google and learn what that is :)

How to calculate permissions overrides
So you have your fancy UserPermissions class above, and you can plug group permissions into that and it'll work. But now you have to make the overrides assigned to each individual user work.

In these examples, I'm going to take the permissions down to a 4-bit binary number like this: b0110. In fact, let's say our group's permissions are b0110 (which is 6). If we want to give that last permission (whatever it might be) to a specific user, we'd set that user's perm_override_add to b0001 (which is 1). Then the system needs to know to add any "on" bits in that to the group permissions we already have, to make it b0111.

But let's say, for the same user, we want to take away that first 1. We'd set their perm_override_remove to b0100. Then the system should know to take any "on" bits from that, and turn them OFF in the group permissions. Still following me? So our group permissions: b0111 minus the perm_override_remove of b0100 equals b0011.

But you'll run into a ton of issues if you try to use simple addition and subtraction like that. Adding b0001 and b0010 will result in b0011, and that's great. But what if you have a group permission of b0010 and you try to add the same bit in your override -- so another b0010? You'd end up with b0100, which isn't right at all! So instead of minus and plus, we use bitwise logic -- OR, AND, and XOR.

Again, I defer to google if you want to learn what each of those things are -- and you should ;-). But just so you don't have to sit down and rip out your hair trying to figure out the combination of AND and OR and all that junk to use, here's a handy dandy instruction sheet:

Added Permissions = Group OR perm_override_add
Permissions to Remove = Added Permissions AND perm_override_remove
Final User Permissions = Permissions to Remove XOR Added Permissions


Now you just need to build that logic into the constructor for your UserPermissions class :D

How to set a permission
You'll want to add another function to your UserPermissions class-- this one, setPermission, should take an integer (a permissions constant) and add it to the permissions number. But remember what we covered above! You can't just "add" it, because if you try to "add" (as in plus sign) a permission that's already on, you're going to be screwing up the permissions block. Instead, you want to set $perms equal to $perms OR the permission. That way, nothing changes if the permission is already set, and it's turned on if not :). Note that if you're using my above code, I have $perms "protected" so that it can't be edited directly. You'll want to add a function called getPermissions to get that number back so you can insert it into the database when necessary.


Extend it!
This system can do so much more. Building a forum? You'll want to have permissions per forum -- you can build another set of permissions overrides into each specific forum. Want people to be able to belong to multiple groups? Throw in another database table that links up user_ids to group_ids, then you can select them all and OR together the permissions. Need more permissions than just 64? Use an unsigned INT(10) for the permission fields, or higher!

This system will work for most types of sites, but the same basic idea can be used no matter how you need to customize it.


Enjoy!
And good luck :)


#9 Just starting PHP?

Posted by Kyek on 28 February 2010 - 09:33 AM

What's all this now?
PHP stands for -- oddly enough -- "PHP: Hypertext Preprocessor". It's a scripting language that you can toss right in with your HTML, and your web server will run the code before it ever sends the page to the user. What's that mean? Well, it means you can do stuff like this in your webpages:

Today's date is: <?php date("m/d/y"); ?>

And what the server will send back is something like this:

Today's date is: 02/28/10

Of course, that's about as basic as it gets. PHP can be used to write incredibly complex interactive websites. This entire forum was written in PHP!

Cool! So I can just put a bunch of those <?php ?> things in my page and have a forum!??!
Not quite :). Mixing PHP in with your HTML is possible, but not something advanced coders to. In order to make complex web apps, you'll usually have a bunch of files that are nothing but PHP code. Don't expect to be able to make something really advanced right away -- PHP is easy to learn, but much harder to master. Start easy and work your way up :)

So what about databases? I heard I need one for PHP.
PHP code can run without a database, but most useful webapps will need one. It's just a place to store and retrieve information really quickly, and the most popular one to use with PHP is called MySQL. We have a forum specifically for this stuff, so go check out "Just starting MySQL?" for tips on how to get going :).

How do I start?
You have a few options here. The most failproof method is to work off of a real web server that has PHP and MySQL already installed. There are some good free ones out there if you don't expect much traffic, but I've heard good things about x10hosting.

There are a million ways to go if you want to develop on your local machine, but here's what I recommend:
Windows: Grab a copy of WAMP -- it's Apache (webserver), MySQL, and PHP, all in one ridiculously easy-to-use pre-configured bundle! It doesn't get much simpler than this. The latest version comes with PHP 5.3, which contains some killer features that most other servers don't even have yet.

Mac: You have a few options! The quickest, easiest, and most user-friendly is MAMP -- Apache (webserver), MySQL, and PHP, pre-configured and self-contained for Mac.

The only drawback is that MAMP currently doesn't support PHP 5.3, despite public outcry. A similar package that DOES is XAMPP. It works just like MAMP, but isn't quite as clean or user-friendly.

If you're serious about this stuff, though, forget the add-on apps. You're running a UNIX-based machine, and you have a webserver with PHP built right into it! The easiest thing to do is just turn it on :). This guide at PHP.net can show you how. If you decide you want MySQL later, check out how to get it in the "Just starting MySQL?" article.

The guides!
While I'd love to write my own set of PHP for Beginners guides, I haven't done that yet. Instead, check out these great guides, and stop back here to search the forum and ask questions when you run into trouble! We're newbie-friendly here :)

Tizag's PHP Tutorial
W3Schools' PHP Tutorial


#74591 Do you feel the tense?

Posted by _Sam on 02 March 2011 - 10:52 AM

There seems to be a lot of hating lately in the Forum. Have you guys forgotten about the "Mean Coders Suck" Passage?

Please just try to reply in a polite way it will look way better, if you can't do it just don't post at all.


#3140 An introduction to MVC architecture

Posted by Kyek on 10 March 2010 - 09:13 PM

An Introduction to MVC Architecture
A Guide by Kyek
Written March 10, 2010



Most... Valuable... Cake?
MVC stands for Model, View, Controller. For years, it's been the standard for how to split the code of a web application, and for good reason -- once you try it, you'll see that there's just no better way to organize the pieces of your project. It makes your work faster, more maintainable, and much easier to reuse in other places.


Understanding the pieces
The easiest way to understand MVC is to imagine someone typing your website's URL into their browser, and that page request hitting your web application. From there, here's how the pieces of the MVC puzzle come together:

Controller: The "Controller" takes the request and reads it, figuring out what the user wants to see. Looking at the URL and the data that was passed along, it appears that the user is trying to look at page two of your blog. Great, the controller's main job is now done! It's identified what needs to be delivered. Now it has two other tasks to do: It has to create the Model, and pass it to the correct View. What are they, you ask? Read on!

Model: The Model is a fancy name for the object containing all the necessary data for the page. In our example, the user has requested page two of your blog. So the controller tells the model "Load Blog page 2 from the database!". The Model's job is to do ANY data-related related task, from database communication to file access. Then it stores that data inside of itself -- all the data needed to construct the page.

View: Now that the controller has identified what's being asked for AND it has the model ready to go, it selects a View. The 'views' are the different display templates for your site. You could have a view for your site's landing page, a view for a blog post's comment section, and a view for pages of blog posts. The view has, at minimum, all the HTML that's needed to display a page, and just enough code inside of it to use the data that's being provided in the model. So in our example, the Controller will send the Model that it has ready to the Pages_Of_Blog_Posts view. The view looks like a normal HTML page, but there's a little bit of code to dig into the model and show that it's page "2" we're looking at. There's a little bit of code to loop through each blog post that's been returned, dumping in the name and a snippet of its text. There's a little bit of code to see that we're on page 2 and that there's a total of 5 pages, so it knows to put in both the "Previous Page" and "Next Page" links.


Wow Kyek, that almost seems too simple!
Well, it's a little more complex than that. Let's take the controller, for example. Modern web applications have a set of controllers -- one to handle each kind of request. Requests for blog pages get sent to the Blog Page controller. Requests for the Blog Comments get sent to the Blog Comments controller. Requests to search the blog goes to the Blog Search controller, and so on. So how do the requests GET to each of those controllers? Well, you need some sort of dispatcher that reads the request and knows which controller it should go to. That, itself, is also part of the "Controller" side of your web application.

The Model is a BIG one that has a few different interpretations. Originally, the word "model" referred to only the data that gets passed to the View, and in some contexts, that's still what people mean when they say "model". But for modern web applications, the Model part of the MVC structure isn't just the data, it's also allll the logic involved in retreiving, saving, writing, or manipulating that data. "Fat Model, Skinny Controller" is a common phrase used to describe where the heavy lifting should happen in your webapp. Controllers should only figure out what type of model needs to be called on -- the model should do every last bit of the data work. So if you're using a database wrapper to support more than one type of database, for example, that's still considered part of your Model.

The View seems like the simplest part, right? Just HTML with some code thrown in to dump out the model's data. But it's often anything but simple. Think about a full, polished website. It probably has a header that appears on every page, right? And maybe a sidebar that's on more than one page, and other parts that are shared between different pages. The first rule of programming is to never have duplicate code, so for the view, you generally want things like your header and your sidebar and your footer all in their own separate files, which can be dynamically included into your main content pages. So the view for our example (loading page two of your blog) may look something like this (just an example here, not a real language):
INCLUDE PAGE_HEADER
INCLUDE LEFT_SIDEBAR
<< Code to display blog posts here >>
INCLUDE GOOGLE_ANALYTICS
INCLUDE FOOTER

And sometimes the people making these views aren't coders, so instead of letting them work with very sensitive PHP or Java or Python, you'll want a mini scripting language for them to use that you'll parse with the controller so that you can be sure these people can't break your site.


Sound like it's too much? It's not, if you start easy!
If you've never built an MVC-structured webapp before, don't be scared off by the complications! You can build very simple and tiny apps with MVC architecture without getting into all that complicated-ness yet. Make a simple controller that can tell what's being requested, make a simple model that only supports one way of storing data, and make views that are simple HTML files that might reuse code here and there. The beauty of starting with that MVC structure in mind is that, at any time, you can make any of those parts more complex and more featureful without ever needing to touch any of the other parts!

So after your app is built, then spend the time to make your Model faster at getting and storing data, and your controllers and views won't care. Then maybe you'll decide to support multiple themes in your view, and your models and controllers won't care about that. You can do upgrades however, wherever, and whenever you like without risking breaking anything. So start easy, then work on making things more advanced!


When should I try this?
If you have a basic understanding of any server-side web language, there's no reason to wait! With how very simply you can do this, it's a great way to start learning. So from here on out, don't build any more webapps without thinking about how to split it up into those three pieces :)


Go forth and code better!


#67863 VPS Comparison

Posted by JackHarley on 27 January 2011 - 04:53 PM

VPS Comparison
Written by AwesomezGuy, January 27th-- Kyek style


Not sure about you, but I am sick to death of all the hundreds of "is this VPS good?" threads.
I'm going to make this a central place for VPS services, if I miss one you know, reply with it and I'll add it.
I'll compare the prices by a base unmanaged 512mb guaranteed RAM VPS.
Since i believe IPv6 support to be pretty important at this stage

Let's go! (the following are in the order I thought of them, which holds no significance)

Slicehost
512mb RAM VPS - $38
IPv6? No

Posted Image

My current host. Unreasonably expensive, but you get what you pay for. Plenty of CPU, not oversold, pretty much 0 downtime.
These guys are a total winner if you have the cash.

------------------------------------------------------

Club Uptimet
512mb RAM VPS - $5.95
IPv6? Yes

Posted Image

Just got one of these, so far so good. Fast and not oversold. Prices are very attractive. Especially loving the IPv6 awesome-ness

MACBOOKPRO:~ Jack$ ping6 ipv6.techndstuff.com
PING6(56=40+8+8 bytes) 2001:470:1f08:136b::2 --> 2607:f0d0:1101:39::92ae:86fe
16 bytes from 2607:f0d0:1101:39::92ae:86fe, icmp_seq=0 hlim=54 time=138.313 ms
16 bytes from 2607:f0d0:1101:39::92ae:86fe, icmp_seq=1 hlim=54 time=139.578 ms
16 bytes from 2607:f0d0:1101:39::92ae:86fe, icmp_seq=2 hlim=54 time=140.191 ms
16 bytes from 2607:f0d0:1101:39::92ae:86fe, icmp_seq=3 hlim=54 time=140.610 ms

------------------------------------------------------

Linode
512mb RAM VPS - $19.95
IPv6? No

Posted Image

Haven't personally tried these guys out, but I've heard good things. Quite expensive, but they look professional. If anyone has any experience with them, reply with a review and I'll put it here.

------------------------------------------------------

BuyVM.net
512mb RAM VPS - $5.95
IPv6? Yes

Posted Image

Don't know much about these guys, the fact that their prices are so low could mean that there's something going on. Any past customers, please reply with your experiences! The IPv6 support does look attractive though /imanipv6nerd

------------------------------------------------------

YardVPS
512mb RAM VPS - $5.95 on OpenVZ, $7.95 on XEN
IPv6? Yes

OpenVZ:
Posted Image

XEN:
Posted Image

Nice prices, nice IPv6, looks professional, no idea what they're like personally though. Again REVIEWS PLEASE.

------------------------------------------------------


#66682 Scripting in Python for a Java Application

Posted by Sephern on 19 January 2011 - 08:28 PM

I've written the majority of the framework for an IRC Bot in java. It does all the pretty standard things, like connecting to servers (it only connects to one channel), with a customizable nickname, owner, and various other options.

I'm on a computer science course, and in this year (the first) we've been taught Python. Therefore, everyone in my group of friends has experience with Python, with knowledge of other languages being sporadic at best (myself knowing PHP, C# and Java, another friend knowing Java and a few others having sixth-form education in things like VB). Therefore, I'd like to distribute the IRC bot as java bytecode (as opposed to source). The issue with this, is obviously that people can't modify the bot very easily. Python, as a scripting language, is ideal for this.

What I'd like to do is have an XML file, which specifies commands. This would be loaded into memory at the start of the program, and when input is found which matches an entry in the XML file, it'd call the related python script. The python script should run alongside the java application (using threads, separate processes, or whatever else. I don't want a user-written module which communicates with a database, for example, interfering with the operation of the java part). It should also be able to call a variety of functions within the java application (a kick function, etc). I don't really want to write a python module to distribute with the java part, but obviously if this is necessary I will do.

What's the best way of going about this?


#7 Just starting Javascript?

Posted by Kyek on 28 February 2010 - 08:16 AM

What's all this now?
Javascript (known "officially" as ECMAScript... but that name sucks) is a scripting language used to write little snippets of code that web browsers run when they load your webpage. It does animations, changes elements on the webpage, can listen for mouse clicks and key presses and perform actions when they happen, and a huge number of other things.

What's this AJAX stuff I keep hearing about?
AJAX stands for Asynchronous Javascript and XML. When Javascript requests brand new data from your website's server and displays it on the webpage without your browser actually needing to load a new page, that's AJAX. Originally it was meant for retrieving XML data (hence the 'X' in AJAX), but now people use this term when you're requesting any kind of data from the server with Javascript. The most popular data format for this is called JSON, now.

JSON? I have a friend named JaSON.. does that count?
JSON stands for JavaScript Object Notation. It's a way of formatting data that's super easy for Javascript to read through, and has way less fluff than XML. It got really popular because it's a smaller amount of data that webservers have to deal with, and it's faster for your website's users because Javascript itself reads it much faster than it can read XML. But if you're just starting out, don't worry about this yet -- you'll run into it later :)

I've heard of something called a jQuery.. what's that?
jQuery is a "Javascript Framework". It's just a Javascript file to include in your websites to give you access to some really powerful Javascript functions. It makes AJAX super easy to do, and lets you pick out certain elements of your page just like you would with CSS. I highly recommend it once you learn the basics!

So, Java ... That's just short for Javascript, right?
Nope! Java and Javascript are two COMPLETELY different languages with two entirely different uses. The syntax is similar, but that's about it. Java is used to make everything from stand-alone desktop applications to server-side dynamic websites like PHP, and is about as advanced as it gets when it comes to web languages. So don't get these two mixed up :)

How do I start!?
You can test Javascript on your own computer just by including it in an HTML page, and opening that page through your favorite web browser's "File" menu. So grab a guide and get started! If you run into problems, come back here, search around, and ask questions :)

Guides!
When it comes to web guides, there's no beating Tizag.com. But W3Schools.com has some good primers as well -- so take a look at both, and go with what you like!

Tizag's Javascript Tutorial
W3Schools' Javascript Tutorial
A boatload of jQuery tutorials by awesome people


#53194 Getting Started with Free EC2

Posted by Kyek on 01 December 2010 - 11:22 PM

Getting Started with Free EC2
Written by Kyek
December 1, 2010


What it is and why it's cool
You know Amazon.com? That cool store that sells everything you've ever heard of? Not a lot of people know that they also have a variety of really incredible services for web programmers. They leverage their incredible global network and data centers to provide things like unlimited online file storage, a slick content distribution network, servers with a wide range of resources that you can request and have ready to go literally two minutes later -- and that's only a little of what they offer.

This guide is about getting you set up with EC2, the "Elastic Compute Cloud" that allows you to tell Amazon you want a server, where in the world you want it, how powerful you want it to be, and what operating system you want on it -- and two minutes later it's yours.


What it's gonna cost you
Recently, Amazon launched a free usage tier. You'll need an Amazon Web Services account (we'll get to that later) with a real, honest-to-goodness credit or debit card attached to it. Beyond that, though, running the least powerful VPS Amazon offers -- nothing shabby at 613MB RAM and up to 2.4Ghz of CPU -- is FREE for your entire first year of membership. After that first year, Amazon charges you hourly for the resources you use. It's a killer. We're talking $0.007 USD per hour here -- and no, that's not an extra zero. Literally a fraction of a cent ;-). So, first year free, after that, you're looking at around $5.20 per month. There are ways for that to rise, but we'll get into it later.


Exactly what you get
EC2 supports almost all *nix and linux variants as well as most versions of Windows Server (though those cost a bit extra and aren't part of the free-for-a-year package). But here's the deal: Once you choose your operating system, that's it. You get a server with that OS, and the credentials you need to connect to it with SSH -- a command-line tool. From there, you'll generally need to set the whole server up yourself. It does take some know-how and it will take some time to get things right if you're not used to installing and configuring software without ever seeing a GUI or clicking your mouse. Good thing you've found the forum section where we help you with that sort of thing ;-).


A quick note about this (and other) EC2 guides
Once upon a time, the only way to start and stop EC2 was with a handful of Java-based command-line tools -- and these tools took a great deal of command-line bashing and local system configuration to get done. Almost every guide you find for EC2 today will walk you through this advanced and sometimes painful process. If you get hooked on EC2, it's worth doing. But not too long ago, Amazon released web-based tools to start/stop servers, handle the access keys, and all of that fun stuff. This guide takes that route, as it's the fastest way to get started.


Ready to dive in? Let's go!
Step 1: Get an Amazon AWS account
As I mentioned earlier, you're going to need some sort of legit credit or debit card to do this, even though it's free. Amazon just needs a way to charge you should you decide to spin up a supercomputer cluster and attempt to decrypt government secrets.

Once you have a card handy, go to http://aws.amazon.com and click this handy button:

Posted Image


That will walk you through the process, and you'll have your very own Amazon Web Services account in no time at all. Once that's all set up, you still need to sign up for the EC2 service with that account. No worries, it's a near-instant process. Just go to this URL: http://aws.amazon.com/ec2 and click this other handy button:

Posted Image


You'll have EC2 access in no time!


Step 2: Sign into the AWS Management Console
Isn't Amazon so convenient, there's a handy link to your Management console at the top of every AWS page! On that same EC2 page you were on, look up to the top and you'll see this:

Posted Image


Click away, adventurer! Past the login page, just hit this tab:

Posted Image


...And you'll be in your fancy new playground! You should see something similar to this:

Posted Image



Step 3: Creating your keypair
If you're not familiar with SSH ("Secure SHell"), it's a method of logging into a *nix server's command line -- but there's more than one way to authenticate yourself. Usually you use a password, but that's relatively insecure. The most secure thing to do is have a big 'ole randomly generated key that proves you are who you say you are. We're going to generate that key.

So click the "Key Pairs" link here:

Posted Image


And then stab your mouse pointer at this little beauty:

Posted Image


Now you can name your new keypair:

Posted Image


As soon as you click 'Create', your key file will download. THIS FILE IS IMPORTANT. IT IS THE ONLY ONE OF ITS KIND. YOU CANNOT RE-DOWNLOAD THIS FILE. SAVE IT, AND BACK IT UP SOMEWHERE SAFE AND SECURE. ANYONE WHO GETS THEIR HANDS ON THIS FILE CAN ACCESS ANY OF YOUR SERVERS AND MAY BE ABLE TO RACK SERIOUS CHARGES UP ON YOUR AMAZON ACCOUNT.

Sorry for the crazy capslock, but it's all true. Save this file somewhere safe, and back it up somewhere that's not on your computer. Do that now. I'm serious. Once that's done, congratulations :) You have a keypair!


Step 4: Configuring the built-in firewall
Amazon has a really slick firewall system that allows you to define what ports will be accessible on your server instances. By default, NO ports are open, meaning that you can't even SSH in. This is bad! So we'll have to open a few ports -- we'll start with 22 (SSH), 80 (HTTP), and 3389 (RDP, just in case you try a windows instance later) -- before we can do anything else.

So click the "Security Groups" link here:

Posted Image


And click the group named "Default":

Posted Image


And now you should have something that looks a bit like this in your bottom frame:

Posted Image


Except you won't have those last three lines yet. You need to add those! Simply use the form fields under the table to add each of those sections like I did, and you'll be all set. Make sure it's exact!


Step 5: Starting your very first EC2 server instance
LET'S PAUSE FOR SOME KNOWLEDGE HERE: You can get EC2 instances in every shape and size. Instances with huge amounts of RAM, instances with tons of CPU power, and everything in between. What you get for free is the "Micro" size, which has one major difference from every other size there is: it has NO HARD DRIVE. In place of the hard drive, when you start up a micro instance, Amazon will give you an EBS (Elastic Block Storage) drive. Think of this as a USB drive that can be plugged into any EC2 instance, and in the case of Micro EC2s, you'll be booting off of it. This will be created and plugged in automatically every time you start a micro instance, and depending on the server configuration, may or may not be deleted automatically if/when you shut the server down. I'm telling you all this because you need to pick a disk image for your server that was made specifically to boot from an EBS drive, and you need to make sure to back up any important data on your server to be certain you don't lose anything. Got it? Let's move on.

Click the "Instances" link here:

Posted Image


And get your mouse all cozy with this seductive button:

Posted Image


And you'll be staring at this scary screen:

Posted Image


The first thing you want to do is click the "Community AMIs" tab at the top, because as in love with Amazon as I am, their Quick Start disk images are not the greatest for starting quickly. Another bit of knowledge: An 'AMI' is basically just a disk image that you can write to an EC2 server and boot up from. So you don't necessarily get a 'clean install' of whatever OS you pick, you get a disk drive snapshot from whomever made the AMI. Generally they're pretty clean and bare, but some AMIs come preconfigured. Since they're almost never preconfigured how you NEED it to be, though, it's better to start clean.

Once the Community AMIs tab has loaded (this can take a minute), tell it you only want to see EBS-boot images:

Posted Image


...then you can filter it further by keywords you want to see in the AMI name. My personal preference is Debian, 64-bit. So I type in 'debian' and, lo and behold, there are only two! Look very carefully, and only the top one is 64-bit:

Posted Image


Note that AMIs are region-specific, so you may not get exactly the same results as me. If not, no sweat! Pick anything that looks good to you-- be adventurous! This is totally free (unless you select Windows) and if you hate what you picked, you can shut it down and start up a new one with absolutely no repercussions. For now, though, I'm going to select the top AMI in my results and move on.

Now I have this window:

Posted Image


And UH-OH! 'Large' size is selected! That's not free! The first thing you should do is go into 'Instance Type' and select Micro:

Posted Image


Then continue. If you wanted, you could have specified which datacenter you want to be in -- but that's unlikely to matter for you just yet.

When you have this screen, just hit 'Continue' as this is advanced stuff that's dangerous to play with your first time through:

Posted Image


And we reach yet another screen that you don't need at this point, so hit 'Continue' again:

Posted Image


On the next screen, the keypair that you created earlier should already be selected for you. How convenient! If that's the case, just mash that 'Continue' button one more time:

Posted Image


Now we get to the firewall portion of the event. Since we've already modified the 'default' security group to fit our needs, we shouldn't need to make any changes here. Get cozy with that continue button yet again!

Posted Image


FINALLY we're done setting this sucker up! Check out the Review screen:

Posted Image


MAKE SURE THAT SAYS 'MICRO' and if everything looks good, hit 'Launch' :)

Posted Image


Once you click Launch, just hit the "Close" button to get out of the wizard.


Step 5: Logging into your new server
Believe it or not, a prerequisite to getting into your server is that your server actually be running. You should still be in the "My Instances" area of the management console, so just mash the refresh button:

Posted Image


...until you see your instance light up with a green dot that says 'running':

Posted Image


And now you're good to go! Just click that instance and your bottom frame should show you something like this:

Posted Image


All we really care about there right now is the 'Public DNS' field. In my example image, it's this:
ec2-174-129-48-123.compute-1.amazonaws.com

That baby is the hostname for your brand new server. If you're using any operating system other than Windows, you're practically done. Open up your favorite Terminal application (Mac users can find this at /Applications/Utilities/Terminal, or download iTerm for a better one), move to the folder that's holding that super-important key file we downloaded earlier with a command like this:
cd ~/Documents
(edit that command with the path to wherever you saved that) and you can log in as the root user (The administrator) using that key with this command:
ssh -i yourname_keypair.pem root@ec2-174-129-48-123.compute-1.amazonaws.com
Just replace "yourname_keypair.pem" with the name of your key file, and fill in your server's hostname in place of my example. Hit 'enter', and say yes when it asks you if you want to trust the computer you're connecting to.

If all goes well, you should be looking at something like this!

Posted Image


Ta-da! That's the command line for your server!


Step 5.5: Uh-oh, using Windows?
It's not problem, Windows can do this too! You'll need two programs: putty.exe and puttygen.exe, both available on this page: http://www.chiark.gr...y/download.html

At the time of this writing, I don't have easy access to a Windows computer, so I'm without screenshots and step-by-step instruction here. But you should be able to figure this out in just a few minutes: What you need to do is fire up puttygen.exe, load in your keypair file, and generate another key file that's in a specific format Putty can use.

Then, open putty, plug in your connection details, and dig through the menu until you find the Authentication area where you can tell it to use the new key file you generated. Hit connect, give it 'root' for the username, and you're in! You should be looking at a command prompt not unlike the screenshot I showed above.


Step 6: Configure!
Now that you have the server, the sky's the limit :) Use the resources here on wdR and on the wider Google to figure out what you need to install, how to install it, and how to set it up. Just remember, if you decide to throw in the towel, BE SURE TO STOP YOUR SERVER INSTANCE IN THE MANAGEMENT CONSOLE! Or next year, you'll see some very strange charges on your credit card ;-). Also be sure to stop the instance if you want to start up a new one to try a different AMI. You only get one instance at a time for free!

[[ This tutorial is a work in progress, but should be near-complete. Be kind, I haven't even proofread it yet! :) --Kyek ]]


#43460 The Backwards PHP 5.3+ Guide

Posted by Kyek on 10 October 2010 - 06:41 AM

The Backwards PHP 5.3+ Guide
A tutorial by Kyek, started October 8, 2010


A quick introduction
Every PHP guide I've seen on the internet or in books teaches PHP in the wrong direction. They tell you the commands, the functions, the loops, the arrays, and only at the very very end do they teach you how to use classes and objects, and almost none of them touch patterns like MVC. So by the time you get to that point, you're already so used to writing awful code that organizing it in logical pieces just seems like a hassle. Impossible, even, if you've already gotten into a project by that point. This guide puts an end to that, and teaches PHP backwards -- the right way. You'll learn classes and object first, then the commands, functions, loops, and arrays to put inside of them -- using the MVC pattern the whole time. If this sounds like a whole lot of gibberish, keep reading ;-)


Chapter 0: Before You Begin
Know What PHP Is!
A lot of people think they need to learn PHP without actually knowing what it is. So here goes: PHP is a scripting language that webservers run to produce a webpage before sending that webpage to your users' web browsers. PHP is *not* code that executes inside of people's web browsers -- to do that, you need Javascript. PHP can only be read and run by your server itself, and the browser simply shows you the result. A simple example of this is that PHP allows you to print the current date out on a webpage. Then, if you right-click the page in your browser and hit "View Source", you'll see the date right in there, as though you typed it directly into the HTML file and uploaded it yourself.

Know If You're Ready To Learn It
PHP is a great language and very easy for programming beginners to pick up. But before you can use it effectively for websites, you need to have a good understanding of HTML and CSS -- and it really doesn't hurt to have decent knowledge of Javascript, either. If you're weak with your HTML code, though, you'll want to bone up on that before continuing.

Setting Up Your Environment
Before beginning this guide, you need to have a PHP development environment set up on your own personal computer. Some folder where you can put PHP files, load up a URL that starts with http://localhost in your browser, and see the result of your code. Later, you'll also need a MySQL server running on your computer, preferably with a copy of PHPMyAdmin preinstalled. If you're on Windows, the fastest and easiest way to get up and running is by downloading a free copy of WAMP. Mac users can use MAMP. Setting up your environment some other way? Just make sure your version of PHP is 5.3 or higher!


Chapter 1: Hello, World! (or, intro to classes and functions)
Writing the PHP
If you're a veteran programmer, you know that writing an application that prints "Hello, World!" is generally the first program you write when learning a new language. If you're not a veteran programmer, I have a fun fact to share with you! Writing an application that prints "Hello, World!" is generally the first program you write when learning a new language!

To do this, we're going to use two PHP files. Seems like overkill for printing one whole line of text, I know-- but there's method to the madness! So find the root folder of your development environment -- the "webroot" -- and we'll start from there. If you're using WAMP or MAMP, consult the documentation if you can't find what folder that is. Within the webroot, create the path 'lib/mysite/controllers' to drop HelloController.php into, and put index.php in the webroot itself.

lib/mysite/controllers/HelloController.php
<?php
namespace mysite\controllers;

class HelloController {

    public function sayHello() {
        echo "Hello, World!";
    }

}

?>

index.php
<?php
require_once(__DIR__ . "/lib/mysite/controllers/HelloController.php");

use mysite\controllers\HelloController;

$site = new HelloController();
$site->sayHello();
?>
Load up index.php in your browser, and ta-da! It says "Hello, World!"

Understanding the PHP
I'll be honest: If this were any other PHP book, it would have told you to make one PHP file with the line
<?php echo "Hello, World!"; ?>
in it and call it a day. But writing a site like that is bad form. The reason why that's bad form, as well as many of the technical things that make this more advanced code, are all things you'll learn slowly over the course of this guide. For now, let's go through this code to get just a basic understanding of what each line does, and we'll revisit any interesting pieces later.

lib/mysite/controllers/HelloController.php
Most good webapps are designed with what's called an "MVC", or "Model, View, Controller" pattern. MVC is a guideline for how to separate different kinds of functionality so that your code stays clean and easy to maintain. We'll work more with this as we go, but for now, know that the "Controller" part of MVC is the part that has the "Business Logic" -- a fancy way of saying that the Controller is what loads a page, knowing all the work that needs to be done to get to that point. For now, we're starting nice and easy. So let's get into the file itself!

<?php
In order to tell your server what's PHP code and what's not, we use <?php ?> tags. Anything inside of those tags will tell the server "I'm code! Run me!", while anything outside will be sent to the web browser like any other text or HTML. Soon, we'll try mixing HTML and PHP together using this concept; for now, though, all of our PHP files will start with <?php and end with ?>. Now you understand the first and last lines of HelloController.php ;-)

So let's hit the real first line, now:
namespace mysite\controllers;
This tells PHP that everything in this file will belong to the "mysite\controllers" namespace. Namespaces are simple -- they're just a way to make sure that the names of classes, functions, and variables in your code don't get inadvertently changed or overwritten by someone else's code that uses similar names. As long as your namespace is unique, you're safe. Namespaces can be anything, but they're most useful when you make the namespace the same as the path to the file. So, ignoring the 'lib' folder because we want our namespace to start with the app name, HelloController is in mysite/controllers. We flip the forward-slash to a backslash (the official separator for namespaces), and we have it :)

class HelloController {
Almost all the code in your webapp will exist inside of a "class", or a big group of code all meant to perform a similar task. That opening-brace (the '{') means that any code after it and before the closing brace ('}') will belong to the HelloController class. The name of that class is arbitrary -- we could call it "class Sharkbait_HuHaHa" and it would still work just fine.

public function sayHello() {
    echo "Hello, World!";
}
Functions are blocks of code that you can call over and over and over again. This particular function is named "sayHello", because all it does is print out "Hello, World!" to the browser. You might have already assumed that the 'echo' command just prints out text-- and if you did, then you're learning pretty quickly :). So every time we tell the "sayHello" function to run, it'll print "Hello, World!". That 'public' keyword just tells the class that we're allowed to run this function from code that's outside of the class. If that doesn't make sense yet, it will in just a little bit.

index.php
<?php
Again here, the file starts with <?php and ends with ?> to let the webserver know that everything in the file is PHP code. With that, let's move on:

require_once(__DIR__ . "/lib/mysite/controllers/HelloController.php");
It would be cool if PHP could magically "find" all the class files that we want to use, but unfortunately it's not that smart quite yet. Until then, we can use a built-in function called require_once to load any PHP files that we might need to use. Since index.php is the file that we're actually loading with the browser, we use require_once to load in the contents of any other PHP file we'll need to show the page. All we have to give it is the path. We start with __DIR__, which is a keyword that PHP will automatically replace with the absolute path to the current file, index.php. This might be something like "C:\Users\Barney\TheOnlyThingOnMyComputerThatIsntPorn". After that we have a single '.', which, believe it or not, is actually a command in PHP. It's called a 'concatenator', and any two "strings" or lines of text that it falls between, it will join together. So we join together your current path with the path from here to the file we want, and ta-da, require_once has everything it needs!

use mysite\controllers\HelloController;
Remember how namespaces keep your code from conflicting with other similarly-named code you might be using? Here's why. For any class you've loaded in with require_once and now want access to use, you just tell PHP to 'use' it. You specify the class by typing its namespace, followed by the class name itself. Now, any time we use HelloController in our code, it will know that what we really want to use is mysite/controllers/HelloController :)

$site = new HelloController();
Classes are like blueprints. They have all the data you need to make something useful, but you need to build the object before you can start using it. So to make an object from our class, we need a few different things:
$site
in PHP, anything starting with $ is called a variable. A variable can contain numbers, text, or even Objects, a usable version of our blueprint class. All we do now that we have a variable is create a, *cough cough*, new HelloController object. Then we take that new HelloController object, and insert it into our variable, $site. This is pretty hard to understand until you use it, so let's use it!

$site->sayHello();
So we know that $site now has all of the HelloController class's code hiding inside of it, and we know that we need to run the "sayHello" function in order to print "Hello, World!" to the screen. Thankfully, this process is very easy :). First, we type $site since that has the function that we want to call. Then we type an arrow ("->") to tell PHP that we want to do something "inside" of $site. We know that sayHello is "inside" of $site, so we type that next. Ta-da, we just ran our sayHello function! With PHP, functions can be a little more complex and allow you to pass data back and forth between them. Normally you'd add more variables between those parentheses () to do that (just like we did with require_once!), but since we don't need to in this case, we just leave them empty.

Feeling Overwhelmed?
That's ok! Because I'll let you in on a little secret: what you just learned is the hardest part of PHP. I warned you that you'd be learning this language backwards ;-). All of our code from here on out will be written in classes and functions, so if you're not quite grasping what they are and why we use them now, just keep on reading and you'll settle into the idea when we use it more. Just feel safe in knowing that it all gets easier from here.


Chapter 2: More Pages! (or, intro to conditionals)
Less is More, but One is Everything.
Good webapps separate the logic for each page into different PHP files. Great webapps split each page into separate files, but load them all through index.php! Sound crazy? Let's do it, then you'll see how easy your life becomes.

A Controller with a View
We've gone over what the Controller is in the Model View Controller structure -- now, let's talk about the View. In our Hello World example above, we're simply echoing out some text. But for most pages, we're going to need a lot more than that! You don't want to echo out things like "<html>" and "<title>My Site</title>" because not only is that a lot of PHP code, it would get incredibly messy. The best thing to do is write a plain old html file, but add bits of PHP code to output any variables you need to display.

Telling Controllers Where to Find Views
If the controller is going to be responsible for loading these HTML pages, it has to know where to find them! So let's add a line to the very top of our index.php:

index.php
<?php
define("VIEW_PATH", __DIR__ . "/views");

require_once(__DIR__ . "/lib/mysite/controllers/HelloController.php");

use mysite\controllers\HelloController;

$site = new HelloController();
$site->sayHello();
?>

define() is another built-in PHP function. You use it to make a very specific kind of variable called a "Global Constant". Global constants are available from any file or class within your application, and can never ever be changed while the PHP script is executing. In this new line, we're making a new global constant called "VIEW_PATH", and using the same __DIR__ and concatenator syntax that we used in the require_once call to define a path to a "views" folder off of the webroot. Now, any controller that needs to load a view has a way to find the view files!

The Calculator Page
So, knowing about Views, we're going to create three files to make a page that can add two numbers together: A Controller, a View for the calculator form, and a View for the calculator results page. Here are the files -- and remember, these paths should come from the webroot!

views/calc_index.php
<!DOCTYPE html>
<html>
	<head>
		<title>My Site : Calculator</title>
	<head>
	<body>
		<form method="get" action="index.php/calc/results">
			<input type="text" name="num1" />
			<input type="text" name="num2" />
			<input type="submit" value="Add!" />
		</form>
	</body>
</html>

views/calc_results.php
<!DOCTYPE html>
<html>
	<head>
		<title>My Site : Calculator Results</title>
	<head>
	<body>
		The answer is <?php echo $answer; ?>!
	</body>
</html>

lib/mysite/controller/CalcController.php
<?php
namespace mysite\controllers;

class CalcController {

	public function index() {
		require(VIEW_PATH . "/calc_index.php");
	}

	public function results() {
		$answer = $_GET['num1'] + $_GET['num2'];
		require(VIEW_PATH . "/calc_results.php");
	}

}
?>

Picking out your URL scheme
Now we have a Controller that loads the Views and that's awesome -- but before we can actually load this page, we need a dispatcher! A Dispatcher (often called a "Router" as well) is a block of code responsible for looking at the URL you're loading, and passing your request off to the correct Controller. In our case, we'll be looking at the URL and deciding if we should load up HelloController or CalcController. So the first step is figuring out what URLs we're going to use!

We already know that we have to load index.php. So there's a couple options for how to add extra information to a URL without actually changing the file you're loading -- and since this is the backwards PHP guide, I'll start you with the best and most advanced solution: PATH_INFO! :) We're going to load your pages with these URLs:
index.php/hello
index.php/calc
index.php/calc/results (this is your calculator results page)

Believe it or not, adding a slash and then more info after your .php file will still load the PHP file. What's more, the PHP engine will even create a variable for you to read what extra info was included!

Adding your dispatcher to index.php
Here's the index.php with our Dispatcher AND the require_once line to bring in our CalcController. Once this is in, you can load the pages with the URLs I gave above. But keep reading, because this does you no good unless you know how it works ;-)

index.php
<?php
define("VIEW_PATH", __DIR__ . "/views");

require_once(__DIR__ . "/lib/mysite/controllers/HelloController.php");
require_once(__DIR__ . "/lib/mysite/controllers/CalcController.php");

use mysite\controllers\HelloController;
use mysite\controllers\CalcController;

// Request dispatcher
if ($_SERVER['PATH_INFO'] == "/hello") {
	$controller = new HelloController();
	$controller->sayHello();
}
else if ($_SERVER['PATH_INFO'] == "/calc") {
	$controller = new CalcController();
	$controller->index();
}
else if ($_SERVER['PATH_INFO'] == "/calc/results") {
	$controller = new CalcController();
	$controller->results();
}
else {
	echo "You must choose a page!";
}
?>

Wow, all kinds of new stuff there! First, two lines I don't need to explain: The extra require_once for CalcController, and the second 'use' line for CalcController with its namespace. Those are just there to load our CalcController class, just like the lines for HelloController. And another thing that's super easy to explain:
// Request dispatcher
This line is called a "comment". Any line that starts with a double-slash "//" is considered a comment, and you can type anything you want there without it affecting your code. It's good practice to drop in comments for any piece of your program that might need more explanation if someone else were to read your code.

But now we're getting into some logic we've never seen: IF statements! An 'if' statement allows you to write some expression that can boil down to "true" or "false". For example, let's say you type:
if (4 > 9000) {
	// Do something here...
}
You'll never reach the "Do something here" section, because 4 is obviously not over 9000 -- so it's "false". But if I were to type:
if (1 == 1) {
	// Do something else...
}
1 is definitely equal to 1, which is "true", so any code we put inside of those curly braces will run!

Let me pause quick to talk about that '==' sign: That looks crazy, right? I mean, in all of our old math classes, we just used one = sign to say if something was equal to something else. In the programming world, though (and this is true for nearly ALL programming languages), the single = sign is to assign a value to a variable. So saying:
if ($myVar = 2)
wouldn't actually compare $myVar with 2 to see if they're equal -- it would literally put the value of 2 into the variable $myVar, overwriting whatever $myVar was already holding. That's bad news! So for comparisons, we use double-equals signs. There's also triple-equals signs, but that's more advanced stuff that we don't need quite yet :). For now, know that this:
if ($myVar == 2)
will see if the variable $myVar ALREADY contains the value 2, and if not, will return "false" so that the code inside of that if-statement doesn't run.

[[ To Be Continued! ]]


#146139 Swift 2 open source for iOS, OS X and Linux

Posted by ianonavy on 08 June 2015 - 10:12 PM

http://www.macrumors...-2-open-source/

 

It looks like Cocoa will remain closed source for now, but Swift has a lot of features that I really like. I might try it out, but I'm not sure that this will make Swift one of the "big boys" like C, C++, Java, Python and Ruby.




#1298 Great Java Starting Resource

Posted by Supaazn on 08 March 2010 - 11:50 PM

This is a awesome book on beginning Java. It is the book used by my AP Computer Science teacher and gives you the nickle tour of the Syntax and Types of Java code. If you are just starting to learn Java, I highly recommend you to Download this book and read through a few of the lessons.

As a added bonus, this book is completely free and legit!

To View online -Click Here!- to save, Rightclick Save file.

I hope this book helps you gain insight into Java.
If this helped you, REP++ Please :-)

-Supaazn


#108403 Hi, I'm the guy who decides whether my company is hiring you.

Posted by Kyek on 12 November 2011 - 09:00 AM

A lot of folks on wdR have been talking about jobs recently -- finding a first one, switching jobs, changing career direction, etc., so I thought it might be a good time for a thread like this. I'm hiring into my department at work right now, and am seeing all the same mistakes here that I've seen reviewing resumes at my past few companies.

So here's a list of tips to keep in mind when putting a resume together, or refining the one you already have. I'm going to be using the pronoun "I" a lot, but keep in mind that nothing I'm saying here is just my own personal preference. I've worked with other hiring managers who work the same way I do. So some of what you read might seem heartless, but trust me when I say it's how it works -- at least in my area. Also note that this is definitely geared toward less-experienced workers -- if anyone's interested, we can discuss tips on how to describe previous positions in an eye-catching way and all that stuff later :)

So, getting on with it, my advice to all would-be hirees:
- The economy is bad and everyone is looking for a better job. That means that when I start hiring, I'm getting huge stacks of resumes that I need to filter through in an extremely short period of time. Here's what I look for in my first pass:
  • You must have the base technologies we use in your Skills section. I'm not talking frameworks and crap -- I can teach that quickly to a good programmer. But if I'm hiring for PHP and you don't have PHP listed, I don't care how quickly you can pick up new languages. I'm not taking the time to teach your the finer points of PHP.
  • There must be no indicators that you're a crap programmer. If I see Frontpage on your resume, you go to the trash immediately. Maybe you've moved on, maybe you never really needed it in the first place, but the fact is that you *might* use it regularly, and I have 200 other resumes from people without that risk. Goodbye.
  • See above, but with Dreamweaver or any other WYSIWYG editor. Generally I'll give you the benefit of a doubt it there's a very very obvious indicator on your resume that you're comfortable with any text editor, but if there's not, I can't waste my time interviewing 20 people that all might rely on software that writes their code for them.
  • You've done SOMETHING with the technologies we use. If you list PHP and you're not showing a portfolio with PHP work, or have a past job with PHP work, or -- and this is the very very worst it gets -- you don't even have your own website, then you're not worth my time.

- Congratulations, you've made it through my first pass. At this point, I've cut that stack of resumes down to half, at least. Now it's time for a second pass, and this is mostly to clear out the fakers. Here's what I'm looking for:
  • If your skills/technology section is huge, and this is an entry-level job, you likely fall into one of two categories:
    • You're very experienced and wouldn't be happy in this entry level job. This is very, very rare, but it's not worth interviewing you if this is the case.
    • Extremely common: You're putting shit on your resume that you've might have written two lines of or read some code of once. Chances are that I'm trashing this resume because there's no assurance that you know PHP or Java or whatever I'm hiring for any better than the 13 other languages in this list. The cherry on top is if you're a college sophomore.
    • Either way, you're not getting an interview.
  • If your resume is focusing on things that have nothing to do with the technologies we use, or even worse, has nothing to do with web development, you're getting trashcanned. I read one yesterday that focused on the guy's work with programmable microcontrollers. That's really impressive, when you're not applying for a web software position. I'm going to assume you don't really care about this field much and would rather be working with hardware. I don't need an employee that doesn't want to be here.
  • Are you a college student/graduate, and do you have relevant coursework listed? Well, congratulations, because it can't possibly help you. Here's why:
    • I head the department at my company. This means I'm pretty successful, and know what I'm doing, and am probably educated pretty well. Therefore, nothing on this list is going to be extremely impressive to me, because I've already done it.
    • If you DON'T have some of the tougher courses that I've taken, I'm going to think less of you. No programming patterns? No algorithms? Do I really want to have to teach a new hire all of these things, if he didn't care enough to learn them himself?
    • If you don't have a course list, I don't even think of the above things. It's safer to not have it, as long as you have side work or a past employer on your resume.
  • Are you listing references? Great. Do you have a work history, and yet your references are college profs or people you worked with at a really old job? That's a pretty good sign that you don't want me talking to people from recent jobs, which tells me you have something bad to hide. Trashcanned.

- Congratulations, you've made it through pass two. Chances are my stack of resumes is pretty thin, now. At this point, I'm going to start ranking them in order of most impressive to least impressive. Then I'm going to decide how many people I want to interview, and take that number of resumes from the top of the stack. Here's what I'm doing when I rank people:
  • Google. If I can't find you, that's bad news. Get a LinkedIn. If you tie that to social networks that portray you in a good light (huge, HUGE plus if they show that you're obsessed with web technology topics), that helps move you closer to the top of the stack.
  • Your own personal site. Don't have one? Unless your work history is ridiculously cool, you just bottom-stacked.
  • Time to peruse your personal site. Is it well-done? If this is a frontend position, I'm pouring through your source with a fine-toothed comb. God save your soul if I find even a hint that this site was generated by something other than your fingers on a keyboard. It's rare for people to get trashcanned at this point, but I'll do it if I see a string of two or more &nbsp's.
  • Is there a portfolio on your site? If you have an example of what you've done with the technologies we use, extra points for you. Even more if it's impressive.
  • Do you have a Github account? No? Bottom-stacked. People passionate about the web have Github accounts.
  • I'm on your Github page. Do you have open-source side projects? Do you contribute to any other open-source project? Major points.
  • Now I'm looking through your code on Github. Is it clean? Points. Is it documented? Big points. Are you using sane programming patterns/showing architectural potential? Huge points. Are you using txt tlk or not bothering with punctuation or capital letters? ...hmm, I'm looking for someone who represents us well to clients and can write really good documentation for their code, so this might drop you a place in the stack if there's someone else without this issue.
  • Do you have side-projects that you really care about? Flippin' awesome. Are they impressive enough that they're actually on your resume directly, rather than something I had to find via Google? Major points.
  • Are you in any area web organizations? Developer meetups, local startup conferences, anything like that? Not a lot of people are, but if you are, you're passionate as hell and I want to talk to you immediately as long as you have some of the above. Top-stacked.

- If you ranked high enough in that stack, you just got a phone call about scheduling an interview -- either a preliminary chat over the phone, or coming into the office. If there's a lot of interest in interview tips, let me know and I'll do another of these ;-).


OMG Kyek, some of these just aren't fair! I use Dreamweaver and I write good code!
Maybe you do. But when I'm reviewing 200 resumes, I don't have time to take risks with someone who might not hand-write their code. If you must put Dreamweaver on your resume, do it like this: Any text editor (Vim, Eclipse, Dreamweaver, etc). Then I won't be second-guessing you. But aside from that specific, keep in mind that nothing you say here, no response you give me, will ever change that fact that this is how many department heads in charge of hiring work. No one cares if you think it's fair or not, this is what's being used right now to filter through resumes. If, based on the above, you're likely to be filtered out, it's time to update your resume no matter how much you disagree.


OMG Kyek, I didn't realize it got so competitive! How can I move my resume closer to the top of the stack, without taking a lot of time to create sites/gain more experience/etc.?
This is a great question, because a big part of where you land is how well you represent yourself. I know it sounds shallow, but if your resume looks really good visually, I know you care about how you present yourself and that you pay attention to detail. Don't go overboard trying to make your resume look all fancy, but if your resume could have been typed out on a typewriter, you probably aren't grabbing my attention right away.

The other thing you need to do is tailor your resume to the job you're applying for. Maybe you really do know a ton of languages, but stop focusing on all your wealth of Java Java Java knowledge when I'm looking for PHP. Don't dump every Java framework you know into your skills section, for example, because I'm going to think you might not be happy working outside of that language. Rewording that as "various popular web frameworks" is going to get you a LOT farther.


That's it! Any questions?


#8474 Hydrogen Overview

Posted by Kyek on 25 March 2010 - 06:35 AM

Hydrogen Overview
Written by Kyek
For Hydrogen Version 0.3.0


DISCLAIMER
Hydrogen is currently in Alpha, meaning that it's in active development and the API may change from version to version in ways that could break existing code. There will be no deprecation until the first major release. So if you use Hydrogen, remember to check the changelogs when you update. They will tell you what needs to be changed in your code. This guide will also be updated to stay current with the latest codebase.

This overview is not for PHP beginners!
I'm planning to write a walkthrough-style tutorial for Hydrogen, but this is not it. Make sure you know these things before attempting to read this guide:
  • What classes and objects are and how to use them
  • What -> and :: do, and the difference between them
  • Basic programming patterns like Singleton and Factory

Contents
  • What is Hydrogen?
    • Hydrogen now
    • Hydrogen in the future
  • Getting Set Up
    • Downloading Hydrogen
    • Structuring Your App
    • Including Hydrogen
      • The Config File
      • The Autoconfig File
      • The Include File
  • The View components
    • View
    • The Hydrogen Templating Engine
    • The PurePHP Engine
  • The Controller components
    • Config
    • Controller
    • Dispatcher
    • Log
    • ErrorHandler
  • The Model components
    • Database: Hydrogen's portable database system
      • Queries through the Engine
      • Queries through the Query builder
    • SQLBeans: Writing your queries for you
    • Caching your data
      • Using the cache engine
      • Using RECache
    • Model: Tying it all together into one badass package

1. What is Hydrogen?
1a. Hydrogen Now
Hydrogen is a toolkit for PHP 5.3 and up that makes your webapps run faster, and run on more platforms. To fully grasp what it is, you should be familiar with MVC-style architecture. Big words scaring the crap out of you? It's actually a very simple thing :) Glance through my Introduction to MVC and you'll understand it all.

Hydrogen has the ability to take over your Model, View, and Controller layers -- but gives you the option to not use any of these parts, and instead use other libraries or your own solutions. Pieces of the framework are loaded as they're needed, so no unnecessary RAM or CPU time is eaten by using alternative libraries in conjunction with Hydrogen.

Hydrogen's model libraries work like magic, often automatically caching the results of certain functions and views, while simultaneously providing a last-line-of-defense against webserver overload. It has a powerful and flexible query builder, multiple caching engines, support for multiple database engines, a query translation system (allowing switching between multiple database backends without rewriting queries), a much-more-efficient-than-an-ORM structure that ... well, works kinda like an ORM, and more.

Its View library is extremely robust and allows you to use the powerful Hydrogen Templating Language which offers a Django-like syntax and features like template inheritance -- but if you prefer to use plain PHP and not bother yourself with learning another language, it supports that as well.

The Hydrogen Controller library implements more of that juicy juicy magic caching, along with a Dispatcher class that allows you to trigger your controllers based on any rules you set up. You're welcome to use any type of URL structure you want without limitation or constraint. Tack on a slew of helpful packages like a highly-integrated config system, logging system, error handler, and more, and Hydrogen -- even in its alpha stages -- is a pretty full package.

1b. Hydrogen in the Future
By Hydrogen's first full release, my goal is to have adapters for all the major SQL servers -- MySQL, PostgreSQL, and MSSQL at minimum, but I'd love Oracle and DB2 as well. I'm also planning on including support for more cache and semaphore engines. SQLBeans is in for a huge overhaul, making it easier and more intuitive to use without the performance sacrifices that come with traditional ORMs. And finally, Hydrogen is in dire need of simpler documentation split into multiple pages, rather than just this thread ;-)

If you have any suggestions for core components to add to Hydrogen, please speak up :)


2. Getting Set Up
2a. Downloading Hydrogen
Hydrogen can be downloaded from HydrogenPHP.com or the downloads section on Hydrogen's GitHub Page. In the future, there will hopefully be much more documentation on the main site ;-). For now, grab the zip, and extract it to a folder called 'hydrogen'.

2b. Structuring Your App
Hydrogen doesn't require you to structure your webapp's files and folders in any certain way. There are just three things that you'll want to have somewhere:
  • A place for a config file. I recommend a 'config' folder from the root so it's easily visible.
  • A 'cache' folder with permissions set to 777 so Hydrogen can write to it.
  • A place to store Hydrogen itself. I recommend lib/hydrogen.

2c. Including Hydrogen
2c1. The Config File
Hydrogen supports three different formats for the config file: A traditional .ini file with standard ini formatting, a .php file with standard ini formatting, and a .php file that interacts with the Config library directly to set your preferences. Samples of these files can be found in hydrogen/config/sample.

If you have a 'cache' folder as mentioned above, I recommend using config_sample.ini.php. It requires no special configuration to protect from public viewing, and the structure is very simple and forgiving. If you don't have a cache folder, I recommend config_sample.php as it will prevent your webapp from having to parse INI-format for every request. Only advanced users should use config_sample.ini, as it is a plaintext file that will be visible by the public without custom webserver configuration to protect it.

Whichever you choose, make a copy of it and put it wherever you're storing your configuration. Name it whatever you like, and open it for editing. Use the following guide to choose the appropriate options in this file:
  • [cache]
    • engine: This is the caching engine to use. Currently, the only supported cache engines are Memcache and Memcached, powered by the PHP extensions of the same names. If you don't have either of these extensions, leave this blank or set it to "No". This is case-sensitive.
    • pool_name: Memcached engine ONLY. Leave blank to make a new connection to the cache with every request, or set to any name to pool the connection so all requests share the same one. Note that if you are using a version of libmemcached > 0.38, some functions, like those used in pooling, do not function correctly with the Memcached PHP extension. To avoid crashes in this case, pool_name should be left blank.
  • [database]
    • engine: The database engine to use. Currently, the only supported database engine is MysqlPDO. If you have MySQL access in PHP, you will be able to use this engine. If you have another type of SQL server, Hydrogen currently does not include support for you. But please contact me if you're interested in writing an adapter for your server!
    • host: The hostname for your SQL server. This is usually localhost unless you have shared hosting. In which case, use whatever your host tells you to use.
    • port: The port your SQL server is listening on. If you weren't told otherwise, it's 3306.
    • socket: OPTIONAL. If you know you're running MySQL from a socket, you can type the path to the socket here. Note that specifying anything for this field will cause Hydrogen to use the socket instead of the host and port.
    • database: The name of the database to use.
    • username: The username to access the database with.
    • password: The password for the given user.
    • table_prefix: OPTIONAL. Specifying a prefix like "myapp_" will cause Hydrogen to add that prefix to each table name when using the Query library to build your SQL queries. This is useful to support users on restrictive hosting packages who may only be able to create one SQL database.
    • Using multiple databases? Check out hydrogen/config/sample/config_multidb_sample.ini.php for an example of using sub-keys in the config file to name different database configurations.
  • [recache]
    • unique_name: Set this to any short, unique string of characters, such as an abbreviation of your app's name. This prevents Hydrogen from overwriting cache entries of other Hydrogen-powered webapps using the same cache system.
  • [semaphore]
    • engine: The type of engine to use for semaphore handling. If you're using Memcache for the cache engine above, set this to Cache. Otherwise, leave this blank or set it to No. This is case-sensitive.
  • [errorhandler]
    • log_errors: Set this to 1 to automatically write errors into the logging system, or 0 to allow errors to go unnoticed. It's a good idea to set this to 1, and let your log settings determine whether errors get logged or not.
  • [log]
    • engine: The logging engine to use. Currently, TextFile is the only supported engine. Leave this blank or set it to No to disable the log system from loading completely.
    • logdir: (For the TextFile engine only!) The path to the folder where logfiles should be stored. Your cache folder is recommended since its permissions are already set to 777, but this can be any folder with 777 permissions. This path can be absolute, or relative from the install directory of the webapp. Note that the engine will throw an exception if the path does not exist/isn't writable, or if you've supplied a relative path for the logdir AND the config file in hydrogen.autoconfig.php. As of v0.1.2, it no longer returns false on error. And as of v0.2.0, relative paths are no longer relative from the config file.
    • fileprefix: (For the TextFile engine only!) What to name the generated logfiles. Specifying log_ will produce files named log_currentdate.log.
    • loglevel: The level of severity to log. Hydrogen's log system closely models the widely-used log4j standard, allowing you to log anything from bits of information to debug messages to serious errors, and this value will determine what actually gets written to the logfile. 0 = No logging, 1 = Log Errors, 2 = Log Warnings & worse, 3 = Log Notices & worse, 4 = Log Info & worse, 5 = Log Debug messages & worse.
Save and move on!

2c2. The Autoconfig File
In your hydrogen folder, copy or rename hydrogen.autoconfig.sample.php to hydrogen.autoconfig.php. This file is responsible for loading your configuration file as soon as Hydrogen is included in your page, and telling the Config module where and how to cache your config file. Following that, there's a listing of configuration options for the programmer (that's you!) to set, because they deal with the function of your app itself and aren't really something that should be exposed to the end-user in the main config file.

As of Hydrogen version 0.2.0, this file is extremely well documented. So read the text in there, tweak the variables as it describes, and you're all set!


2c3. Including Hydrogen
The hard work is done! All you need to do now is require_once() hydrogen at the top of any PHP file that can be loaded directly by the public. If you're using proper MVC structure, there will probably only be one PHP file that you need to do this for :) The file to include is hydrogen.inc.php, so the top of your file should look like this, for example:
<?php

require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");

// Your code here

?>

Now, in any of your php code after that require_once() line or in any other PHP files you include() or require(), all you have to do to use one of Hydrogen's libraries is write a 'use' line like this:
<?php

require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");

// Hydrogen's "Config" library is located in hydrogen/config/Config.php,
// if you look at Hydrogen's file structure.  So to use the Config library
// in our code, just change the slashes to backslashes, and take off the .php
use hydrogen\config\Config;

// Now we can use the config library!
echo Config::getVal("database", "password");

// osht we just gave out our MySQL password!

?>

3. The View Components
3a. View
use hydrogen\view\View;
View is as easy as it gets. In the MVC structure, it's important that you keep all HTML and other output in their own files, and that's what View is for!

View allows you to declare variables that the presentation layer of your website should have access to display, as well as load the appropriate templates. Templates can be pure HTML and PHP -- the languages you already know -- but they can also be written in the very snazzy Hydrogen Templating Language which gives you the ability to do a whole range of cool stuff.

For now, let's look at how to send variables to views and how to specify which view to show. Sending a variable to a view layer is very easy: You simply pick something that a view should be allowed to show, and send it with the View::setVar() command. For example:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\view\View;

View::setVar('title', 'Coolest Site Ever');
View::setVar('welcomeTo', 'killer page!');
View::load('welcome');

Notice on the last line, we're calling View::load() to tell the View library to load the "welcome" template. Could it be easier? ...well, yes! Here's how to load a view and send all those variables in just one line of code:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\view\View;

View::load('welcome', array(
	'title' => 'Coolest Site Ever',
	'welcomeTo' => 'killer page!'
	));

And that's as complicated as it gets! Now, though, you need to know how to write an HTML page that can echo those variables out. For that, you have two choices: The 'hydrogen' template engine, or the 'purephp' template engine. You can set which one to use in your autoconfig, as well as what extension to use for those files. The extension .tpl.php is the default, but that can be whatever you'd like.

So let's explore the template options!


3b. The Hydrogen Templating Engine
The Hydrogen Templating Engine was added in Hydrogen version 0.3.0, and is a very exciting addition to the View library. It offers a range of convenient commands and shortcuts, and is so powerful that it has its own guide available here:

Hydrogen Templates for Front-End Developers

I won't spend much time covering it in this overview as it already has its own guide, but let me outline the slickest part of this engine: Template inheritance. It sounds like a scary technical feature, I know, but just imagine being able to put your entire site layout in one file just like this:

base.tpl.php
<!DOCTYPE html>
<html>
    <head>
        <title>{% block pageTitle %}{% endblock %} - My Site</title>
        {% block header.css %}
            <link rel="stylesheet" href="/css/main.css" />
        {% endblock %}
    </head>
    <body>
        <div id="header">
            Welcome to My Site!
            <div id="navigation">
                Some links and styling here...
            </div>
        <div>
        <div id="content">
            {% block content %}
            {% endblock %}
        </div>
        <div id="footer">
            Copyright 2011 Me
        </div>
    </body>
</html>

And now every other page can inject itself into that template wherever there are blocks:
welcome.tpl.php
{% extends base %}

{% block pageTitle %}Welcome{% endblock %}

{% block header.css %}
    {% parentblock %}
    <link rel="stylesheet" href="/css/welcome.css" />
{% endblock %}

{% block content %}
    Welcome to my site!
{% endblock %}

So if you want to change your entire site layout and organization... you do it with just one file ;-).

For all other documentation, I defer to the Hydrogen Templates for Front-End Developers guide :)


3b. The PurePHP Engine
Don't want the hassle of learning to use the Hydrogen Templating Engine? No problem: just write your views in straight-up PHP! Select the 'purephp' engine in your autoconfig, and away we go!

You'll recall from the View section above that we're calling our 'welcome' template like this:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\view\View;

View::setVar('title', 'Coolest Site Ever');
View::setVar('welcomeTo', 'killer page!');
View::load('welcome');

So all we need to do is go to the view folder (defined in your autoconfig) and create the file welcome.tpl.php (or whatever extension you defined in the autoconfig) and add something like this:
<html>
    <head>
        <title><?php echo $this->title; ?></title>
    </head>
    <body>
        Welcome to my <?php echo $this->welcomeTo; ?> <img src='http://www.webdevrefinery.com/forums/public/style_emoticons/default/smile.gif' class='bbc_emoticon' alt=':)' />
    </body>
</html>

What's all that? Well, we're pulling in a variable called "title" and a variable called "welcomeTo". We say "$this->" because this variable belongs to the 'context' in which we're executing the template.

$this-> also grants us access to a few handy functions! Inside of a view file, if you need to build a URL, these are super convenient:
<html>
    <head>
        <title><?php echo $this->title; ?></title>
        <link rel="stylesheet" type="text/css" href="<?php echo $this->viewURL('/css/style.css'); ?>" />
    </head>
    <body>
        Welcome to my <?php echo $this->welcomeTo; ?> <img src='http://www.webdevrefinery.com/forums/public/style_emoticons/default/smile.gif' class='bbc_emoticon' alt=':)' />
        Go back to the <a href="<?php echo $this->appURL(); ?>">start</a>.
    </body>
</html>

viewURL() produces a URL relative to the root URL of the view folder, while appURL() produces a similar URL, but relative to the root of the webapp itself. Leaving the function void of arguments returns the appropriate URL with no trailing slash. Giving an argument appends the argument to that URL, and ensures appropriate slashes when they're concatenated. Note that it's also acceptable to construct URLs like this:
<?php echo $this->viewURL(); ?>/css/style.css

You can also include views in other views. For example, if your code for the page header is all in one file, you could include that in other pages like this:
<html>
    <head>
        <title><?php echo $this->title; ?></title>
    </head>
    <body>
        <?php $this->loadView('includes/header'); ?>
        Welcome to my <?php echo $this->welcomeTo; ?> <img src='http://www.webdevrefinery.com/forums/public/style_emoticons/default/smile.gif' class='bbc_emoticon' alt=':)' />
    </body>
</html>

loadView('includes/header') would cause the header.php file in the "includes" folder (inside of the views folder) to be inserted into the page at that point. This is a great way to include common code in your pages without having multiple copies to maintain if you need to change it!

Note that Hydrogen's view sandbox also understands two different variable scopes. Any variables accessed with $this->varname, whether you're reading them or setting them, will be available to all views -- whether they're printed with View::load() or $this->loadView(). However, simple variables like $myVar are local to the current view. To illustrate, suppose you have a view called varview.tpl.sphp that contains just this:
<?php $this->letter = 'B'; ?>
<?php $number = 2; ?>

If you loaded the following view...
<?php $this->letter = 'A'; ?>
<?php $number = 1; ?>
Time to include varview!
<?php $this->loadView('varview'); ?>
"letter" is now <?php echo $this->letter; ?>
"number" is now <?php echo $number; ?>

...then you would get this output:
Time to include varview!
"letter" is now B
"number" is now 1

Because $this->letter uses the request-level scope, varview can change it. But $number is view-scope, meaning it's private to each specific view, so varview's change doesn't affect the variable in the main view.

And that's the View library! Nice and easy :)

4. The Controller Components
4a. Config
use hydrogen\config\Config;
Hydrogen's Config library is very straight-forward. In the Getting Set Up section, we've already looked at the config file and how it's formatted. While this has all the config options that Hydrogen requires, you can add more options or groups of options of your own! This allows your entire webapp to be configured from one file.

Config is a static library, so it can be used globally without instantiation. There are only three functions you'll want to use:

getVal($section, $key, [$subkey = false])
Returns the value for the specified config option, or NULL if the option doesn't exist.

section is the name of the [section] you want to look into.
key is the name of the config property inside of the section.
subkey if the key is a numbered array or associative array, you can specify its index here. Otherwise, don't specify this and you'll get the value you want.

getRequiredVal($section, $key, [$subkey = false])
Returns the value for the specified config option, or throws a ConfigKeyNotFoundException if the option doesn't exist. It has all the arguments of the above function :)


So if you add this section to your config file:
[general]
site_name = Coolest Blog Ever
site_url = http://www.coolestblogever.com

You can echo the site name with this line:
echo Config::getVal("general", "site_name");

To set a configuration value within the code, there's this function:
setVal($section, $key, $value)

And its arguments should be pretty obvious. So if you want to override whatever the user typed in for their database username (I have no idea why you'd want to do this), you could run this line of code:
Config::setVal("database", "username", "JimBob");

Note that this line doesn't change the file at all. It just changes the value of that key for the duration of the request.

And now you know how to use the config system!

4b. Controller
use hydrogen\controller\Controller;
Controller is one of the simplest libraries included in Hydrogen. If you're already familiar with Hydrogen's Model library, then you already know how to use it :).

Controller, like Model, is meant to be extended into a new class. You should have a new Controller for all the base pieces of functionality in your site. Here's a simple start:
<?php
// Assuming hydrogen is already included
namespace myapp\controllers;

use hydrogen\controller\Controller;

class BlogController extends Controller {

}

?>

Ta-da! We now have a controller class. The public functions you write inside of this class should each handle a page request. For our blog, we'll want one page that shows a summary of the five most recent blog posts. Since it's the controller's job to call the right models and pass them to the right view, we're going to write a function that does just that (keep in mind that I'm making up this model code and passing it to an imaginary view -- you'll need to do that work yourself):
<?php
// Assuming hydrogen is already included
namespace myapp\controllers;

use myapp\models\BlogModel;
use hydrogen\controller\Controller;

class BlogController extends Controller {

	public function summary($pageNumber=1) {
		$bm = BlogModel::getInstance();
		$postData = $bm->getSummaryPage($pageNumber);
		
		if ($postData)
			View::load('blog_summary_page', array("posts" => $postData));
		else
			View::load('no_posts');
	}
}

?>

Simple, right? And that's all that goes into a controller function. We'd need more functions in here, of course -- one for loading just a single blog post and all of its details and comments, one for posting a comment, maybe we'd want to work posting a blog entry into this controller too. But for now, let's move on.

Let's say that, in this entire page, there's no part that's different depending on the user who loads it. That it looks exactly the same for everyone. That means we can cache the entire output of this page instead of generating it over and over again every time the page is requested, right? Check out how simple this is:
<?php
// Assuming hydrogen is already included
namespace myapp\controllers;

use myapp\models\BlogModel;
use hydrogen\controller\Controller;

class BlogController extends Controller {

	public function summary__($pageNumber=1) {
		$bm = BlogModel::getInstance();
		$postData = $bm->getSummaryPage($pageNumber);
		
		if ($postData)
			View::load('blog_summary_page', array("posts" => $postData));
		else
			View::load('no_posts');
	}
}

?>

With just those two underscores after the function name, we now have access to two functions in this controller: summary(), and summaryCached(). summary() will run the function like usual, without the underscores in it. summaryCached() will check to see if this page has been saved to the cache, and if so, simply output what's in the cache. If not, Controller will run the function directly, and listen for any output to the browser for the duration of the function call. Any output it catches will be cached using the RECache algorithm (we'll get to that later!) for five minutes.

Want it to be a time other than five minutes? Just include the number of seconds after the two underscores:
public function summary__1800($pageNumber=1)

1800 seconds=30 minutes, so now it'll be cached for 30 minutes instead -- all by just calling summaryCached() instead of summary() :).

Controller supports all the Magic Caching features of Model, including cache groups. So keep your eyes open once you get to the RECache and Model sections of this guide, and remember that anything you learn there can be applied here.

And that's it! Doesn't get much simpler :)

4c. Dispatcher
use hydrogen\controller\Dispatcher;
This is one of the libraries that sets Hydrogen apart from many other frameworks. Whereas most PHP frameworks nail you down into using a very specific URL structure, Dispatcher allows you to set up your URLs however you like, calling the appropriate controllers regardless.

The format for Dispatcher is that you set up a list of rules for what types of requests to direct to which controllers, and then call the Dispatcher::dispatch() function to process the current request with those rules. There are two types of rules in Dispatcher:

Mapping Rules
Mapping rules allow the controller name and function name to be specified in the URL itself. The most popular mapping rule is the PathInfoAutoMapRule, which would allow us to construct a URL like this in order to trigger the function in the example we used in the above Controller:
http://www.mydomain.com/appfolder/index.php/blog/summary/2

In a mapping rule, you specify which element is the controller name (in the case of the PathInfoAutoMapRule, it's the first PATH_INFO element -- "blog" in this case), which is the function name ("summary"), and which elements should be sent to the controller function as arguments ("2"). Mapping rules will automatically capitalize the controller name (so "blog" becomes "Blog"), and you can specify a namespace to tack on before the controller name, and a suffix to tack on after it. If we set the namespace to "\myapp\controllers" and the suffix to "Controller", our rule will now automatically call \myapp\controllers\BlogController. Woo!

Matching Rules
Matching rules call a predefined controller and function when the pattern matches a part of the request. Like mapping rules, matching rules can be applied to the PATH_INFO, GET variables, or the URL as a whole, just for starters. Most matching rules also allow you to define parts of the match to be sent as arguments.

But enough talkity talk. Here's how you use it.
Your webapp should have only one point of entry for this to be truly effective. You can have more, of course, but most programmers will want to have one single index.php file from where all pages will be accessed. And you'll set that up like this:
<?php
require_once(__DIR__ . '/lib/hydrogen/hydrogen.inc.php');

use hydrogen\controller\Dispatcher;
use hydrogen\view\View;

// Set up the variables that you need for pretty much all views
View::setVar("title", "FishCake, Yum!");

// You only need to do this part if you're not autoloading your classes.
// I recommend autoloading your classes exactly like Hydrogen does, but if
// you're not, you need these commands to tell Hydrogen what PHP files to
// include() for what classes.  Relative paths are relative to the base URL
// set up in the autoconfig file.
Dispatcher::addControllerInclude(
    '\fishcake\controllers\HomeController',
    'lib/fishcake/controllers/HomeController.php');
Dispatcher::addControllerInclude(
    '\fishcake\controllers\CakeController',
    'lib/fishcake/controllers/CakeController.php');
Dispatcher::addControllerInclude(
    '\fishcake\controllers\ErrorController',
    'lib/fishcake/controllers/ErrorController.php');

// The HomeMatchRule matches when there is no PATH_INFO.  So basically, this
// will match when index.php is loaded directly.
Dispatcher::addHomeMatchRule('\fishcake\controllers\HomeController', "index");

// The PathInfoAutoMap rule will match any time a correctly-formatted URL
// matches an existing controller and function name.  It's very versatile, as it
// covers most pages.
Dispatcher::addPathInfoAutoMapRule('\fishcake\controllers', "Controller");

// This rule matches everything, no matter what.  Good to use for a 404 page <img src='http://www.webdevrefinery.com/forums/public/style_emoticons/default/smile.gif' class='bbc_emoticon' alt=':)' />
Dispatcher::addMatchAllRule('\fishcake\controllers\ErrorController', "error404");

// And run the app!
Dispatcher::dispatch();
?>

Simple as pie! There are far too many rules available to list out here, but there's one for pretty much everything you'd want :) For a listing of all rules and examples of what they do, simply open hydrogen/controller/Dispatcher.php. And enjoy URL freedom :)

4d. Log
use hydrogen\log\Log;
If you're familiar with log4j, then you already know how to use this. Basically, there are five levels of "severity" you can use when logging:

Error: Use this to log serious errors in your code that you'd want to know about even when running your site on a production server.
Warn: Use this to log near-errors, or errors that your code is fully capable of handling on its own. Stuff you'd probably want to know about, but nothing serious.
Notice: Use this when something important happens that you might want to watch for, but isn't necessarily an error.
Info: General info to let yourself know when certain things have happened in your code. Stuff that might help out during development, but definitely wouldn't want to see on a production server.
Debug: Use this level to dump variable contents or other debug-only things you use during development. All things you won't care about once your code is stable.

Log is a static library just like Config, so you don't instantiate it yourself. Just call the function named for the severity level you want to use:
Log::debug("The 'world' variable is set to: $world");
Log::info("The world object has been initialized.");
Log::notice("Coder object sent message 'Hello' to World object.");
Log::warn("World object did not respond; continuing.");
Log::error("HOLY SHIT THE WORLD EXPLODED.");

Each of those functions has two more optional arguments -- one for filename to log and one for line number to log. If you don't specify those, the logger will use the filename and line number that you're calling the log on. But if, for some reason, you want to use a different value, you do that like this:
Log::warn("Found something really fat.", "yomamma.php", 2);

As you'll remember from the config file, you can specify which levels of severity you want the Log to actually ... well, log. So there's no need to remove the Log:: lines from your code when you launch it in production -- just set your log level to "error" or "warn" and go from there.

Ta-da, now you can log :)

4e. ErrorHandler
use hydrogen\errorhandler\ErrorHandler
Hydrogen's error handler puts a stop to ugly PHP errors. If you've ever seen a PHP error message come up when you loaded one of your pages, or if one of your pages just stopped loading right in the middle because of an error and you were left with half a page, this is the solution to that problem.

ErrorHandler captures your site's output rather than letting it be sent to the user, and only when the page is 100% complete and successful does it pass that information along to the user's browser. That way, if an error is encountered, ErrorHandler can trash what it's collected so far and send a nice, pleasant error page back to the user instead.

On an error, you can send back what appears to be a stock webserver error page, a much prettier default error page, or any other page that you specify -- and that can be accompanied with any HTTP error code you like. You can tell the browser that it's a 403 or 404 error, a 500 error, or whatever suits your fancy. You can also have the site redirect on an error. So instead of showing an error page, you can send the user back to the front page of your site, or to a Rick Roll. Whatever you feel like.

ErrorHandler is stackable, too. So as soon as your code starts, you can attach a default "omg I have no idea what just happened" error page. But then later, when you're about to, say, contact the Apple iTunes server, you could attach a "Failure to connect to iTunes" error page, then clear that error when the hard part is over. If something fails after that, the "omg I have no idea what just happened" page will show.

Make sense? Here's your arsenal:
// To respond with a webserver-like "500 Internal Server Error"
ErrorHandler::attach();

// To respond with a 404 instead (all status codes can be found
// at the top of ErrorHandler.php)
ErrorHandler::attach(ErrorHandler::HTTP_NOT_FOUND);

// To respond with the default pretty error page and a
// "500 Internal Server Error" error code
ErrorHandler::attachErrorPage();

// To respond with your custom error page and a
// "403 Forbidden" error code
ErrorHandler::attachErrorPage(__DIR__ . "/errors/403.php", ErrorHandler::HTTP_FORBIDDEN);

// To respond with a simple string and a 500 error code
// (just like the above functions, you can add an error
// code argument to this too if you want)
ErrorHandler::attachErrorString("OSHT I messed up <img src='http://www.webdevrefinery.com/forums/public/style_emoticons/default/sad.gif' class='bbc_emoticon' alt=':(' />");

// To redirect to another site in case of error
ErrorHandler::attachRedirect("http://fuck.org");

// To detach the last added error handler.
$successful = ErrorHandler::detach();
// If this results in the only attached error handler being detached,
// ErrorHandler will pass control back to whatever error handler you
// may have had set up before this.

// To detach any and all error handlers currently attached and pass
// error handling control back to whatever error handler you may have had
// set up before this.
ErrorHandler::detachAll();

// To set the types of errors that ErrorHandler will catch
ErrorHandler::setHandledErrors(E_ALL & ~E_DEPRECATED & ~E_NOTICE & ~E_WARNING);
// Note that the above is the default setting for ErrorHandler.  You can change
// it using this method, before or after any handlers have been attached.

Overwhelmed? It's ok -- generally, you only ever need to call ErrorHandler::attachPage(); and let that be it. Here's an example of how you can "stack" error handlers, though, to get more specific error messages for certain parts of your code:
<?php

// Import Hydrogen and start using its error handler
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\errorhandler\ErrorHandler;
ErrorHandler::attachErrorPage();

// If this errors, we get our nice default page
do_stuff();

// Now we're going to do risky stuff!  If it fails, there's a nice custom message
ErrorHandler::attachErrorString("Failed during nuclear bomb launch.  Try again later.");
launch_nuclear_bombs(4);
ErrorHandler::detach();

// If this next part errors, we get the nice default error page
// again, not the nuclear bomb message
do_more_stuff();
ErrorHandler::detach();

// If this fails, we're boned.  Our last attached handler has been removed,
// so we'll get whatever PHP's configured to do in case of an error.  Unless
// we had a different error handler set up before all of this, of course.
do_even_more_stuff();

?>

New in v0.2.0!
You can now send error headers without triggering an error! Check how easy it is to send a 404 page for a page that actually exists:
<?php
// Assuming hydrogen is already included
hydrogen\errorhandler\ErrorHandler::sendHttpCodeHeader(ErrorHandler::HTTP_NOT_FOUND);
?>
<html>
<head>
    <title>Uh-oh!  Not found.</title>
</head>
<body>
    The page you're looking for isn't here.  Sucks for you.
</body>
</html>

And that's ErrorHandler!

5. The Model components
5a. Database: Hydrogen's massive portable database system
5a1. Queries through the Engine
use hydrogen\database\DatabaseEngineFactory;
Hydrogen's database system is modeled very (very) closely after PDO. Thus, if you understand how to query through PDO, you can use Hydrogen's database library already. The benefit to Hydrogen's library is that any database interface -- MySQLi, ODBC, mysql_, and anything else -- can be used in exactly the same way PDO is used, as long as there's an adapter for it.

Note that, for best results, you should NOT submit queries directly through the engine, but instead, using the Query Builder outlined below. Queries submitted through the engine are not translated to the format necessary for the currently connected SQL server, so they'll only work if you're targeting a specific type of server like MySQL. But enough warnings, let's get on with it!

Getting a database link:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\database\DatabaseEngineFactory;

$db = DatabaseEngineFactory::getEngine();
// $db is now a connected database engine object.

Simple, right? This makes a new database connection with the information in the config file. If there's more than one database config in there, you can pass in the subkey name as an argument to getEngine() -- see hydrogen/config/sample/config_multidb_sample.ini.php for an example of such a config file.

Alternatively, you can supply a server type, hostname, port, username, and yadda yadda yadda in the getCustomEngine() function. I won't go into detail here since it's so rare you need to do that, but see hydrogen/database/DatabaseEngineFactory.php for full documentation on this function.

Important: DatabaseEngineFactory, as the name suggests, uses the Factory pattern. If you're not familiar, this means that every time you call "getEngine()", you'll get the EXACT same object back. Not a copy of it, not a new one that acts the same way, the EXACT same object that's already in use. This prevents you from making multiple connections to the same server. The only way you'll get a different object back is if you supply a different hostname/database/subkey name/etc.

Once you get $db, you can interact with it much like a PDO instance -- again, even if the driver for the engine being used isn't a PDO driver. For full documentation of each function, open hydrogen/database/DatabaseEngine.php. This is one of the few files that's 100% documented :)

Example query using the $db object above:
$stmt = $db->prepare("SELECT username FROM users WHERE user_id = ?");
$stmt->execute($userId);
$result = $stmt->fetchObject();
$username = $result->username;

echo "Your name is $username";

5a2. Queries through the Query Builder
use hydrogen\database\Query;
The Query object is the recommended way to write full queries, if you're not using SQLBeans. It allows you to construct a query using the SQL you already know, without ever concatenating strings or using messy logic to determine when you need a space or an AND. In addition, the Query object will automatically format your SQL query to match whatever SQL server you're connected to.

Currently, the Query class only supports the SELECT, INSERT, UPDATE, and DELETE verbs. CREATE, ALTER, and others are planned for future releases.

Here are some samples of how to use the Query object:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\database\Query;

// Build the query
$query = new Query("SELECT");
$query->field("username");
$query->from("users");
$query->where("user_id = ?", $userId);

// From here, we can execute our query and get the results exactly as if
// $query were a DatabaseEngine or PDO instance.
$stmt = $query->prepare();
$stmt->execute();
$result = $stmt->fetchObject();
$username = $result->username;

// Let's delete something
$query = new Query("DELETE");
$query->from("users");
$query->where("username like ?", $username);
$query->limit(1);
$stmt = $query->prepare();
$stmt->execute();

// Time to make a new user.  Actually, hell, let's make three.
$query = new Query("INSERT");
$query->intoTable("users");
$query->intoField("username");
$query->intoField("email");
$query->intoField("joindate");
$query->values("(?, ?, NOW())", array($usernames[0], $emails[0]));
$query->values("(?, ?, NOW())", array($usernames[1], $emails[1]));
$query->values("(?, ?, NOW())", array($usernames[2], $emails[2]));
$stmt = $query->prepare();
$stmt->execute();

// And let's update an existing one
$query = new Query("UPDATE");
$query->table("users");
$query->set("email = ?", $newEmail);
$query->where("user_id = ?", $userId);
$query->limit(1);
$stmt = $query->prepare();
$stmt->execute();
?>

The fun part is, you don't have to build your queries "in order" anymore. For example, this works great:
$query = new Query("SELECT");
$query->limit(10);
$query->orderby("username");
$query->field("username");
$query->from("users");
$query->field("email");
$query->where("email like ?", "%gmail.com");
$stmt = $query->prepare();
$stmt->execute();

The thing that separates the Query class from other array-based builders is that everything that's possible with typing out a MySQL query by hand is possible by using the Query class. It imposes no artificial limitations.
/*
INSERT ... SELECT syntax
http://dev.mysql.com/doc/refman/5.0/en/insert-select.html

INSERT INTO table1 (field1,field3,field9)
SELECT field3,field1,field4
FROM table2;
*/
$query->new Query("INSERT");
$query->intoTable("table1");
$query->intoField("field1");
$query->intoField("field3");
$query->intoField("field9");
$sel->new Query("SELECT");
$sel->field("field3");
$sel->field("field1");
$sel->field("field4");
$sel->from("table2");
$query->select($sel);
$stmt = $query->prepare();
$stmt->execute();

/*
STRAWBERRY QUERY
http://dev.mysql.com/doc/refman/5.0/en/example-maximum-column-group-row.html

SELECT s1.article, s1.dealer, s1.price
FROM shop s1
LEFT JOIN shop s2 ON s1.article = s2.article AND s1.price < s2.price
WHERE s2.article IS NULL;
*/
$query = new Query("SELECT");
$query->from("shop", "s1");
$query->field("s1.article");
$query->field("s1.dealer");
$query->field("s1.price");
$query->join("shop", "s2", "LEFT");
$query->on("s1.article = s2.article");
$query->on("s1.price < s2.price", false, "AND");
$query->where("s2.article IS NULL");
$stmt = $query->prepare();
$stmt->execute();

/*
SELECT name, category, price
FROM products
WHERE (price > 5 AND price < 10)
    OR (name like 'diamonds' AND (price = 200 OR price > 500))
GROUP BY category
HAVING MAX(price) > 8
LIMIT 5, 10
*/
$query = new Query("SELECT");
$query->field("name");
$query->field("category");
$query->field("price");
$query->from("products");
$query->whereOpenGroup();
$query->where("price > ?", 5);
$query->where("price < ?", 10, "AND");
$query->whereCloseGroup();
$query->whereOpenGroup("OR");
$query->where("name LIKE ?", "diamonds");
$query->whereOpenGroup("AND");
$query->where("price = ?", 200);
$query->where("price > ?", 500, "OR");
// The following two lines are optional since there are no more WHERE
// expressions, but I'll include them here to eliminate confusion.
$query->whereCloseGroup();
$query->whereCloseGroup();
$query->groupby("category");
$query->having("MAX(price) > ?", 8);
$query->limit(10, 5);
$stmt = $query->prepare();
$stmt->execute();

Finally, Query also supports PDO-style :variable syntax.
$query = new Query("SELECT");
$query->field("username");
$query->from("users");
$query->where("group_name = :group");
$stmt = $query->prepare();
$stmt->execute(array("group" => "Administrators"));

Believe it or not, this class is capable of even more fanciness -- but for a complete listing of its functions, see hydrogen/database/Query.php. To see how queries are formatted or to write your own server-specific formatter, see hydrogen/database/QueryFormatter.php and hydrogen/database/formatters/StandardSQLFormatter.php.

5b. SQLBeans: Writing your queries for you
use hydrogen\sqlbeans\SQLBean;
SQLBeans is one of the most powerful parts of Hydrogen. It takes the concept of Javabeans (objects meant only for storing data in Java), and adds SQL CRUD (Create, Read, Update, Delete) functionality to make communicating with your database as easy and as hassle-free as possible. It's similar in concept to an ORM, but doesn't have the inherent inefficiencies of ORM queries.

First, an example of what SQLBeans is capable of. In this example, UserBean automatically communicates with our database's 'user' table:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
require_once(__DIR__ . "/lib/myapp/sqlbeans/UserBean.php");

use hydrogen\database\Query;

$query = new Query("SELECT");
$query->where("username = ?", $username);
$users = UserBean::select($query);

if ($users) {
    // $users is an array of users.  We're only concerned with the first (and probably only) one
    $user = $users[0];

    echo "Hello there, " . $user->username . "! You currently have " . $user->score . " points."
    echo "I'm going to give you 10 more!";

    $user->score = $user->score + 10;
    $user->update();

    echo "You now have 10 more points in our database!  Awesome!";

    $user->delete();

    echo "lol, we just deleted you from the database.  Too bad.";

    $fred = new UserBean();
    $fred->username = "Freddy";
    $fred->score = 0;
    $fred->insert();

    echo "But we just made a new dude named Freddy with a default score of " . $fred->score . "!";
    echo "Maybe he'll have better luck.";
}
else
    echo "Sorry, that user doesn't exist";

?>

So let's get into what makes the magic happen, here. Like Hydrogen's Model class, in order to use SQLBeans, you must create a new class of your own and extend Hydrogen's SQLBean library. So create a new php file (name it whatever you like) and do that. It should look something like this:
<?php
// Namespace is optional, but you should get into the habit of using it
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UserBean extends SQLBean {

}
?>

You don't need to include any logic code in that file, but we WILL need to define some variables so that SQLBeans knows all about the database table this matches up to. Here's our file with those variables added. See my comments to understand what each variable says:
<?php
// Namespace is optional, but you should get into the habit of using it
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UserBean extends SQLBean {
	/**
	 * The name of the table this bean refers to.  Do not
	 * include the prefix you're using, if any
	 */
	protected static $tableNoPrefix = 'users';

	/**
	 * A unique alias for this table.  This can be anything,
	 * as long as none of your other beans have the same alias.
	 */
	protected static $tableAlias = 'usr';

	/**
	 * The name of this table's primary key field
	 */
	protected static $primaryKey = 'id';

	/**
	 * Usually, your primary key will be set to auto-increment,
	 * or increase by 1 automatically.  Set this to true if that's
	 * the case for this table, or false if you have to specify your
	 * own ID every time you insert.
	 */
	protected static $primaryKeyIsAutoIncrement = true;

	/**
	 * An array of every field in the table.
	 */
	protected static $fields = array(
		'id',
		'username',
		'score'
		);
}
?>

Easy enough, right? Just look into your database and answer the questions. Now you're done! require_once() that file into your code, and you'll be able to use your bean just as I did in my example. One thing to note from my example is how the Query object is used with SQLBeans to narrow your results.
// This will print a list of every single user in the database
$users = UserBean::select();
foreach ($users as $user)
	echo $user->username . "<br />";

// This will get an array of the first 10 users in alphabetical order
$query = new Query("SELECT");
$query->orderby("username", "ASC");
$query->limit(10);
$users = UserBean::select($query);

// This will get all users whose names start with F
$query = new Query("SELECT");
$query->where("username like ?", "F%");
$users = UserBean::select($query);
SQLBeans will use the exact same query object you provide to form its own database query, so don't do anything crazy like adding tables or sending an "INSERT" query or anything like that. Just use where, group by, order by, limit, and all those extras to get exactly the results you want.

So while that's convenient and all, this isn't anything you couldn't easily do with the query object alone. The power of SQLBeans lies in bean mapping -- tying beans to other beans.

Let's say, for example, that we added a "usergroups" table to our database, with three simple columns. 'id' for our primary key, 'name' for the name of the group, and 'description' to say what it's for. Let's make a UsergroupBean object for that:
<?php
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UsergroupBean extends SQLBean {
	protected static $tableNoPrefix = 'usergroups'
	protected static $tableAlias = 'ugrp';
	protected static $primaryKey = 'id';
	protected static $primaryKeyIsAutoIncrement = true;
	protected static $fields = array(
		'id',
		'name',
		'description'
		);
}
?>
In our 'users' table, we'll have to add a field to show which group each user belongs to. So our UserBean becomes this:
<?php
// Namespace is optional, but you should get into the habit of using it
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UserBean extends SQLBean {
	protected static $tableNoPrefix = 'users';
	protected static $tableAlias = 'usr';
	protected static $primaryKey = 'id';
	protected static $primaryKeyIsAutoIncrement = true;
	protected static $fields = array(
		'id',
		'username',
		'score',
		'group_id'
		);
}
?>
Now, the mapping! Every group_id value in the user table will match exactly one id in the usergroups table. Here's how we describe that link within our UserBean:
<?php
// Namespace is optional, but you should get into the habit of using it
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UserBean extends SQLBean {
	protected static $tableNoPrefix = 'users';
	protected static $tableAlias = 'usr';
	protected static $primaryKey = 'id';
	protected static $primaryKeyIsAutoIncrement = true;
	protected static $fields = array(
		'id',
		'username',
		'score',
		'group_id'
		);

	// We add $beanMap to say that this bean is linked to other beans
	protected static $beanMap = array(
		// For each "link", we provide a name.  We'll name this one "group"
		'group' => array(
			// The type of JOIN to use.  LEFT, INNER, OUTER, RIGHT -- all valid here
			'joinType' => 'LEFT',

			// The full name of the bean to join with.  If you're not using
			// namespaces, this would just be "UsergroupBean"
			'joinBean' => 'myapp\sqlbeans\UsergroupBean',

			// The name of the field in this table to link with the primary key
			// of the joined table.  The usergroup table's primary key is 'id',
			// which matches up to the group_id field in this table, so we use
			// group_id here.
			'foreignKey' => 'group_id'
			)
		);
}
?>
And that's it! If you're confused, take your time reading through it -- as long as you're familiar with how JOINs work in SQL, this should be no sweat :)

Once that's done, here's how powerful SQLBeans becomes:
$query = new Query("SELECT");
$query->limit(2); 
$users = UserBean::select($query);

foreach ($users as $user) {
    $group = $user->getMapping("group");
    // $group is now a UsergroupBean object containing the group data
    // for this user.
    echo "Hello " . $user->username . "!<br />";
    echo "You're in group " . $group->name . "!<br />;
    echo "Some information about that group: " . $group->description . "<br />";
}
The select for the user automatically pulled the information for the group :). And it doesn't stop there -- UserBean could have had more than one mapping, the objects it's mapped to could have had THEIR own mappings, THOSE mappings could have mappings, and SQLBeans would have gotten all that information and organized it with just one SQL query.

But it's not just that we have access to the group info now. Let's say both of those users are in the same group -- meaning, their group_id fields are the same. SQLBeans sees that and shares a group object between them. So you can do this:
$group1 = $users[0]->getMapping("group");
$group2 = $users[1]->getMapping("group");

// group1 and group2 are both the exact same UsergroupBean object
// since our two users have the same group_id.  So we change one,
// and the other changes too.
$group1->description = "Members of this group like cheese.";

echo $group2->description;
// Outputs "Members of this group like cheese."

$group2->update();
// Saved that description change to the database
But what if we need to work with a user's group name a lot? It's pretty messy to do that getMapping() call in our code, so SQLBeans provides a way to include variables not in that bean, and override variables that are. Let's add two functions to the UserBean.php file:
<?php
// Namespace is optional, but you should get into the habit of using it
namespace myapp\sqlbeans;

use hydrogen\sqlbeans\SQLBean;

class UserBean extends SQLBean {
	protected static $tableNoPrefix = 'users';
	protected static $tableAlias = 'usr';
	protected static $primaryKey = 'id';
	protected static $primaryKeyIsAutoIncrement = true;
	protected static $fields = array(
		'id',
		'username',
		'score',
		'group_id'
		);
	protected static $beanMap = array(
		'group' => array(
			'joinType' => 'LEFT',
			'joinBean' => 'myapp\sqlbeans\UsergroupBean',
			'foreignKey' => 'group_id'
			)
		);

	protected function get_groupname() {
		$group = $this->getMapped('group');
		return $group !== false ? $group->name : false;
	}

	protected function set_groupname($val, $func=false) {
		$group = $this->getMapped('group');
		return $group !== false ? $group->set('name', $val, $func) : false;
	}
}
?>
Now you can do this in your code:
echo "User " . $users[0]->username . " is in group " . $users[0]->groupname;
$users[0]->groupname = "Banned";
$users[0]->update();
echo "But now his group name has changed in the database!";
Note that you don't even call the function we just wrote above -- simply accessing the "groupname" variable will cause SQLBeans to check to see if there's a get_groupname() method it should be running. And when we set the group to Banned, before SQLBeans looks to see if it actually has a variable named groupname (which the UserBean doesn't), it looks to see if it has a function called set_groupname (which it does :)).

You can even write get_ and set_ functions to override variables the bean already has. This is most useful with dates in SQL, because you can convert dates to one format when they're "gotten", and convert them back into the format your SQL table needs when they're set.

SQLBeans has a hundred more features than what I've covered here -- like joining beans with a custom "ON" clause instead of using a foreign key, telling them not to do any mapping when you call select(), giving them a custom mapping to follow during select(), using a different database engine than what's set up in the config file, swapping out entire mapped beans (to change which group a user is in, for example), error handling, setting variables equal to SQL functions, serializing and unserializing them without losing their database connection, and on and on and on. If you're interested in one of these things, see hydrogen/sqlbeans/SQLBean.php or ask here :)

The source code for AppDB is also a great resource, as it uses (literally) every feature of SQLBeans at the time of this writing. See lib/appdb/sqlbeans for the beans themselves, and lib/appdb/model for the files that use the beans.

5c. Caching your data
5c1. Using the cache engine
use hydrogen\cache\CacheEngineFactory;
Hydrogen's cache system is similar in structure to its database system. You get an engine from the factory, and no matter how many times in your code you get that engine, it's always the exact same object. You communicate with all cache engines in the exact same way, no matter which the config file is set to. You can get an engine and execute cache commands even when people aren't using a cache, and your code will still function as expected.

Also like the database system, I do not recommend using it directly. You can, and this guide will show you how, but it's a much better idea to use RECache as described in the next section.

The cache system is meant to store bits of information in RAM -- most often SQL query results, as pulling those from a cache is much faster than having an SQL server process the query. When you store something, you assign it a "key", or an identify string, which you can use to get the information back later. You also specify an expiration time, so you can say your cached data is allowed to stay cached for 5 minutes. After that time, the cache engine should delete it so that you're forced to cache updated data.

The CacheEngine system is modeled closely after PHP's Memcache library. Here's a very simple walkthrough of how to use it:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\cache\CacheEngineFactory;

// First we get the cache engine.  The actual engine returned depends on
// what's in the config file, but we don't need to know what engine is
// being used to use it.
$cache = CacheEngineFactory::getEngine();

// Try to get our page 1 blog posts from the cache.
$posts = $cache->get("blog_page_1");

// Was it in there?
if (!$posts) {
    // Our posts weren't in there.  Let's get them from MySQL.
    $posts = a_function_to_get_our_posts_from_mysql();
    // Now that we have the data, cache it for next time
    $cache->set("blog_page_1", $posts, 300);  // 300 seconds = 5 minute cache
}

// Now we use $posts just like we would normally
display_blog_page($posts);

?>

99% of the time, get and set are all you'll need to use if you're communicating directly with the cache engine. There are many more methods available, however. hydrogen/cache/CacheEngine.php has a listing of all methods, and full documentation for each :).

Using the CacheEngine directly leaves a lot to be desired, though. Keep reading to see RECache's features, and you'll understand why!

5c2. Using RECache
use hydrogen\recache\RECacheManager;
Welcome to RECache, the very first Hydrogen library, and the entire reason I wrote Hydrogen in the first place :). RECache stands for Ridiculously Efficient Cache, and the name isn't an empty promise :).

Features
  • Enforces key expiration times (sometimes cache expiration is too glitchy to count on)
  • Allows key "grouping", so huge groups of keys can all be manually expired at the same time.
  • Allows keys to belong to multiple groups
  • Uses a unique semaphore system to ensure that only one visitor can update the cache at a time, without slowing down any other users or forcing them to wait.
  • Detects when your server is getting more requests than it can handle, and cancels certain requests to keep the server from crashing.
  • Serves an entire page from the cache if it exists, or caches an entire page if it doesn't, all with one function call.
  • Allows you to execute code if the frequency of a certain action rises above a certain level
  • Allows you to cache a big object if and only if it's requested at a high frequency

The first problem with traditional caching
Let's look at the code example from above:
$posts = $cache->get("blog_page_1");
if (!$posts) {
    $posts = a_function_to_get_our_posts_from_mysql();
    $cache->set("blog_page_1", $posts, 300);
}
display_blog_page($posts);
That works for low-traffic sites, but imagine what would happen if 20 people came to the site all at the same time. MySQL queries are fast, but not instant -- sometimes taking a second or more. 20 people will come in, and the cache won't have the blog page stored for any of them. So all at the same time, 20 people will execute the exact same MySQL query, causing the query to take longer than usual. In that time, more people will visit the site, not get the cached version of the page, and start the mysql query. If you have a high-traffic site, this can topple your entire server. If you have a moderate-traffic site, it still has the potential to noticibly slow the site down for your visitors.

The RECache algorithm solves that by making sure only ONE person can run the MySQL query and update the cache. All other visitors are served data that's been expired for less than a few seconds while that happens. No slowdown, and eliminates practically all load from the MySQL server.

The second problem with traditional caching
You have a blog. Your blog has 20 pages. Each one is set to go into the cache for 10 minutes once it's requested. You make a new blog post, and now the last post on each page gets moved back a page. Only not, because the pages have all been cached at different times. For 10 minutes after every blog post, you have posts duplicated between page 1 and 2, page 2 and 3, page 3 and 4, etc. You know how many real blogs have that problem? None.

With RECache, that's not a problem. Put all those cached pages in the "blogpages" group. When you make a new post, tell RECache to expire all keys in that group. Whether the group has 10 keys or 10 thousand keys in it, the expiration takes place instantly.

Get started with Caching and Grouping
Let's start easy, getting the blog page data.
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\recache\RECacheManager;

// Get the RECache object
$recache = RECacheManager::getInstance();

// What blog page was requested?
$reqPage = $_GET["page"] ?: 1;

// See if RECache has it
$pageData = $recache->get("page_$reqPage");
if (!$pageData) {
    // RECache doesn't have it?  Get it and cache it.
    $pageData = get_blog_page($reqPage);
    $recache->set(
        "page_$reqPage", // Name of the key to set
        $pageData,       // Value to store in this key
        600,             // Seconds until key expires
        "pages"          // Group name or array of group names for this key
        );
}

// Now the data is ready for you to use
display_blog_page($pageData);
?>

But now, using recache, we can force all cached pages to expire at the same time by saying:
$recache->clearGroup("pages");
And magically, all of those pages will be updated the next time they're requested :)

The same thing, using the RECache Algorithm
Even though we just did that with the RECache library, my "RECache Algorithm" -- the thing that makes sure your SQL server doesn't get overloaded -- wasn't used. Here's how to do the exact same thing we just did, using that algorithm. Hint: It's mad crazy easier.
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\recache\RECacheManager;

// Get the RECache object
$recache = RECacheManager::getInstance();

// What blog page was requested?
$reqPage = $_GET["page"] ?: 1;

// Get the data.
$pageData = $recache->recache_get(
    "page_$reqPage",  // The name of the key to get
    600,              // Expiration time if the data needs to be cached
    "pages",          // Group name or array of group names for this key
    "get_blog_page",  // Name of the function to call for updated data if necessary
    array($reqPage)   // Array of arguments to pass to that function
    );

// Now the data is ready for you to use
display_blog_page($pageData);
?>
One function call, and that's all it takes. No if-statements, no deciding when to update the data... RECache takes care of it all, and it makes sure that only one person gets to update the cache at a time. No server overloads. You can expire the "pages" group exactly how we did above, too.

Caching an entire page
Sometimes you'll want to cache a full page of output -- doing this for RSS feeds is really common. All it takes is one function call once you have the $recache object.
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\recache\RECacheManager;

// Get the RECache object
$recache = RECacheManager::getInstance();

// Print the page and quit if it's cached, or let
// the page load and THEN cache it if it's not.
$recache->pageCache(
    600,         // Number of seconds until expiration
    "RSS_page",  // Key name
    "feeds"      // Group or array of groups to put this key into
    );

// All the code to print the page normally goes here
?>
There you have it. One function call to handle either printing the entire page from the cache, or putting an entire page into the cache -- depending on whether it's already there or not. IMPORTANT: That's the non-recache-algorithm version of pageCache, which allows for frequency checking (we'll cover that soon). If you want to use the recache algorithm, just change $recache->pageCache to $recache->recache_pageCache :)

Checking Frequency
Even though we're not caching any important data with this, a memory cache is still the best way to track the frequency of a certain action across all of your visitors. In this example, we're going to track how often the page is loaded. If traffic gets really high, we're going to use a totally made-up function to E-mail the site admin.
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\recache\RECacheManager;

// Get the RECache object
$recache = RECacheManager::getInstance();

// Have we gotten more than 30 requests in the past 60 seconds?
// ("pageloads" in the line below is just the key name we're using to track this)
if ($recache->checkIfFrequent(30, 60, "pageloads"))
    email_admin("HOLY SHIT THE SITE IS EXPLODING!");

// Continue loading the page here <img src='http://www.webdevrefinery.com/forums/public/style_emoticons/default/smile.gif' class='bbc_emoticon' alt=':)' />
?>

Caching frequently requested data
Sometimes a user's request generates a ton of data that you could cache, but you're reluctant to cache all that data if no one's likely to use it. The most common example of this is search results. You want to cache real, common searches, but you don't want to cache people's typos -- things that will never be searched again. Caching search results only if the search is common is a good way to go.
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
use hydrogen\recache\RECacheManager;

// Get the RECache object
$recache = RECacheManager::getInstance();

// What are we searching for?
$searchTerm = $_GET['search'];

// Get the page data
$pageData = $recache->get("search_$searchTerm");

if (!$pageData) {
    // The search isn't cached.  Get the results, 
    // and cache it only if it's common
    $pageData = run_search($searchTerm);
    $recache->setIfFrequent(
        3,                     // Number of requests that have to happen
        60,                    // ...within this many seconds.
        "search_$searchTerm",  // The key to save the data under
        $pageData,             // The data to be saved
        3600,                  // Number of seconds until expiration
        "pages"                // Group name or array of group names to use
        );
}

// Now use $pageData as you normally would
display_search_results($pageData);
?>

Other Stuff
This was the tip of the RECache iceberg. There are many other features not covered here -- like increment/decrement, stats tracking, getting the REAL hit/miss count, clearing individual keys, clearing all keys cache-wide, using the RECache Algorithm more directly with recache_get_wrapper, and much much more. For a listing of all RECache functions with some (very) basic documentation, see hydrogen/recache/RECacheManager.php.

5d. Model: Tying it all together into one badass package
use hydrogen\model\Model;
All these caching and database libraries are fun tools, but they're all very separate and a pain to use individually. The Model library is a ridiculously simple library to make using all of this stuff very very easy :). The idea is that you'll have a "Model" object for each of the main data parts of your webapp. For example, in a blog, you'd have one to handle users, one to handle blog posts, and one to handle blog comments -- just to start :). And their job would be to get and return data in exactly the format you need it. Let's start by building a UserModel that will get all of our User data for us.

Model, like SQLBean, is used by writing your own class that "extends" it. So let's do that now:
<?php
// Namespaces are optional, but recommended
namespace myapp\models;

use hydrogen\model\Model;

class UserModel extends Model {

}

?>
Simple enough :) Note that I didn't include the hydrogen.inc.php file there, because it should already be require_once()'ed by whatever PHP file that will be including our new UserModel.php file.

Our model class needs only one thing to do its job: a "protected static" variable called $modelID. $modelID should be a unique string that doesn't match any of your other models. Since this is our UserModel, I'll just call it "um".
<?php
// Namespaces are optional, but recommended
namespace myapp\models;

use hydrogen\model\Model;

class UserModel extends Model {
    protected static $modelID = "um";  // That was easy <img src='http://www.webdevrefinery.com/forums/public/style_emoticons/default/smile.gif' class='bbc_emoticon' alt=':)' />

}

?>

Now we're set, and we can put functions inside of this to get our data! So I'm going to include Query from Hydrogen and the UserBean that we wrote with SQLBeans earlier, and write up a small function to get all a user's information by his ID number:
<?php
// Namespaces are optional, but recommended
namespace myapp\models;

use hydrogen\model\Model;
use hydrogen\database\Query;

// I'm going to assume the PHP file for this bean was already included at some point
use myapp\sqlbeans\UserBean;

class UserModel extends Model {
    protected static $modelID = "um";  // That was easy <img src='http://www.webdevrefinery.com/forums/public/style_emoticons/default/smile.gif' class='bbc_emoticon' alt=':)' />

    /**
     * Takes a user ID integer and gives back a fully populated
     * UserBean object, with the attached UsergroupBean.
     *
     * @param userId number The ID of the user to get
     * @return mixed All the information about the requested user,
     *     or false if the user does not exist.
     */
    public function getUser($userId) {
        $query = new Query("SELECT");
        $query->where("id = ?", $userId);
        $userArray = UserBean::select($query);
        if (!$userArray)
            return false;
        return $userArray[0];
    }
}

?>
If you've been awake during the "Query" and "SQLBeans" sections, this should make sense :) In our function, we make a new SELECT query, narrow it down to one specific id, and hand it off to the UserBean, which gives us an array. If the array is empty, that means there was no user with that ID. Otherwise, our user is going to be the very first (and only) thing in that array, so we return it.

Right now you're probably asking yourself "So what good is this? We're not doing anything different." And if you are, that's very observant of you! Right now, in our main PHP code, we can do this:
<?php
require_once(__DIR__ . "/lib/hydrogen/hydrogen.inc.php");
require_once("/path/to/UserModel.php");
use myapp\models\UserModel;

// Models are singletons, so instead of saying "new UserModel", we say this:
$userModel = UserModel::getInstance();

// Now we can call the function inside of it
$firstUser = $userModel->getUser(1);

?>
Still no big deal, right? We're doing some fancy object-oriented stuff, but still nothing that couldn't be achieved without this crazy model class. UNTIL we change our UserModel code from this:
public function getUser($userId) {
to this:
public function getUser__($userId) {
And now, instead of calling "getUser", we can call "getUserCached" -- and the output of that function will be cached using the RECache algorithm for five minutes. Check out the code that uses it:
// Get the Model
$userModel = UserModel::getInstance();

// Get the cached version of the user
$user = $userModel->getUserCached($userId);

// Get the user and bypass the cache
$user = $userModel->getUser($userId);
That's it. No extra code, no dealing with the cache yourself, you don't even add those underscores anywhere in the main code when we're calling getUser(). Add those underscores in the UserModel, and it will automatically handle a "Cached" version of the function. Period. Done.

What's that you say? You want to cache it for 600 seconds (10 minutes) instead of 300 seconds (5 minutes)? Ok, change this:
public function getUser__($userId) {
to this:
public function getUser__600($userId) {
And you're done. See, 300 seconds is the default -- so we didn't have to do anything special to use it. If you want anything else, you just type the number after the two underscores. Now, every time we call getUserCached() with a new userId, it will cache the result of the function and give it to straight from the cache every time you call the function in the future, all using the advanced RECache algorithm so it's crazy fast.

What about groups, you say? You want to be able to manually expire your cached user information by putting them all in a recache group? Just change this:
public function getUser__600($userId) {
To this:
public function getUser__600_users($userId) {
Now all the keys that this function caches will be put in the "users" group. Anywhere in your code, you can manually expire the "users" group using RECache (see the RECache guide above) and your webapp will start getting all new values from MySQL.

Do you need the result of that function to belong to more than one group? Just keep tacking group names on like this:
public function getUser__600_users_admins_people($userId) {

It's really that easy :) You can get the benefits of RECache without ever touching that library at all. Heck, you can get the benefits of RECache by adding as little as two extra characters to functions that you want to be cached. It just couldn't be easier!

Just to be clear, though, no matter what expiration time or group names you put in that function name, you still call it without all of that in there. If your getUser function looks like what I just posted, this is still what you use:
$firstUser = $userModel->getUserCached(1);
Unless you don't want the cached version for some reason, in which case you do this:
$firstUser = $userModel->getUser(1);

So when you decide to change how things are cached, you only change it in one place :)


And that, my friends, is it.
Just remember that Hydrogen is in Alpha, and there WILL be bugs -- both in my Hydrogen code, and in my examples in this guide. I am EXTREMELY thankful to anyone who helps me find them. An extra special shoutout to magik who found some of the most important bugs right away!

This guide will be updated every time I make a new full release, but if you want to stay on top of development, visit Hydrogen on GitHub at: http://github.com/TomFrost/Hydrogen


#40850 Hashing Myths

Posted by Kyek on 20 September 2010 - 07:54 AM

Hashing seems to be a bit of a hot topic on wdR recently, and all these threads have a common theme: People are treating hashing myths as cold hard facts. And, by extension, teaching others and themselves some really really bad practices and flat-out lies. So, let's talk about those!

Myth #1: When you hash something, you get a unique result that no other file or string or password can have.
Wroonnnnggggg. Let's attack this one with simple logic. Let's say your hash is 32 characters long. Now let's say you hash every possible 33-character string there is. You will have strings with matching hashes, or "collisions". It's simple logic -- there are far more combinations of 33-character strings than there are of 32-character strings, because for every 32-character string that exists, you can tack on every possible character to the end and make a bunch of 33-character strings. So, just making up some example numbers, if there are 90,000 33-character strings and 20,000 32-character strings, some of those 33'ers MUST have the exactly the same 32-character hash. The goal of hashing algorithms is to make collisions as rare as possible, but it is impossible to write to a hashing algorithm that has no collisions.


Myth #2: MD5 is insecure.
Wroonnnnggggg. MD5 is a less sophisticated (and therefore much faster) hashing algorithm than, say, SHA-256, but it is not insecure. An insecure hash would mean that the hash could be reversed -- or rather, that you could take a hash, and, using that and having no other information, produce a string that has the same hash. You cannot do that with MD5. In fact, the closest anyone has gotten to this is changing an existing, large file in a way that doesn't change the hash it already has. No one in the history of humankind has been able to produce a "reverse" MD5. This myth comes from the fact that MD5 is a common target of password-cracking attacks, which leads to our next myth...


Myth #3: MD5 is less secure for password hashing than other algorithms like SHA.
Wroonnnnggggg. There are exactly three attacks that can be used to find out someone's password if you have the hash of that password:
  • Hash database lookup. You go to a super-large online database of short words and their hashes, plug in the hash, and see if a short word with that hash has ever been submitted before. A common prevention for this is to salt your passwords.
  • Brute force. You run through all the words of a dictionary, hashing each one, to see if the hash matches what you have on hand. If that doesn't work, you just start hashing every possible combination of 5, 6, 7, or 8-character words to find a match. This takes for effing ever and rarely produces a result, and can easily be prevented with a salt.
  • Rainbow tables. This is a method of hashing and re-hashing the data you have to find similarities in the hashes of other words, which can eventually lead to finding the password. Salts have minimal effect on these attacks.
Now I'm gonna drop some knowledge into that noggin of yours. Every single one of these attacks, INCLUDING RAINBOW TABLES, can be applied to any hashing algorithm in existence. Those of you using SHA-1 or SHA-256 because you think it's less susceptible to attack than MD5 have been lulled into a very false sense of security. Database lookups, brute forcing, and rainbow table-based attacks are just as easy with those other algorithms as they are with MD5, and in many cases are EASIER to attack. Which brings us to our next myth...


Myth #4: Sophisticated, super-long hashes like SHA-256 are harder to attack because they're longer.
Wroonnnnggggg. Let's face it: the biggest threat to hash cracking attacks is the rainbow tables method. It is by far the most efficient tradeoff between processing time and storage space, and often times can find a password in under a minute if you have enough chains. But rainbow tables are not magical. That's the impression most people have because it's easier to believe that than to learn how they work, but seriously, the rainbow table attack isn't hard to understand. I suggest reading up on it if you have a spare 15 minutes.

So if you know how rainbow tables work, you know that the biggest weakness they have is hash collisions -- which, you'll remember from above, means more than one string that produces the same hash. So password hashing security is a tradeoff: You want an algorithm that has reasonably few collisions so that no two passwords are likely to generate the same hash, but you also want one that produces enough collisions to potentially send a rainbow table into an endless loop that can't be cracked. SHA-256 is horrible for this, because it's too good a hashing algorithm. You're extremely unlikely to get any collisions at all when using SHA-256 for a password (even a salted one), so using that makes your passwords -- salted or not -- easier to crack. SHA-256 is awesome for hashing large files. Not so awesome for passwords.

At the same time, though, you don't want to use a measly 32 or 48-bit hash, because collisions are extremely likely with those. You want to find a happy medium, which is around 128 bits. What's a fast 128-bit hashing algorithm? MD5.


Myth #5: SHA-1 is still better to use than MD5 because MD5 is more likely to be cracked.
Wroonnnnggggg. Again, you're being lulled into a false sense of security. Not only is SHA-1 160 bits (so you'll still get collisions, but fewer than MD5), hash databases and rainbow tables are just as easy and well-developed for SHA-1 as they are for MD5. This myth is one that is spread far and wide among developers who have never taken the time to research the facts. The fact that SHA produces a longer hash has little-to-no impact on the ability to store them in a database or produce rainbow chains with them. All it takes is a tiny bit more storage space, and that's true for any hashing algorithm in the world, whether it's 8 bits or 8 thousand bits. The length only helps for reducing collisions, which, past around 128 bits, doesn't help at all when you're hashing passwords rather than large files.


Myth #6: Hashing with multiple algorithms is more secure!
Wroonnnnggggg. No. Just no. Come on, now you're just pulling stuff out of your ass. I've seen this before: sha1(md5("Password")). That is ridiculous. You're feeding 128 bits into 160 bits, which is an easy easy crack. You can't make hashes more-hashy. You might add one more step to the process for someone cracking it, but it's going to end up with the same result. Don't guess at what's more secure, know what's more secure.


Myth #7: Using a global salt AND a user salt is more secure than using just a user salt.
There was a thread recently in which folks supported the idea of using a global salt in addition to a user salt, and I dismissed it as pointless. I was met with some fierce opposition, claiming that it makes passwords more secure if a hacker should be able to steal a copy of your database but not your source code. This is true, however the amount of extra security this offers is minimal at best. Here's why:

Out of our list of attacks on hashes that I wrote out earlier, there is only one attack that's made more difficult to crack by using a global salt, and that's the brute force attack -- the one that's already almost impossible to use successfully to begin with. Adding a global salt only makes this more-impossible-than-nearly-impossible. So I'll admit there is SOME benefit, but with the most common and most successful attack being rainbow tables, folks would be crazy to try a brute force attack anyway. They'd just rainbow table it, and the global salt won't make your password any more secure than a properly long user salt would. And hey, let's face it: If someone's able to steal a copy of your database, chances are that they can nab a copy of your source without too much trouble too.

If you're looking to protect against the case where someone gets a copy of your database, a far, far, far more effective solution would be to use symmetric encryption on your hashes. Choose a fast, simple symmetric encryption algorithm (RC4, Blowfish... your call), generate a key and store it somewhere in a configuration file. Use that to encrypt all your salted password hashes in the database. Then, when you pull them from the database, just decrypt them with that key when you read them. Now you have a hash that can't even be attacked with a rainbow table if someone happens to gain unauthorized access to either your database or your account on the server, and this is still effective if you distribute your software to the public. MUCH more effective than using a global salt.


So what are the best practices for password hashing?
The only two, good options for password hashing are MD5 and SHA-1, and you should let the language you're programming in dictate which one you use. For PHP (and most languages), MD5 is faster than SHA-1, so it's the better option. The key to making it secure is using a salt of the appropriate length. The sweet spot for securely storing a password that isn't susceptible to dictionary or brute force attacks AND is relatively safe from rainbow table attacks is to feed twice the number of bits of a hash into a new hash. So, for example, using MD5, you'd want to hash two MD5s. You can generate a 128-bit salt for this (which, for a lot of users, can take up some significant storage space), or you can generate a salt that's just a handful of characters long and hash that. My favorite method is this:
$finalHash = md5(md5($salt) . md5($password))
The benefit of that is that you're feeding twice the data of an MD5 into a new hash (hitting the collision sweet spot), it's ultra ultra portable because you can do this in any language including raw SQL, three MD5s is still a relatively fast process, and, for web use, you can have javascript hash the password before it gets sent to your server and you can still generate the final hash with that.

Whether you want to put the symmetric encryption layer on top of that is entirely up to you :)


So please, for the love of all that is holy, stop teaching these myths to other people! The world's programmers thank you :D




#13890 The Rep system

Posted by Kyek on 19 April 2010 - 01:42 PM

The Story:
A lot of people hate Rep, and they have darn good reason. Rep on forums creates a community of noobs whining for people to give them rep, which ends up destroying said forums.

Here, it's a little different.
We had a few folks whining for rep in their signatures, but a few PMs took care of it. Now we have a rep system that actually works, which is kindof cool. My hope was that the demographic here would be old enough to have a more mature approach to it, and so far, it's looking good.

Except for negative rep.
There are one or two folks here who think it's funny to click the negative rep button on people. Sometimes just doing it to a whole thread, sometimes following one particular person around and -repping them because they don't like their signature. Uncool.

So here's what I went done did.
Negative rep is no more. Admins/Mods/Mini-mods can still give it if they feel the need (which we.. probably never will), but members cannot. What you CAN do is give up to 10 positive rep a day, so if someone gives you a smart answer or helped you or was above-and-beyond patient with you, don't hesitate to hit the green plus below their post.


But what if someone's an asshole?
Then forget trying to give them -rep, because we don't want assholes here to begin with. Just click the Report button on their post and us mod-types will take a look at it, possibly deleting their post, and giving them a warning. If assholishness continues, we'll ask the person to leave or just straight-up ban them. These are all much more effective than negative rep in the first place.

But why? Why rep at all?
Because on a site like this, you need to know when someone really knows what they're talking about and when the person giving you instructions might not be so knowledgeable. It's a topic I discussed at length here. Some people, when they give you an answer, are giving you an answer based on years of experience and uncountable hours spent testing and experimenting. Other people, when they give you an answer, are telling you something they just thought up as they read your post. Those answers aren't always bad -- in fact, depending on the person, they can be quite good. But we're looking for a system to be able to show others a poster's experience level, and rep is the best solution we've found.

So in the future, to give people a better idea of the skill/experience level of the person they're talking to, Rep counts may be included in the information under your avatar when you post. But not if it creates a truckload of whiners -- in that case, we'll remove it and go back to the drawing board :)


#134687 [Mod post] Just a reminder about reputation

Posted by ArronH on 14 March 2013 - 11:43 AM

Just a friendly reminder that the reputation system is reserved for people who have posted truly helpful advice or feedback. It is not a Facebook Like button that you click when you think the post was funny or an opinion you agree with. I've noticed a lot of posts lately that have been getting a bunch of positive reputation that weren't at all helpful or beneficial to the discussion.

B) Carry on sexy people.


#157891 webdevRefinery is closed

Posted by Kyek on 16 October 2016 - 09:06 PM

Hi everyone,

 

Allow me to confirm what everyone assumes: webdevRefinery is officially closed.

 

This forum started in an age where scalability was expensive, new major languages appeared seemingly daily, and access to knowledgable and experienced help was exceptionally hard to find. webdevRefinery was created to share these things with a community of tech-interested folks of all levels, allowing them to grow together and receive instruction at a level that generally couldn't be found in schools.

 

I'm so proud of the success of this community. With a membership that started out with mostly high school, college, and recently-graduated folks looking to expand their tech knowledge, we ended up with technical executives, authors for well-known technical book publishers and tech blogs, engineers at Facebook and Google, and leaders in some of today's most loved startups. Lives were changed as a result of wdR, and mine was no exception.

 

Today, specialized technical help is much easier to find. Creating web applications with best practices in mind is far better documented. Sites like Reddit provide incredible niche communities surrounding individual technologies. Launching and scaling applications is now far cheaper, and the barrier to entry has never been lower. The problems that webdevRefinery set out to solve have been solved, and I'm absolutely thrilled both with the state of the software engineering community at large and the contributions this forum has made to it, however small.

 

Effective immediately, no new posts can be made on the forum, and signups are closed. I'll keep the posts accessible for as long as is feasible, but I place no guarantee on that. Please make sure anything important to you, including any private messages, is backed up. Access to this content may be removed without notice. wdR has been relatively activity-free for many months now, so I'm confident this decision won't have a major impact.

 

Thanks for your support and attention over these past 7 years. I hope wdR has helped you!

Kyek

 

PS- if you're looking to keep in touch, find me on Reddit, Twitter, GitHub, or LinkedIn.

 

PPS- Apologies to anyone who joined in 2016. I've deleted all topics and posts by members who joined on or after January 1 2016 due to the extreme spam problem.




#123244 So there's a prank war going on at work..

Posted by Kyek on 15 June 2012 - 09:26 AM

Holy crap you are fantastically evil.

The best work prank I've ever pulled: First job out of college, the devs (well, really, just me) and the designers used mac pros. I ripped into one of those greeting cards that plays music when you open it and took out the electronics. The "music" was some rap song that just didn't translate through a speaker that small, so it sounded like random garbage noise. The electronics were fairly simple -- two pieces of metal touch, sound comes out.

So I got a length of speaker wire -- you know, two wires in one. I soldered the wires to the the pieces of metal that switch the sound on. The wires were 20 feet long, so I could stand 20 feet away and the sound would start if I touched the other end of the wires together.

I mounted this device on top of the internal speaker on the lead designer's mac pro. I ran the wires out the back of the case through a vent, hid it in the other cables coming off his desk, ran it along the wall, up my desk, and rigged the wires underneath a stray napkin so all I had to do was lean over on to the napkin for the sound to start.

Then the fun began. Some days I would lean on the napkin once. Some days twice. Then I'd go a few days without.

"What? What's that sound? My volume's not even working."
"Hey I muted it and it's still playing that sound"
"What the HELL *IS* that? It sounds like a trash compactor!"
"I REBOOTED AND IT IS STILL HAPPENING WHAT THE HELL"
"Ok what the FUCK. My HEADPHONES are plugged in. They're PLUGGED. IN. How the fuck is the speaker still playing that fucking sound with my headphones plugged in?!"
"Ok what the fuck, this is a mac and I have a virus. How the fuck does that happen."
"HOLY SHIT I CAN'T DEAL WITH THIS SHIT WHAT THE FUCK"

Over the course of three weeks, he restarted more times than I could count, installed virus detection software, constantly killed all processes on his computer that he didn't recognize (creating more crashes, convincing him further that his computer was broken), and plugged and unplugged his headphones repeatedly over and over as though his computer didn't realize they were plugged in. After 3 weeks, he started to reformat when I finally admitted what was going on ;-). It is a small wonder that I was not fired.

So yeah. Do that :D.