Q:

Calling variable methods to handle ajax requests

I made a controller.php file to handle ajax requests, with $_POST parameters action and module
if (isset($_POST["action"])) {
    $action = strtolower($_POST["action"]);

    if ($action === "get_notification") {
        //  return session notification messages
       //...

    } elseif (isset($_POST["module"])) {
        require_once("libraries/class.module.php");
        $module = new Module;

        $moduleName = strtolower($_POST["module"]);

        //  check if module-name is valid
        if ($module->verify($moduleName)) {
            // load  $modulename class
            require_once("libraries/class. ".$moduleName. ".php");

            //  factory used to create instance of $moduleName
            require_once("libraries/class.factory.php");

            // note: moduleName class extends Module class
            $module = $Factory->create($moduleName);

            //  verify method of $moduleName verifies action/method parameter
            //  using method_exists and cross-checking against a permitted methods list

            if ($module->verify($action)) {
                $message = $module->$action();
                echo json_encode($message);

            } else {
                //  handle invalid requests
                echo json_encode(["0", "Invalid request received"]);
            }

        } else {
            //  handle invalid requests
            echo json_encode(["0", "Invalid request received"]);
        }

    } else {
        //  handle invalid requests
        echo json_encode(["0", "Invalid request received"]);
    }

} else {
    //  handle invalid requests
    echo json_encode(["0", "No request received"]);
}

But I read this post on Stack Overflow which advises against using variable methods.  Should I replace mine with a switch case that checks each $action and calls corresponding method. 

A:

First and foremost, I would worry about getting rid of that awful code nesting you have.  This nesting yields increased cyclomatic complexity which tends to lead to more buggy/fragile code and code that is harder to maintain. 
So, at a minimum, think of re-writing your code like this:
if(empty($_POST['action'])) {
    echo json_encode(["0", "No request received"]);
    exit;
}

$action = strtolower($_POST['action']);
if($action === 'get_notification') {
    //  return session notification messages
    exit;
}

if(empty($_POST['module'])) {
    echo json_encode(["0", "No request received"]);
    exit;
}

require_once("libraries/class.module.php");
$module = new Module;
$moduleName = strtolower($_POST["module"]);

if(!$module->verify($moduleName)) {
    echo json_encode(["0", "No request received"]);
    exit;
}

// rest of code, now without nesting

Now, with that consideration out of the way.   Let's get to your main question.  I am bothered by the fact that you call your script controller.php when what it is really doing is routing.   Your "modules" seem to really be your controllers and your "routing" is done in a very procedural fashion. 
What you are really missing here is the concept of a router which can authoritatively map the request to a controller which can handle the request and to which request execution is then handed off.  This is a concrete application of the Inversion of Control principle.   You are trying to keep control of request execution at the level of this script, which means you will have to continue to increase the complexity of this script as you add new use cases. 
So to answer you question about using a switch.  I would recommend that, rather than thinking about variable methods or switch as your alternatives, you think about proper routing and inversion of control as your alternative. 
Envision a top level script here which looks like:
/**
 * An object that encapsulates the request data - URI, GET/POST data, etc. 
 * that you can pass through your system
 */
$request = new Request();

/**
 * An object which can inspect the request and instantiate a controller to 
 * fulfill it.  It then hands off script control to the controller. 
 */
$router = new Router();
$controller = $router->getController($request);
$controller->execute();

That's it.   That's all that needs to be in this top-level script file (with the exception of any script bootstrapping) if you embrace the concepts of routing and inversion of control. 
The Router of course would need to hold the logic mapping request properties to controllers - oftentimes with that mapping be captured in a config file.  That might look something like this (here I am using JSON representation, but this could take many forms):
{
    "action": {
        "get_notification": "NotificationController",
        "other_action": {
            "module": {
                "some_module": "OtherActionSomeModuleController",
                "other_module" "OtherActionOtherModuleController",
                ...
             },
             "other_module: { ... },
        },
        ...
    }
}

This could, for example, just map various actions, modules, etc.  to different controller class names.  Each of these controllers would need to be able to accept a Request object as a dependency and know what to do with it when execute() is called.  The router would instantiate the appropriate class and then pass the Request to it. 
As a developer wanting to add new action/behaviors to the system, all you would need to do is build a new controller for it, and then add the new controller to the route map as appropriate.  You would not need to change one bit of code in the rest of the application (i.e. the top-level routing script) unless you were introducing a non-compatible change for some reason (i.e. your whole route map would need to change). 
And in case you were wondering what happened to all the handling of invalid requests, that would be something that would live with the Router.   If it cannot successfully route the request, it could just instantiate an InvalidRequestController or similar to fulfill the request. 
So your getController() method might look like:
// assume the config shown above has been loaded into a multi-dimensional array
// in $map property. 
public function getController(Request $request) {
      if(!array_key_exists($request->action, $this->map) {
          return new InvalidRequestController($request);
      }
      $action = $this->map[$request->action];
      if(!is_array($action)) {
          return new {$action}($request);
      }
      if(!array_key_exists($request->module, $action)) {
          return new InvalidRequestController($request);
      }

      return new {$action[$request->module]}($request);
}

A few other considerations:

You really ought to consider using an autoloader (ideally a PSR-4 compliant one).   Having a bunch of include/require statements littered throughout your code to load classes is really a very old way of doing things. 
You really should consider sending appropriate HTTP error codes when your application receives mal-formed requests (i.e. all of your "invalid request" conditionals). 
What is the purpose of having both action and module if you only have a single action you are listening for and everything else goes to modules?   It seems like this could perhaps be just a single layer of routing as opposed to the nesting routing construct this code implies. 
I don't know what $module->verify() does, but is it a potential security concern with passing unvalidated POST data deeper into the application? 
When you have variable interpolation like this $module->$action(), consider using braces to eliminate ambiguity - $module->{$action}() (as you could theoretically have $module->{$action()} usage). 
There are a bunch of bad coding resources/examples out there on the web (especially for PHP) that can lead you to bad programming habits, or that are out of touch with modern development approaches.   I will leave you with what I consider to be the best resource to someone learning to work with PHP - http://www.phptherightway.com/ 

