Use try/catch as if/else? - php

I have a class that accepts user ID when instantiated. When that ID does not exist in the database, it will throw an exception. Here it is:
class UserModel {
protected $properties = array();
public function __construct($id=null) {
$user = // Database lookup for user with that ID...
if (!$user) {
throw new Exception('User not found.');
}
}
}
My client code looks like this:
try {
$user = new UserModel(123);
} catch (Exception $e) {
$user = new UserModel();
// Assign values to $user->properties and then save...
}
It simply tries to find out if the user exists, otherwise it creates a new one. It works, but I'm not sure if it's proper? If not, please provide a solution.

No this isn't proper, try catch blocks should be used to handle code where anomalous circunstaces could happen. Here you're just checking if the user exists or not so the best way to achieve this is with a simple if else.
from wikipedia definition of programing expception:
"Exception: an abnormal event occurring during the execution of a
routine (that routine is the "recipient" of the exception) during its execution.
Such an abnormal event results from the failure of an operation called by
the routine."

As #VictorEloy and #AIW answered, exceptions are not recommended for flow control.
Complementing, in your case, I would probably stick with a static method for finding existing users that would return an instance of UserModel if it finds, or null in case it does not. This kind of approach is used in some ORM libraries like Active Record from Ruby on Rails and Eloquent from Laravel.
class UserModel {
protected $properties = array();
public static function find($id) {
$user = // Database lookup for user with that ID...
if ($user) {
return new UserModel($user); // ...supposing $user is an array with its properties
} else {
return null;
}
}
public function __construct($properties = array()) {
$this->properties = $properties;
}
}
$user = UserModel::find(5);
if (!$user)
$user = new UserModel();

It is debatable, but I'm going to say this is NOT proper. It has been discussed before. See
Is it "bad" to use try-catch for flow control in .NET?

That seems like a proper behaviour as long as it doesn’t throw when $id is null (that way, you can assume a new one is to be created).
In the case of the code that uses your class, if you’re going to be inserting it with that same ID later, just insert it with that ID to begin with without checking – it could be possible, though unlikely, that something happens between the check and the insertion. (MySQL has ON DUPLICATE KEY UPDATE for that.)

Related

Calling methods on object gets 'call to member function on array' error

I inherited this project from my predecessor, and he was way overqualified. A lot of stuff he wrote goes over my head. But as far as vanilla php goes, I'm pretty confident, and can't for the life of me figure out why the application thinks the object I created is an array. Maybe I don't actually know anything. You tell me.
use via\zoom\Bulletin;
use via\zoom\DatabaseConnection;
require_once('includes/config.php');
require_once(CORE .'sql.php');
require_once(CORE . 'model.php');
require_once(CORE . 'bulletin.php');
// If we've passed the validation step we can guarantee we have a valid $active_user
validate();
//run if a page deletion has been requested
if (isset($_GET['delpage'])) {
$del = $_GET['delpage'];
$bulletin = new Bulletin;
$bulletin = Bulletin::get($del);
if(!empty($bulletin))
{
$bulletin->delete();
/*
So.
For some reason, the above object is cast as an array.
If you try to cast it as an object, it defaults to stdClass.
On the left we have a method complaining that it can't work outside of its class. Hard stop, array to method exception.
On the right we have an object with all the right data, but set to the wrong class, so it can't find the delete method at all. Hard stop, undefined method exception.
*/
//this is the workaround, pulled the script straight from the delete method in the model class
/*$dbh = DatabaseConnection::get();
$query_string = "DELETE FROM brochure_generator_bulletin WHERE id = $del";
try {
$dbh->query($query_string);
//return true;
} catch (\Exception $e) {
//return false;
}*/
}
header('Location: bulletins');
exit();
}
Here's the get method from the Bulletin class, extends Model--
public static function get( ...$ids )
{
$matches = parent::get( ...$ids );
foreach( $matches as &$match )
{
$match->content = json_decode( $match->content );
}
return $matches;
}
And here's the delete method from the Model Class:
public function delete()
{
if (isset($this->id)) {
$dbh = DatabaseConnection::get();
$query_string = "DELETE FROM {$this->table_name} WHERE id = \"{$this->id}\"";
try {
$dbh->query($query_string);
return true;
} catch (\Exception $e) {
return false;
}
}
return false;
}
What am I missing? Is he using a framework I'm not familiar with? I'm utterly grasping at straws here, and at this point my options are grab all the method scripts and stick them where they need to be inline, or just starting over from the ground up.
You don't need to create a new Bulletin object before using the static get() method, so you can remove this:
$bulletin = new Bulletin;
That $bulletin variable is immediately overwritten by the next line anyway.
$bulletin = Bulletin::get($del);
get() takes one or more ids and returns an array of one or more corresponding objects. You're giving it one id and expecting one object back, but it's still going to return that object inside an array. You just need to get the object out of the array so you can call its delete method.
if(!empty($bulletin))
{
$bulletin = reset($bulletin); // get the first item in the array
$bulletin->delete();
You could also review the model and see if it has a different method that returns a single object rather than an array of objects.

Is there a way to centrally preprocess all logs created by monolog?

I am currently working on a big application that uses monolog for logging and was asked to hide any sensitive information like passwords.
What I tried to do, was extending monolog so it would automatically replace sensitive information with asterics, but even though the data seems to be altered, in the end the original text gets logged.
use Monolog\Handler\AbstractProcessingHandler;
class FilterOutputHandler extends AbstractProcessingHandler
{
private $filteredFields = [];
public function __construct(array $filteredFields = [], $level = Monolog\Logger::DEBUG, $bubble = true)
{
$this->filteredFields = array_merge($filteredFields, $this->filteredFields);
parent::__construct($level, $bubble);
}
protected function write(array $record)
{
foreach($record['context'] as $key=>$value){
if(in_array($key, $this->filteredFields)){
$record['context'][$key] = '*****';
}
}
return $record;
}
}
And when I initialize my logger I do this:
$logger->pushHandler(new FilterOutputHandler(['username', 'password']));
$logger->debug('Sensitive data incoming', ['username'=> 'Oh noes!', 'password'=> 'You shouldn\'t be able to see me!']);
I also tried overridding the handle and processRecord methods of the AbstractProcessingHandler interface but in vain. Can this be done in monolog?
Looks like I was trying the wrong thing.
Instead of adding a new handler to my logger, I had to add a new processor by using the pushProcessor(callable) method.
So, in my specific use case, I can add filters to my context like this:
function AddLoggerFilteringFor(array $filters){
return function ($record) use($filters){
foreach($filters as $filter){
if(isset($record['context'][$filter])){
$record['context'][$filter] = '**HIDDEN FROM LOG**';
}
}
return $record;
};
}
And later I can add filters simply by
(init)
$logger->pushProcessor(AddLoggerFilteringFor(['username', 'password']));
...
(several function definition and business logic later)
$logger->debug('Some weird thing happened, better log it', ['username'=> 'Oh noes!', 'password'=> 'You shouldn\'t be able to see me!']);

How to test a class which depends on an Eloquent model with relationships?

What is the best way to write a unit test for a class which depends on an Eloquent model with relationships? E.g.
real object (with database). This is easy, but slow.
real object (no database). I can create a new object but I can't see how to set the related models without writing to the database.
mock object. I run into issues using Mockery with Eloquent models (e.g. see this question).
other solutions?
context: I'm using Laravel with Authority RBAC for access control. I want to find the best way to test my access rules in a unit test. Which means I need to pass the user dependencies to Authority during the test.
If you're writing unit tests, you shouldn't ever use a database. Testing against a database would be considered an integration test. Check out Roy Osherove's videos.
To answer your question, (and not having delved into Authority RBAC, I'd do something like this:
// assuming some RBAC class
SomeRBACClass extends RBACBaseClass {
function validate(UserClass $user) {
if (!$roles = $user->getRoles())
{
return false;
}
$allowed = array('admin', 'superadmin');
foreach ($roles as $role) {
if (in_array($role->name, $allowed)) {
return true;
}
}
return false;
}
}
SomeRBACClassTest extends TestCase {
function test_validate_WhenPassedUser_callsGetRolesOnUserWithNoArgs()
{
$rbac = new SomeRBACClass();
$user = Mockery::mock('UserClass');
$user->shouldReceive('getRoles')->once()->withNoArgs();
$rbac->validate($user);
}
function test_validate_getRolesOnUserReturnsCollectionOfRoles_CallsGetAttributeWithNameOnFirstRole() {
$rbac = new SomeRBACClass();
$user = Mockery::mock('UserClass');
// assuming $user->getRoles() returns a collection
$collection = new \Illuminate\Support\Collection(array(
$role1 = Mockery::mock('Role'),
$role2 = Mockery::mock('Role'),
));
$user->shouldReceive('getRoles')->andReturn($collection);
$role1->shouldReceive('getAttribute')->once()->with('name');
$rbac->validate($user);
}
function test_validate_getAttributeWithNameOnRoleReturnsValidRole_ReturnsTrue() {
$rbac = new SomeRBACClass();
$user = Mockery::mock('UserClass');
// assuming $user->getRoles() returns a collection
$collection = new \Illuminate\Support\Collection(array(
$role1 = Mockery::mock('Role'),
$role2 = Mockery::mock('Role'),
));
$user->shouldReceive('getRoles')->andReturn($collection);
$role1->shouldReceive('getAttribute')->andReturn('admin');
$result = $rbac->validate($user);
$this->assertTrue($result);
}
This is not a thorough example of all the unit tests that I would write, but it's a start. E.g., I would also validate that when no roles are returned, that the result is false.

Is this correct use of Exception handling in PHP / Symfony2

I'm creating a service to fetch some user data
class ExampleService{
// ...
public function getValueByUser($user)
{
$result = $this->em->getRepository('SomeBundle:SomeEntity')->getValue($user);
if (!$result instanceof Entity\SomeEntity) {
throw new Exception\InvalidArgumentException("no value found for that user");
}
return $result;
}
}
Then in my controller I have
// ...
$ExampleService = $this->get('example_serivce');
$value = $ExampleService->getValueByUser($user);
Should I be using an exception here to indicate that no value was found for that user in the database?
If I should, how do I handle what is returned from $ExampleService->getValueByUser($user) in the controller - let's say I just want to set a default value if nothing is found (or exception returned)
Here is how I do it. Let's use a user service and a controller as an example. It's not an exceptional condition in the service layer — it just returns the result without checking it:
class UserService
{
public function find($id)
{
return $this->em->getRepository('UserBundle:User')->find($id);
}
}
But in the controllers layer I throw an exception if the requested user not found:
class UserController
{
public function viewAction($id)
{
$user = $this->get('user.service')->find($id);
if (!$user) {
throw $this->createNotFoundException(
$this->get('translator')->trans('user.not_found')
);
}
// ...
}
}
Where you want to handle the exception is kind of up to you, however I would handle it in the controller (and throw it in the model). I usually try to call a different template if there is an error so as to avoid a bunch of conditionals, but sometimes you just have to put extra logic in your template instead.
Also, you have to ask yourself if this is really an exceptional condition - it might be easier to return null and handle that return value in your controller. I can't really tell from the data objects (value, service, and user) whether this is something that will happen all the time or not.

Mysqli connection trying with different users

I'm trying to create a PHP class extending mysqli that is capable of connecting with another user if the connection fails. It is probably easier to explain with code:
public function __construct() {
$users = new ArrayObject(self::$user);
$passwords = new ArrayObject(self::$pass);
$itUser = $users->getIterator();
$itPass = $passwords->getIterator();
parent::__construct(self::$host, $itUser->current(), $itPass->current(), self::$prefix.self::$db);
while($this->connect_errno && $itUser->valid()){
$itUser->next();
$itPass->next();
$this->change_user($itUser->current(), $itPass->current(), self::$prefix.self::$db);
}
if($this->connect_errno)
throw new Exception("Error", $this->connect_errno);
}
$user and $pass are static variables containing arrays of users and passwords.
If the first user fails to connect, I try with the next one.
The problem here is with $this->connect_errno. It says it cannot find Mysqli.
Is there any solution to this or should I create a Factory class?
Not sure why it doesn't find the object (hint, anybody?), however you can still use mysqli_error() for error handling, as shown in this article.
while(!mysqli_error($this) && $itUser->valid()) {
// (...)
}
and
if(mysqli_error($this)) {
throw new Exception(mysqli_error($this), mysqli_errno($this));
}

Categories