I'm really confused as to what's the correct way of handling a class-chained exception.
For example,
I have a factory class,
GameFactory
<?php
namespace App\GameFactory;
class GameFactory
{
public static function generateGame($game)
{
if (!GameEnum::tryFrom($game)) {
throw new InvalidGameException(.....);
}
return new SuperMario();
}
}
and I have a service class which will then be called by a controller class.
GameService
<?php
namespace App\Service;
class GameService
{
public function runGame()
{
$factory = GameFactory::generateGame($this->game);
// do logic...
}
}
GameController
<?php
namespace App\Controller;
class GameController
{
public function run($game)
{
$service = new GameService($game);
$service->runGame();
}
}
My question is, do I need to catch it in GameService, and then in GameController?
or is catching it in the Initiator-Class(GameController) enough?
I would suggest two approaches:
1. You should always use try...catch block for such scenarios. This can help with the encapsulation and makes sure that other classes (parent or else) are not responsible for errors thrown by the children or friend classes.
2. Think of the Exception Stack to unfold in a one direction only. If you catch the exceptions of children in parent classes, then the log entries will try to make (a circle) and loop back to the caller class ( which is parent in our case). So, you should throw (display or manage those) in their respective classes.
Note:
Please note that throw, catch, logging and displaying or rendering are different from one another.
Related
I have an abstract question for you.
Question:
How can a subclass that extends an abstract class register itself to the abstract class or another class?
Problem:
Guess we have a module master named ModuleMaster and maybe someone else writes another modules to handle a specific problem without modifying the master class and named it ModuleA. For that reason we want to implement a dynamic loading of problem solutions.
My idea:
File: Extensions.php:
namespace Project\Extensions;
class Extensions
{
public function getLoadedModules()
{
var_dump(ModuleMaster::LOADED_MODULES);
}
}
File: Modules\ModuleMaster.php:
namespace Project\Extensions\Modules;
abtract class ModuleMaster
{
public const LOADED_MODULES = array();
}
File: Modules\ModuleA.php:
namespace Project\Extensions\Modules;
class ModuleA extends ModuleMaster
{
}
I hope you understand what I mean and can help with that abstract problem.
This is very strange to use. It's probably a better design to have an external registry for your module. But I think you're asking for this:
File: Extensions.php:
namespace Project\Extensions;
use \Project\Extensions\Modules\ModuleMaster;
class Extensions
{
public function getLoadedModules()
{
var_dump(ModuleMaster::getLoadedModules());
}
}
File: Modules\ModuleMaster.php:
namespace Project\Extensions\Modules;
abstract class ModuleMaster
{
public static function getLoadedModules() {
$parent = self::class;
return array_values(array_filter(\get_declared_classes(), function ($class) use ($parent) {
return in_array($parent, class_parents($class));
}));
}
}
File: Modules\ModuleA.php:
namespace Project\Extensions\Modules;
use \Project\Extensions\Modules\ModuleMaster;
class ModuleA extends ModuleMaster
{
}
Example use:
$e = new \Project\Extensions\Extensions;
$e->getLoadedModules();
Example result:
array(1) {
[0]=>
string(34) "Project\Extensions\Modules\ModuleA"
}
Please note that the code only works if all your class files are included into the context before running getLoadedModules(). PHP won't know your class exists if it is not already loaded into the context.
You seem to be trying to create a capability in the parent class which is not required or inappropriate in the child class. This is the opposite of inheritance and hence an anti-pattern. Further, even though it might be considered as an extension of reflection, you are trying to put runtime data in a class - that's not what classes are for.
You've also not explained in any way that I can understand why you want to do this.
I suspect you really want to implement a factory, strategy or a registry object.
This is a follow-up to my previous question about resolving the diamond issue in php.
As I state in that question, I resolve my problem by using traits and passing the instance of the class to the method of the trait. Such as:
trait SecurityTrait
{
public function beforeExecuteRouteTrait($controller, Dispatcher $dispatcher)
{
// Do something that makes use of methods/members of the controller
}
}
class AppController extends Controller
{
use SecurityTrait;
public function beforeExecuteRoute(Dispatcher $dispatcher)
{
return $this->beforeExecuteRouteTrait($this, $dispatcher);
}
}
However, I am still uncomfortable with this as I don't think this is how traits are really supposed to be used. In my reading I haven't found any way in which to access class members in traits (make $this inside a trait refer to the class using it). Is this possible? Or is there another way to implement a similar behaviour?
After reading some of the answers...
Previously I thought I had received errors when using $this->... inside the trait and this led me to believe the trait could not access anything to do with the underlying class. After reading the answers I tried altering my code to use $this->... inside a trait again and it works - which means a typo several weeks ago has given me far too much headache...
The example given previously now looks like this
trait SecurityTrait
{
public function beforeExecuteRoute(Dispatcher $dispatcher)
{
// Do something that makes use of methods/members of the controller
}
}
class AppController extends Controller
{
use SecurityTrait;
}
Much cleaner and more easily understandable but provides the same functionality.
If you use a trait inside a class then that trait has full access to all class's members and vice versa - you can call private trait methods from the class itself.
Think of traits as code that literally gets copy/pasted into the class body.
For example:
trait Helper
{
public function getName()
{
return $this->name;
}
private function getClassName()
{
return get_class($this);
}
}
class Example
{
use Helper;
private $name = 'example';
public function callPrivateMethod()
{
// call a private method on a trait
return $this->getClassName();
}
}
$e = new Example();
print $e->getName(); // results in "example"
print $e->callPrivateMethod(); // results in "Example"
In my view referencing classes in traits is not the best way to use them but there's nothing stopping anyone from doing it.
No, that's exactly what Traits are for. Your class already extends a class so you can't inherit the methods and variables of any other classes.
Think of a Trait like copy/paste for code execution. When a class includes a Trait, it's just as if you had written all that code into the class itself.
In my Apigility project I have different Rest resources, all of them extends my class ResourseAbstract and in there I extend the AbstractResourceListener as Apigility needs.
So for example my resource User:
<?php
namespace Marketplace\V1\Rest\User;
use ZF\ApiProblem\ApiProblem;
use Marketplace\V1\Abstracts\ResourceAbstract;
class UserResource extends ResourceAbstract
{
public function fetch($id)
{
$result = $this->getUserCollection()->findOne(['id'=>$id]);
return $result;
}
}
And ResourceAbstract:
<?php
namespace Marketplace\V1\Abstracts;
use ZF\Rest\AbstractResourceListener;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use ZF\ApiProblem\ApiProblem;
class ResourceAbstract extends AbstractResourceListener implements ServiceLocatorAwareInterface {
}
Now, I need to run a function each time an http request is made, if I query /user in my browser the UserResource class will get instantiated and so the ResourceAbstract, my "solution" to get something to run on each call was to use a constructor inside ResourceAbstract, and this "works":
function __construct() {
$appKey = isset(getallheaders()['X-App-Key']) ? getallheaders()['X-App-Key'] : null;
$token = isset(getallheaders()['X-Auth-Token']) ? getallheaders()['X-Auth-Token'] : null;
//some code
return new ApiProblem(400, 'The request you made was malformed');
}
The thing is I need to return an ApiProblem in some cases (bad headers on the http request), but as you know constructor function does not return parameters. Another solution will be to thrown an exception but in Apigility you are supposed to ise ApiProblem when there is an api problem. Is the constructor approach correct? How will you solve this?
Throwing an exception would be a solution, as long as you catch it on the parent portion of the code.
Are you using the ZEND MVC with your apigility project ?
If yes, you could consider hooking up a call that will be executed before the MVC does the dispatching.
If you want to look on the feasability of that approach, you can check that question asked on stackoverflow : Zend Framework 2 dispatch event doesn't run before action
I've not used this library, however it looks as if you can attach a listener to 'all' events by either extending the 'dispatch' method or adding your own event listener with high priority. The controller then listens for the returned 'ApiProblem'.
Attaching a listener is probably a better idea, in your custom class extending AbstractResourceListener (or from within it's service factory) you can then attach the event.
abstract class MyAbstractResource extends AbstractResourceListener
{
public function attach(EventManagerInterface $eventManager)
{
parent::attach($eventManager);
$eventManager->attach('*', [$this, 'checkHeaders'], 1000);
}
public function checkHeaders(EventInterface $event)
{
$headers = getallheaders();
if (! isset($headers['X-App-Key'])) {
return new ApiProblem(400, 'The request you made was malformed');
}
if (! isset($headers['X-Auth-Token'])) {
return new ApiProblem(400, 'The request you made was malformed');
}
}
}
The above would mean that any event triggered would first check if the headers are set, if not a new ApiProblem is returned.
I have an exception in application/core named prefix_Exceptions.php with the same class name. I try to throw this exception from a controller and I get:
Fatal error: Class 'prefix_Exceptions' not found in user_controller.php
In application/core/prefix_Exceptions.php:
<?php
class prefix_Exceptions extends CI_Exceptions {
public function __construct() {
parent::__construct();
}
public function test() {
echo "This is a test.";
}
}
And in application/controllers/user_controller.php:
<?php
class User_Controller extends CI_Controller {
public function view($id = '0') {
$this->load->model('user_model');
$u = $this->user_model->getUser($id);
if (!isset($u)) {
$this->exceptions->test(); // ???
}
echo "Test: $u";
}
}
Oh, and my prefix is set to prefix_:
$config['subclass_prefix'] = 'prefix_';
I've read about a dozen threads on this issue and none of them fix my exception so that it can be thrown by the controller.
The main reason your code is not working, is (as the error message suggests): your prefix_invalid_user.php is never loaded. CI does not know to load this file, as you are not following the required file naming scheme.
If you want to extend a built-in class, you have to use the same class name, except you change the prefix from CI_ to MY_ (or whatever prefix you set in your config).
To extend the class CI_Exceptions you would have to name it MY_Exceptions and save that php file in /application/core/MY_Exceptions.php. Then, and only then, will CI auto-load it for you.
However you should also know that CI's exceptions class isn't actually for throwing exceptions (the name is misleading, but CI_Exceptions handles error reporting). As you can see in the /system/core/Exceptions.php file, the CI_Exceptions class does not extend PHP's native Exceptions class, which is necessary to create custom, throwable exceptions.
If you want custom, throwable exceptions you have to create your own wrapper for them, and load/autoload it as a library.
Edit:
As per the OP's request, I'm adding the other half of the solution, which was to simply fetch the class object from CI's innards. For this, we can use the load_class function, which will return our class object if it has been instantiated, and if not, it will instantiate and return it.
$foo = load_class('Exceptions', 'core', $this->config->item('subclass_prefix'))
Then we can access the methods of our custom Exceptions class as so:
$foo->someMethodName();
I am searching for a architectural solution in instantiating different child classes based on an object type or extending Base class with the methods of child classes.
To give an example:
There is a base class User and several child classes Partner, Client, Moderator which have specific methods an their own constructors.
When I am calling
$user = new User($userid);
I want User class
class User
{
public function __construct($userid) {
self::initDB();
if ($this->isPartner()) {
//extend this class with the methods of "Partner" child class and run "Partner" class constructor
}
if ($this->isClient()) {
//extend this class with the methods of "Client" child class and run "Client" class constructor
}
if ($this->isModerator()) {
//extend this class with the methods of "Moderator" child class and run "Moderator" class constructor
}
}
}
To return me an object with all of the methods depending on what roles does user have.
I know my logic is broken somewhere and the example I provided is wrong. But the only solution I see now is to build one giant class that has all of the methods for all of the roles - which looks like a mess.
First of all, your database logic should be totally separate from your domain objects (User, etc). Otherwise you are violating the single responsibility principle (SRP).
Set up your classes something like the following (base class User and multiple subclasses):
class User
{
private $id;
// getters and setters go here
}
class Moderator extends User {}
class Partner extends User {}
// etc
Then, create some sort of UserManager class that implements an interface that looks like the following:
interface UserManagerInterface {
function loadUserById($id);
}
The implementation of that method should load the passed user id's information from the database, look at what type it is (partner, moderator, etc) and then instantiate the appropriate class and hydrate the appropriate information.
The problem is that you cannot call new User and get anything other than a User object.
This sounds like the perfect use-case for the factory pattern.
The simplest form of this uses a static method to invoke the correct constructor.
So you could have code like this:
class User {
public static function create($userid) {
// get user from the database
// set $isPartner to true or false
// set $isClient to true or false
// set $isModerator to true or false
if ($isPartner) {
return new Partner($userid);
} elseif ($isClient) {
return new Client($userid);
} elseif ($isModerator) {
return new Moderator($userid);
} else {
return new User($userid);
}
}
}
You can then call User::create($userid) to get the appropriate object.
If your code is appropriately structured, it may well be possible to have code along the lines of Lusitanian's answer (fleshed out) that would do a better, more flexible job.