0

The title is a bit misleading because I know it's not possible to share a static var over the children, but I'm searching for a way to fix my problem without doing it with a shared static var.

I have a superclass called 'Model' and some underlying child-classes called 'User' and 'Company'. I want to automate as much as possible, so that the subclasses only have fields and nothing more.

I want to create static 'GetAll' and 'GetByKey' methods in the superclass, so that I can use the following code:

 $users = User::getAll();
 $company = Company::getByKey(34); // Where 34 is the ID.

In my case, the Company class should know his primary key, without me telling him every time. I wan't to avoid methods like this in the child-classes:

public static function getByUserId($userId)
{
    return static::find(array('userId' => $userId));
}

To do this, I created a static variable 'primaryKey' in the superclass. The problem is, there is no perfect place to 'set' the variable. The constructor won't be used if the only thing I do is getting all Users. Next to that, the static variable belongs to Model, which means that it will be overridden, if I have code like this:

 $company = new Company(); // Model sets 'primaryKey' to 'id'
 $user = User::GetByKey('myUsername'); // The 'primaryKey' is still 'id', but it should be 'username'

I do want to save the $primaryKey because getting the primary key from MySQL, every time I request some models, is just overhead.

Is there any way to give the subclasses their own static variable-values without creating them in the specific classes? I want to minimize the amount of work done for the Models. Using reflection or magic methods is no problem at all. In the most absurd-situation I have to make a DatabaseManager which has an array that holds all information from all models.

Note: I'm asking if there is a solution for my problem, even if it's not recommended or ugly code. I know that this question has been asked many times.

Thanks in advance,

Erwin Okken
  • 233
  • 2
  • 17
  • 2
    If I understood your issue correctly, you wouldn't have had this problem if you designed your application logic without statics. – Artemkller545 May 23 '14 at 08:29
  • What about setting the primary key as a constant in each class, and then getting the value by using constant([called class].'primary_key'). Sorry for not being able to supply some code, but I'm browsing SO while taking some support calls @ work. – illuzive May 23 '14 at 08:30
  • Why don't you simply instantiate your classes?! Please see [How Not To Kill Your Testability Using Statics](http://kunststube.net/static/) – deceze May 23 '14 at 08:36
  • illuzive: I have more variables like this so that will be moving the problem. @deceze, Ben-beri: You mean something like: $modelManager->GetByKey('User', 'myUsername'); ? – Erwin Okken May 23 '14 at 09:02
  • @ErwinOkken Take a look at my answer. – Artemkller545 May 23 '14 at 15:50

1 Answers1

0

You can use Services & the DataMapper pattern here intead of using dirty statics.

According to How should a model be structured in MVC?

Also it seems like you're failing to understand what a model is, take a look at the link above, read it carefully.

I can't really implement your code idea into this example exactly - because I didn't really understand what's going on there (power of statics)

I'll try to show you a very similar example:

class CompanyBusiness {

    private $mapper;
    private $domain;

    public function __construct(CompanyMapper $mapper, CompanyDomain $domain) {
        $this->mapper = $mapper;
        $this->domain = $domain;
    }
}

In this example, you have a Comapany service, which handles comapny workers (Employers) for now. You want to start off with adding new workers to your comapny, the service will receive the data from the place where you handle input/output (i.e a controller) and then initialize the data into the DomainObject. Note: your company only allows the worker if his email is valid and hes atleast 23 years old, therefore you will have to validate his data in your DomainObject see example below:

/**
 * Adds a new worker to the comapny, following the company rules.
 * @param $firstname    string  First name of the worker
 * @param $lastname     string  Last name of the worker
 * @param $age          int     Age of the worker
 * @param $email        string  E-mail of the worker
 * @return bool         Business action succeed
 */
public function addWorker($firstname, $lastname, $age, $email) {
    $this->domain->setFirstname($firstname);
    $this->domain->setLastname($lastname);
    $this->domain->setAge($age);
    $this->domain->setEmail($email);

    if ($this->domain->validated()) {
        $this->mapper->addNewWorker($this->domain);
        return true;
    }
    else {
        // error has occured, maybe set error message?
    }
    return false;
}

And your domain method:

/**
 * Checks if worker follows the requirements/valid
 * @return bool valid
 */
public function validated() {
    return $this->age >= self::MINIMUM_AGE && filter_var($this->email, FILTER_VALIDATE_EMAIL);
}

Note: self::MINIMUM_AGE is a constant in the domain class.

The DataMapper is the fun part, that's the class which handles with your data structures i.e MySQL database.

That's how I use it with the implementation above:

class CompanyMapper extends Mapper {

    private $tables = array(
        'workers'   => 'company_workers'
    );

    public function addNewWorker(CompanyDomain $company) {
        parent::getDB()->insert($this->tables['workers'], array(
            "first_name"    => $company->getFirst(),
            "last_name"     => $company->getLast(),
            "age"           => $company->getAge(),
            "email"         => $company->getEmail()));
    }

    public function getWorkerProfileByID($id) {
        /** @var $query PDOStatement */
        $query = parent::getDB()->fetch($this->tables['workers'], array("worker_id" => $id));
        return $query->fetch();
    }
}

Basically DataMappers should contain the database connection, doesn't matter where - I've used a parent class to store it there.

And then again, another example, get the profile data of a specific worker (by his ID)

/**
 * @param $id       int ID of the worker
 * @return array    The data of user's profile
 */
public function getWorkerProfile($id) {
    $data = $this->mapper->getWorkerProfileByID($id);
    return $data; // check for nulls after receiving return or set error messages
}

Also if you're going to work with dependency injections like this, you will eventually get tired injecting and caring about the dependencies in these constructors, you can use a DI container which will automatically create & inject these objects for you, try this one take a big look at the ReadMe, it will explain & show useful things.

Community
  • 1
  • 1
Artemkller545
  • 979
  • 3
  • 21
  • 55