Hi! Thanks for contributing!
For the best experience for everyone, there's a few guidelines we'd like everyone to follow.
This is a work-in-progress, so feel free to put ideas forward as to coding styles. The current guideline is based on suggestions from team members, and the recommendations from the PHP Framework Interop Group
- Files are UTF-8 encoded without a BOM (PSR-1)
- Only use long-syntax php tags:
<?php
and omit the closing tag at the end of a file. (PSR-1) - Don't use PHP versions which are newer than specified in INSTALLING.md - it won't run in production.
- Files contain a single class definition, and the file is named for the class. Alternatively, they should contain a script, not both. (PSR-1) All new classes should be appropriately namespaced.
-
Four spaces per level of indentation please.
-
Opening braces go on the next line for methods and classes, but on the same line for control structures (PSR-1).
-
Spaces before and after brackets, not inside (PSR-1).
Quick example:
class Foo extends FooBase implements IFoo
{
public function sampleFunction($a, $b = null)
{
if ($a === $b) {
bar();
} elseif ($a > $b) {
$foo->bar($arg1);
} else {
BazClass::bar($arg2, $arg3);
}
}
final public static function bar()
{
// method body
}
}
Soft limit of 80 chars, hard limit of 120 please. Lines longer than 130 chars make code review on GitHub nasty.
UpperCamelCase
for classes. (PSR-1)lowerCamelCase
for methods. (PSR-1)UPPER_CASE
for constants. (PSR-1)- Properties should have names which closely match their backing fields:
<?php
class Foo
{
private $foo;
public function getFoo()
{
return $this->foo;
}
public function setFoo($foo)
{
$this->foo = $foo;
}
}
- Interfaces should be called ISomething.
-
The ternary (
?:
) operator should only be used where appropriate - short expressions only please! -
Heredoc/Nowdoc should not be used for output - use templates. Extended SQL statements are OK, but please use appropriate boundary markers, such as
SQL
;
Some how-tos for some of the technologies and libraries we use.
We use PDO for database access, backed by a MariaDB database.
We have some database conventions too:
- Table names in our database are lowercase singular forms (
request
,user
,ban
) of the data they store. - The primary key is always a surrogate integer key called
PRIMARY
referencing a column calledid
, which is generated by theauto_increment
attribute. - Foreign keys should be named
<referencing table>_fk_<referencing column>_<referenced table>
. For example, therequest.reserved
column is a foreign key touser.id
, and the constraint and associated index are namedrequest_fk_reserved_user
. - The index and constraint for foreign keys should be named identically.
- Indexes should be named
<table>_idx_<columns...>
- Unique indexes should be named
<table>_uidx_<columns...>
- Check constraints should be named
<table>_check_<columns...>
- Fulltext indexes should be named
<table>_ft_<column>
Accessing the database is done either at an entity level through subclasses of DataObject
(preferred), or directly through PDO. The DataObject
class and it's subclasses implement the active record pattern.
Firstly, you'll need to grab a copy of the relevant database object from somewhere close by. If you're on a page, this is likely $this->getDatabase()
This is a PDO(ish) object which you can do what you need to with.
You can grab entities using the static methods defined on them:
$database = $this->getDatabase();
$request = Request::getById($id, $database);
All queries using data which is not 100% guaranteed to be safe and obviously safe (aka: hard-coded nearby) must be parameterised using prepare()
. Don't sanitise the data yourself, pass it as a parameter.
$statement = $database->prepare("INSERT INTO geolocation (address, data) VALUES (:address, :data);");
$statement->bindValue(":address", $address);
$statement->bindValue(":data", $data);
$statement->execute();
Please try to use named parameters, and strongly avoid positional parameters, as named parameters can't be broken by accidental reordering.
Transactions are handled automatically by the framework - every page load is run within it's own transaction. If you need to rollback the transaction, then you probably want to throw an exception, but it is possible to manually rollback the transaction. If you do roll it back manually, you need to take responsibility for handling that rollback correctly, and ensuring that future failed statements are also rolled back - aka start a new transaction immediately after rollback.
Create a new file in sql/patches
based on patch00-example.sql
. Please follow the naming scheme already in place, and
follow the instructions in the example code.
Pick the next sequential number - this will be your new schema version number. Name the file as patchXX-description.sql
,
where XX
is your version number, and description
is a very brief summary of the changes you're making.
Now, open your new file, and you'll see it's full of database code. No need to fear! There's two places you need to touch.
Update line 25, and put your version number in where the 0
is:
DECLARE patchversion INT DEFAULT 0;
Secondly, place all the database update statements you need to make around line 56, where this block is:
-- -------------------------------------------------------------------------
-- Developers - put your upgrade statements here!
-- -------------------------------------------------------------------------
-- ALTER TABLE foo DROP COLUMN bar;
-- -------------------------------------------------------------------------
Test your patch by running the entire file against your up-to-date database. It should apply the patch without question, if it does complain it's probably because a) your database is out of date, or b) you didn't pick the next sequential number. When it's applied, verify it's correctly made the changes you need in the database, and update the tool configuration to the correct schema version.
We use Smarty as a templating engine to handle all output to the web.
Smarty is very powerful, and allows us to put display logic directly within the templates, leaving our PHP code free of display code to cleanly handle the business logic.
No display code should be in the PHP files, everything should be in the templates, and as everything is contained in the templates, escaping for display can be done exactly at the point of display, and it's obvious where data isn't escaped for display. Therefore, no escaping should be done in the PHP code.
We use v3.1.14 of Smarty currently, and it's probably a better idea to get yourself familiar with it from their documentation. Useful sections:
There's not much to creating a new page, only two things need to happen:
- Create your page in the
\Waca\Pages
namespace as a class extending\Waca\PageBase
(or one of it's subclasses). Implement the two required methods:main()
andgetSecurityConfiguration()
.main()
should contain your page's logic.getSecurityConfiguration()
should return an instance of SecurityConfiguration, please try and use the static instances where possible. - Register your page in
\Waca\RequestRouter
with an appropriate URL slug.
You'll probably want to use templates, and remember not to use superglobals. Most of the utilities you need should be exposed in PageBase already, and this includes the database ($this->getDatabase()
)