As the code is "fixed" (i.e. not driven from a database) then there is no need to pump into an array (and all the processing/memory overhead that it requires. So yes, it can be improved.
But there are many options depending on how much the project will grow.
Simplest
The simplest would be simple "if" statements, or a switch. I'd start there to keep it simple.
More Complex
You say other projects have different pages / controllers - but there is a reason. And as you're asking for improvements, especially if you're expecting the project to grow to such as extent that you're looking for optimizations, then you really should consider these reasons (and split into files).
At the other end of the scale, you can split all the calls into files/classes and auto-load the files/classes.
This way you only execute the code you need (smaller file sizes), is very modular and easy to work on collaboratively. And if you add a new action, you don't need to modify the index or array - you only modify the action file you're working on.
Example (vastly simplified from a project I'm currently working on with this approach):
1) Create a "baseAction" base class the all actions will extend from. You can add common features such as cleaning/pre-processing parameters, logging, validating headers etc.
abstract class baseAction {
protected $aExpectedParams = [];
protected $aParams = [];
protected $validParams = true;
function __construct() {
foreach (self::$aExpectedParams as $name=>$aParam) {
if (isset($_GET[$name]))
if ($aParam['type'] == 'string') {
self::$aParams[$name] = $_GET[$name];
} elseif ($aParam['type'] == 'int') {
self::$aParams[$name] = (int)$_GET[$name];
}
} elseif ($aParam['required']) {
self::$validParams = false;
}
}
}
// This is the called function
abstract function execute();
}
2) Create the "action" classes, by extending the base Action. Save these in individual files (so others can collaborate on the project without interfering).
// put in 'actions/userLogin.php
class userLogin extends baseAction {
protected $aExpectedParams = [
'login' => ['type' => 'string', 'required' => true]
'password' => ['type' => 'string', 'required' => true] // NOTE: you should never actually pass password unencrypted through "get" as they'll get stuck in user logs!
];
public function execute() {
// Do Whatever
}
}
.
// put in 'actions/displayUsersTable.php
class displayUsersTable extends baseAction {
public function execute() {
// Do Whatever
}
}
3) Create an autoloader to pull in those individual files.
function myAutoloader($className) {
if (file_exists(__DIR__ . '/actions/' . $className . '.php')) {
require_once(__DIR__ . '/actions/' . $className . '.php');
}
}
spl_autoload_register ('myAutoloader');
4) Then your index.php is as clean as
$action = $_GET['action'] ?? '';
if (strlen($action) > 0 && class_exists($action) && method_exists($action, 'execute')) {
$oAction = new $action();
$oAction->execute();
} else {
// Oopsie
}
(Notes on this last snippet: the "class_exists" triggers the auto-loader. the "method_exists" is to check someone hasn't requested a common php class such as "object"; if you're being safer you should namespace this or add extra validation. This is just an example!)