Accept two different model instances as a function argument php - php

I'm writing a helper function to check whether any address information is present in my database by checking if any of the relevant fields don't equal null.
So, I have two models which I need to accept in the function as an argument.
App\Customer
App\Supplier
The variable passed can be either one.
For a function that should only accept one kind of model, I can just do this:
function check_for_address_info(App\Customer $customer) {
// other unrelated stuff
}
Is there a way to accept both models, or do I have to manually check it in the function by doing something like this:
function check_for_address_info($param) {
if(!is_a($param, 'App\Customer' || !is_a($param, 'App\Supplier')) {
// not an instance of either model
return false;
}
// do stuff as normal
}
Any ideas on how to accept two different models as a function argument?
I'm on Laravel 5.8.

There is two approaches, if it makes sense inheritance wise, you can extend a parent model and declare it as the type of the parameter. This will be checked on run time and provide an error if you pass the wrong type to the method.
public class Profile extends Model {
}
public class Customer extends Profile {
}
public class Supplier extends Profile {
}
function check_for_address_info(App\Profile $customerOrSupplier) {
}
In weak typed languages, it is common to have parameters that are generic. The way PHP solve this problem, is you can declare it in PHP Doc blocks. This will not check types on run time and is mostly for typehinting, documentation and static analysis tools.
/**
* #parameter \App\Customer|\App\Supplier $customerOrSupplier
**/
function check_for_address_info($customerOrSupplier) {
}

Related

How can I define a replicate behavior for the execute method without changing all the models' namespace?

I have a class like this:
<?php
namespace App\ORM;
use Cake\ORM\Query as ORMQuery;
use Cake\Database\ValueBinder;
use Cake\Datasource\ConnectionManager;
class Query extends ORMQuery
{
/*Some stuff which has no relevance in this question*/
protected $mainRepository;
protected function isReplicate()
{
return ($this->mainRepository && $this->mainRepository->behaviors()->has('Replicate') && ($this->getConnection()->configName() !== 'c'));
}
public function __construct($connection, $table)
{
parent::__construct($connection, $table);
$this->mainRepository = $table;
}
public function execute()
{
if ($this->isReplicate()) {
$connection = $this->getConnection();
$replica = clone $this;
$replica->setConnection(ConnectionManager::get('c'));
$replica->execute();
}
$result = parent::execute();
return $result;
}
}
This works well, that is, there is a central c server and there are other servers that are separated by districts. There are some tables that are to be refreshed on c when something was executed for them on a district server. Table models that are to be replicated look like this:
<?php
namespace App\Model\Table;
use App\ORM\Table;
class SomeNameTable extends Table
{
public function initialize(array $config)
{
/*Some initialization that's irrelevant from the problem's perspective*/
}
}
Everything works, but there is a catch. The use App\ORM\Table; statement specifies that my own Table implementation should be used and that Table implementation ensures that once query is being called, my Query class will be used, described in the first code chunk from this question. This is my Table class:
<?php
namespace App\ORM;
use Cake\ORM\Table as ORMTable;
class Table extends ORMTable
{
public function query()
{
return new Query($this->getConnection(), $this);
}
}
As mentioned earlier, everything works as expected, but this approach effectively means that I will need to change the namespace of the base class for all the models where this replication behavior is needed. This is surely easy to do now, but in the future I worry that some new tables are to be replicated and developers working on that will not read documentation and best practices section, inheriting the model directly from Cake's class with the same name. Is there a way to tell Cake that I would like all models fulfilling some logical validation, (for example for models that have a behavior) will use the overriden execute method? In general, the answer to such questions would be a "no", but I wonder whether Cake has a feature for overriding code behavior based on some rules.
Ex.
function isReplicate($model) {
return ($model->behaviors()->has('Replicate'));
}
The function above could determine whether the new execute is preferred or the old, Cake one.
Not really, no, at least as far as I understand your question.
There's events that you could utilize, Model.initialize for example, it would allow you to check whether the initialized model has a specific behavior loaded, but you would not be able to change how the inherited query() method behaves.
You could use it for validation purposes though, so that developers that don't follow your documentation get slapped in the face when they don't extend your base table class, eg check whether it's being extended and throw an exception if that's not the case, something along the lines of this:
// in `Application::bootstrap()`
\Cake\Event\EventManager::instance()->on(
'Model.initialize',
function (\Cake\Event\EventInterface $event) {
$table = $event->getSubject();
assert($table instanceof \Cake\ORM\Table);
if (
$table->behaviors()->has('Replicate') &&
!is_subclass_of($table, \App\Model\Table\AppTable::class)
) {
throw new \LogicException(
'Tables using the Replicate behavior must extend \App\Model\Table\AppTable'
);
}
}
);
See also
Cookbook > Events System
Cookbook > Database Access & ORM > Table Objects > Lifecycle Callbacks

PHP Design pattern to pass a class name and check it for the class existence

I have several classes handling data validation and API requests preparation for every external API function. An example of those:
class PostProductFunction($user_params)
{
validate()
{
//...
}
invoke($user_params)
{
$this->validate();
//doing request...
}
}
I have an APIAccount class to represent one of several API accounts. It handles auth and it has a method
function invokeFunction($functionClassName, $user_params)
{
// check if the class $functionClassName exists and creates an instance of it
// ... $obj = new $functionClassName();
$obj->invoke($user_params);
}
So, the function class doesn't know about auth stuff and the APIAccount class doesn't know about user data structure.
The question is how to handle this $functionClassName inside the APIAccount class. Do I need to store all names of function classes somewhere? Do I need some kind of enum class? I don't want to simply take a string and then check whether the class with this name exists because the programmer passing this string can easily mistype, and in general he needs documentation to know the proper function name. I want that he somehow see all available options with something like enum. Do you have any ideas how to better implement it?
I'm not sure you need a design pattern for this. The following should satisfy your needs:
function invokeFunction($functionClassName, $user_params)
{
if (! class_exists($functionClassName)) {
throw new Exception("Class {$functionClassName} does not exist.");
}
$obj = new {$functionClassName}();
$obj->invoke($user_params);
}
I figured out how to both keep the invocation method signature and make it easier for programmers to see the hint with possible class names to pass in IDE.
The method itself remains as it is:
function invokeFunction($functionClassName, $user_params)
{
// check if the class $functionClassName exists and creates an instance of it
if (! class_exists($functionClassName)) {
throw new Exception("Class {$functionClassName} does not exist.");
}
$obj = new $functionClassName();
$obj->invoke($user_params);
}
However, in the classes which names can be passed as $functionClassName one needs to add a static method like this:
public static function getFunctionName()
{
$function_name = get_called_class();
// .... some optional operations with the name depending on what part of
// (or the full name) invokeFunction is anticipating
return $function_name
}
Then calling the invokeFunction is as simple as:
invokeFunction (SomeCertainFunctionClass::getFunctionName(), array('some_param'=>'some_val'));
Using this approach you can have your IDE hint when typing 'SomeCertainFunctionClass' and at the same time you can use a class name string as a function argument without any downfalls.

Yii extending the user doesn't seem to retain additional instance variables

I have read the tutorials on how to make a sub class of CWebUser and followed the instructions. The paths all work, and the code is getting into the right methods, however the value returned from my getter is always nil.
class PersonUser extends CWebUser {
// Store model to not repeat query.
private $_person;
// Load user model.
public function loadPerson($id=null, $duration=0)
{
$this->login($id,$duration);
$this->_person=Person::model()->findByPk(Yii::app()->user->id);
}
public function getPerson()
{
return $this->_person;
//return Person::model()->findByPk($this->id);
}
}
If I echo in the loadPerson method $this->_person->first_name after I set _person I get the value I expect. However, at any later time, if I ask for Yii::app()->user->person, the getPerson() method gets called, but $this->_person is now null. I know it's getting in there, if I uncomment the line below and have it look up the person every time, it works.
Is this an issue with Yii? I would really like to be able to cache the person object so I can reference it throughout the session without having to make more calls to the database. What am I missing??
There is no issue with Yii....
As per the documentation, CWebUser class identifies predefined variables "id" and "name" which remains persistent through out the session. Any additional variables should be used with getState() and setState() methods.
" Moreover CWebUser should be used together with IUserIdentity Class which implements the actual authentication algorithm. "
The method loadUser() is never called. And the login() call inside also doesn't make sense. A simpler implementation of getPerson() would be.
private $_person = false;
public function getPerson()
{
if($this->_person===false)
$this->_person = Person::model()->findByPk($this->id);
return $this->_person;
}

PHP Multiple Inheritance with Interfaces

I'm trying to understand how using interfaces gives me multiple inheritance as I've been googling.
class A
{
function do1(){}
function do2(){}
function do3(){}
}
class B extends A
{
function do4(){}
function do5(){}
function do6(){}
}
class C extends B
{
}
In the above example, class C has all the methods from class A and B. However, class B also has all the methods of class A, which is not necessary desired.
My searches have come up to use interfaces to solve this issue by moving methods to a class and creating interfaces, as below.
interface A
{
function do1();
function do2();
function do3();
}
interface B
{
function do4();
function do5();
function do6();
}
class C implements A, B
{
function do1(){}
function do2(){}
function do3(){}
function do4(){}
function do5(){}
function do6(){}
}
I don't really see how this solves the issue because all the code is in the new class. If I just wanted to use class A as originally, I would have to create a new class that implement interface A and copy the same code to the new class.
Is there something I'm missing?
PHP doesn't have multiple inheritance. If you have PHP 5.4, though, you can use traits to at least avoid every class having to copy code.
interface A {
public function do1();
public function do2();
public function do3();
}
trait Alike {
public function do1() { }
public function do2() { }
public function do3() { }
}
interface B {
public function do4();
public function do5();
public function do6();
}
trait Blike {
public function do4() { }
public function do5() { }
public function do6() { }
}
class C implements A, B {
use Alike, Blike;
}
class D implements A {
use Alike;
// You can even "override" methods defined in a trait
public function do2() { }
}
Note, though, you have to both implement the interface and use the trait (or, of course, provide your own implementation). And C and D are not related at all, except in both implementing the A interface. Traits are basically just interpreter-level copy and paste, and do not affect inheritance.
The first thing to understand about interfaces is that they are NOT used for inheritance. That is a very important thing to understand. If you're trying to make several classes share the same concrete code, that is not what an interface is for.
The second thing to understand is the difference between client code, and service code.
Client code is essentially the "last step" in a sequence of requests for data. A controller or a view in MVC can be considered client code. The model, meanwhile can be considered service code.
Interfaces are intended for client code to enforce consistency in the types of data it gets from services. Or another way to think about it - interfaces are a way for services to make sure they will be compatible with a request from client code. That is ALL they do. They quite literally provide an interface by which data is accessed, not an implementation that multiple classes can share.
So to give you a concrete example:
Client Code - a ProfileViewController class for a user's forum profile
class ProfileViewController
{
public function showProfile(User $user)
{
$user->getProfile();
}
}
Service Code - a User model that retrieves data and passes it on to the client code that is requesting it
class User
{
public function getProfile()
{
$profile = Do some SQL query here or something
return $profile;
}
}
Now suppose later on you decide to break up Users into Members, Administrators, Referees, Moderators, Writers, Editors etc, and that each has their own unique type of profile. (e.g. its own custom query, or data, or what have you)
There are now two problems present here:
You need to guarantee that whatever you pass in there will contain a getProfile() method.
showProfile() will fail if you pass in anything other than a User object.
1 is easy to solve through abstract classes and methods (or through Interfaces). 2 at first sounds easy as well, because you can just make Moderators, Admins, and Members all subclasses of a User base class.
But then what happens when down the road, in addition to USER profiles, you want to have generic profiles for things. Perhaps you want to show profiles of sports players, or even profiles of celebrities. They're not users, but they still have profiles/details pages.
Because they're not users, it may not make any sense to consider them subclasses of User.
So now you're a bit stuck. showProfile() needs to be able to accept more than just a User object. In fact, you don't know what type of object you will ultimately want to pass in there. But at the same time, since you always want to be able to grab $user->getProfile(), anything you pass in there must be generic enough to be passed in, AND implement a concrete getProfile() method.
Solution? Interfaces!!!!!
First some service code
// First define an interface for ANY service object that will have a profile
interface IHasProfile
{
public function getProfile();
}
// Next, define the class for an object that should have a profile. I'll do a bunch for the sake of an example...
class User implements IHasProfile
{
public function getProfile()
{
$profile = Your unique user profile query here
return $profile;
}
}
class Celebrity implements IHasProfile
{
public function getProfile()
{
$profile = Your unique celebrity profile query here
return $profile;
}
}
class Car implements IHasProfile
{
public function getProfile()
{
$profile = Your unique vehicle profile query goes here
return $profile;
}
}
Next, the client code that will use it
class ProfileViewController
{
public function showProfile(IHasProfile $obj)
{
$obj->getProfile();
}
}
And there you have it. showProfile() has now been abstracted enough that it doesn't care what object it gets, it only cares that the object has a public getProfile() method. So now you can create new types of objects to your heart's content, and if they are intended to have profiles, you can just give them "implements IHasProfile" and they will automatically just work with showProfile().
Kind of a contrived example, but it should illustrate at least the concept of interfaces.
Of course, you could just be "lazy" and not typecast the object at all, and thus allowing ANY object to be passed in. But that's a separate topic entirely ;)
Multiple inheritance is possible only for Interfaces!
such as my output for it:
php > interface A{};
php > interface B{};
php > interface C extends A,B{};
php > class D implements C{};
php > $d = new D();
php > echo ($d instanceof A);
1
I created A and B interfaces and C interface extends them.
After we have D class which implements C interface
Finally, I ask if $d object is instanceof A interface, yeah it's true
For the lulz, I try to create E class which extends D and stdclass classes and get error!
php > class E extends D, stdclass{};
PHP Parse error: syntax error, unexpected ',', expecting '{' in php shell code on line 1
Parse error: syntax error, unexpected ',', expecting '{' in php shell code on line 1
Multiple inheritance is not possible in PHP like in many OOP supported languages
See similar topic here. The topic is in AS3 but gives you answer.
To answer particularly about solving using interfaces is answered in the same post here
As told here by #tonicospinelli, it seems that indeed, PHP allows multiple inheritance of interfaces, but it isn't clearly explained, just given an example
The way multiple inheritance works, PHP passes these using Traits that implement Interfaces.
Once you declare a Class implementing a "multi-interface" (1), you may use already defined Traits to assure inheritance is well-performed.
(1): Saying "multi-interface" I mean a class implementing an interface what extends from multiple other interfaces

How can I make my validation class extendible?

I am writing a validation class in PHP which I would like to be extendible without editing the main parent class. I have provided a simplified version below of what I hope to achieve. I am passing a function/method name to validate() which first checks if it exists and if it does invokes it to check the variable I passed is valid. I am new to OOP and having problems with scope / visibility as I'm unable to get any custom validation rules in the child class working without hardcoding the name of the child class in the parent class. What is the best way to go about this? Many thanks for any assistance you can provide.
$rule = "number";
$var = "abcdef";
class Validation
{
public static function validate($rule, $var) {
if (is_callable("self::{$rule}")) {
return self::$rule($var);
}
}
protected static function number($var) {
return (preg_match("/^[0-9]+$/i", $var));
}
}
class MyRules extends Validation
{
public static function letter($var) {
return (preg_match("/^[a-zA-Z]+$/i", $var));
}
}
print MyRules::validate($rule, $var) ? "Valid!" : "Not valid!"; // Not valid!
Firstly, you can prevent overriding of the validate method using the final keyword:
public static final function validate($rule, $var) {
As for not being able to call static methods of subclasses, this can be done using Late Static Binding:
class Validation {
public static final function validate($rule, $var) {
if (is_callable("static::$rule")) {
return static::$rule($var);
}
}
}
This sounds like a job for strategy design pattern. It can be used if you want different variants of an algorithm, in your case the validation.
Here is how i work with my self made framework / libraries :
Like most n-tier systems , (MVC is like that) , the data must be checked b4 it is passed to the back-end tier(the database) ... so in a mvc arhitecture u can make a model that is the corespoindent to a db table , put in there the queries connection and validation... That model class needs to know only about that table data and nothing more...
If ull get to a point and see that ur validations get to some kind of routine... u can make a library... Or u can think from start what kind of data will ur app have...
P.S. : so for each data type in a table write a the correspondent validation, if the validations repeat make a library and call the lib validation method for the coresponding db data type

Categories