Let's assume that we have module called 'UsersModule' with the following model in it:
class User extends CActiveRecord
{
// Some code here
}
We use this module in different applications and some time we want to extend User model to add some custom methods or properties to it. More over, often we want to change tables in database to store this new properties in it. But we don't want to change code in the UsersModule itself because it comes from the master repository (GitHub for ex.) and when we fix some bugs in it we want to simply update this module from repository in all our projects. At the same time we want to save custom changes made for the projects. So we have the following idea:
In UsersModule.php we do the following:
class UsersModule extends CWebModule
{
public $usersBaseClass = 'UsersBase';
}
In Users.php:
$extendClass = Yii::app()->getModule('users')->usersBaseClass;
$version = '1.0';
$usersFile = Yii::getPathOfAlias('application.runtime').'/Users_extends_'.$extendClass.'_v'.$version.'.php';
if(!file_exists($usersFile)) {
$code =<<<PHP
<?php
/**
* This is the model class for table "{{users}}".
*/
class Users extends {$extendClass}
{
/**
* Returns the static model of the specified AR class.
* #param string \$className active record class name.
* #return Users the static model class
*/
public static function model(\$className=__CLASS__)
{
return parent::model(\$className);
}
}
PHP;
file_put_contents($usersFile, $code);
}
if(!class_exists('Users', flase))
require_once($usersFile);
Also we introduce UsersBase.php:
class UsersBase extends CActiveRecord
{
// All base Users model logic is here
}
So when we use Users class somewhere in our application our code in Users.php generates real Users class that extends desired base class. In each project when we want to extend our Users model we can do the following:
In configs/main.php of the project:
'modules' =>
'users => array(
'usersBaseClass' => 'MyUsers'
)
And also we add MyUsers.php some where in our application:
class MyUsers extends UsersBase
{
// All the custom logic goes here
}
So my question is:
Is it a good idea to generate classes automatically in runtime or not?
Genrating php code during runtime could work, but not the best solution imho. You should use some kind of table inheritance.
More info:
http://www.yiiframework.com/wiki/198/single-table-inheritance/
http://learnyii.blogspot.hu/2012/04/yii-table-inheritance-single-active.html
Related
I want to execute my custom code after or before every $model->save() in Yii2.
I want to perform this globally like using components, etc.
I want to create a user activity log to store how many times a user insert or update any rows in database table, so for this I want to run some code when ever data inserted or update in tables.
Any help or suggestion will appreciated.
As #patryk mentioned ActiveRecord has beforeSave and afterSave methods.
I use something like the following to store a created date for new records (and updated date when existing records are updated). The code in the example is, of course, trivial but it allows you to use any arbitrary code you need, see the layout and how to split code for 'new' records and existing.
This overridden method can be added to any model class which extends ActiveRecord to allow the parent beforeSave to be called correctly also.
/**
* #inheritdoc
*/
public function beforeSave($insert)
{
if ($insert) {
// This is a new instance of modelClass, run your 'insert' code here.
$this->created_date = time();
}
// Anything else will be run any time a model is saved.
$this->updated_date = time();
return parent::beforeSave($insert);
}
edited to add:
if the code to be run is the same for each model you could create a trait and use the trait in each model to allow you to change the behaviour in one place. Or create a custom ActiveRecord class to override the beforeSave method for each subclass.
Create new class(MyActiveRecord) which extends \yii\db\ActiveRecord
Use extends MyActiveRecord to all your project models
Ex:
class MyActiveRecord extends \yii\db\ActiveRecord
{
public function afterSave($insert, $changedAttributes){
//This will called after every model saved
return parent::beforeSave($insert,$changedAttributes);
}
}
In your project other models
class Customer extends app\models\MyActiveRecord
{
}
Yii2 ActiveRecord class has beforeSave and afterSave methods. https://github.com/yiisoft/yii2/blob/master/framework/db/BaseActiveRecord.php#L926
But maybe it would be better to do such operation on database triggers?
I would like to hear your tips or solutions to this problem. Even more helpful would be links to some working!! open source projects which implements some of these mechanisms.
For example i want to synchronize all github's gist with Laravel model. I think about two form.
Option 1
Write separate classes for example Importer and call it's method from controllers and commands and interact with Model.
Option 2
Put data extracting logic (api calls) inside Laravel's model hierarchy (traits,interfaces and so) and within artisan's command let's say synchronize:all loop through all model and check for any changes and update each row.
example
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Sample\Http\Client;
class Gist extends Model
{
/**
* Example of fetching new gists
*/
public static function getNew($gistId = '6cad326836d38bd3a7ae')
{
$resposne = Client::request('GET', 'https://api.github.com/gists/'.$gistId)
->response()
->json();
$Gist = new static($response);
$Gist->save();
return $Gist;
}
/**
* Get list of all gist from external source and ... who knows
*/
public function listAllGistFromApi()
{
$resposne = Client::request('GET', 'https://api.github.com/gists/')
->response()
->json();
return $response;
}
/**
* Example of updating existing gist
*/
public function updateFromApi()
{
$gistId = $this->id; // primary key also api uid
$resposne = Client::request('GET', 'https://api.github.com/gists/'.$gistId)
->response()
->json();
return $this->setAttributes($response)->save(); // simplified logic
}
}
Your solutions...
You can use observers, what it does? it keeps an eye on the model changes, there you can perform your Apis to the out data source, take a look here how to apply observes in Laravel
I have a project made with the command phalcon project simple --type=simple.
I haven't changed the structure at all.
The part where I'm stumped, is that I have two databases I look at.
I want to access the Account model for both databases A and B, with out doing something like $account = AAccount::find(); and $account = BAccount::find();.
I currently have this code:
model/AAccount.php
class AAccount extends Phalcon\Mvc\Model {
// use DB A
}
model/BAccount.php
class BAccount extends Phalcon\Mvc\Model {
// use DB B
}
What is the most optimum way of doing so? Namespaces? I cannot change the table name Account for both.
I don't know if I understand your question: you have two tables with the same name, but they are in two different schemas (databases)? If yes, I had the same problem and I solve it with the follow structure (you can see part of this code in my bauhaus project) (see this reference too: point to other schema (Phalcon - Working with Model)):
(1) Base model class located at models/:
namespace MyApp\Model;
class Base extends \Phalcon\Mvc\Model
{
// code for your model base class
}
(2) Base class for schema A located at models/schema-a/:
namespace MyApp\Model\SchemaA;
class Base extends MyApp\Model\Base
{
// ...
// returns the name of the schema A
public function getSchema()
{
return `schema_a_name`;
}
// ...
}
(3) Base class for schema B located at models/schema-b/:
namespace MyApp\Model\SchemaB;
class Base extends MyApp\Model\Base
{
// ...
// returns the name of the schema B
public function getSchema()
{
return `schema_b_name`;
}
// ...
}
(4) Account Model in the schema A located at models/schema-a/:
namespace MyApp\Model\SchemaA;
class Account extends Base
{
// ...
}
(5) Account Model in the schema B located at models/schema-b/:
namespace MyApp\Model\SchemaB;
class Account extends Base
{
// ...
}
This solution works good when you have a fixed number of schemas, but If you have no-fixed number of schemas, I think a better solution would be to create an logic in the getSchema function of the model base. Something like:
public function getSchema()
{
// this is just a suggest
return $this->getDI()->scope->currentSchema;
}
I hope this can help you.
Note: you will have to be careful to create relationships between models with namespace.
Here's what I'm trying to implement in my program:
The program should open a zip file, which contains many data files
The format of the data files can differ between zip files (e.g. csv, tab delimited, or could even be some kind of binary file which needs decoding)
However, within a zip file all data files will be of the same type
I have been reading "Design Patterns" by Gamma et al., and have been looking at the Abstract Factory pattern to try to solve this.
Ideally, I want to have one class for the Zip file, which can read any type of data file within it. I guess I would have two classes - FileTypeA and FileTypeB, which could process different formats of data (although there could be more in the future). I would like a way of telling my ZipFile class which type of file to use when reading the data.
So far, this is what I have come up with:
<?php
/**
* An abstract factory used for creating data files of any type
*/
abstract class DataFileFactory{
abstract function createFile($id);
}
/**
* A factory for creating and setting up a data file of type 'A'
*/
class FileAFactory extends DataFileFactory{
public function createFile($id){
$file = new FileA();
$file->setSampleId($id);
return $file;
}
}
/**
* A factory for creating and setting up a data file of type 'B'
*/
class FileBFactory extends DataFileFactory{
public function createFile($id){
$file = new FileB();
$file->setSampleId($id);
return $file;
}
}
/**
* An abstract class which defines some functionality of a data file
*/
abstract class DataFile{
abstract function readData();
abstract function setSampleId();
}
/**
* Concrete class that processes a data file of type 'A'
*/
class FileA extends DataFile{
public function readData(){
echo "Reading data from a file A<br/>";
}
public function setSampleId(){
echo "Setting sample id of a file A<br/>";
}
}
/**
* Concrete class that processes a data file of type 'B'
*/
class FileB extends DataFile{
public function readData(){
echo "Reading data from a file B<br/>";
}
public function setSampleId(){
echo "Setting sample id of a file B<br/>";
}
}
/**
* Concrete class that reads a zip file and reads each file within the zip
*/
class ZipFile{
private $files = array("file1.txt","file2.txt","file3.txt","file4.txt");//this would be an array read from the zip file
private $sampleId = 1;//this would be derived from some other function
/**
* Read all the files in a zip archive.
* $factory can be an instance of any class that extends DataFileFactory, and is used for creating each file
*/
public function readFiles(DataFileFactory $factory){
foreach($this->files as $fileName){//loop through each file in the zip
$file = $factory->createFile($this->sampleId);//use the factory to create the desired file
$file->readData();//now read the data from the file!
echo "object created of type: ".get_class($file)."<hr/>";
}
}
}
/***********************************************************************************************
* IMPLEMENTATION
***********************************************************************************************/
$zip = new ZipFile();//create a new zip file
$factory = new FileAFactory();//instantiate a new factory, depending on which type of file you want to create
$zip->readFiles($factory);//read the files, passing the correct factory object to it
Can anyone tell me:
(A) Whether this is a good way of achieving what I'm looking for, or is there some simpler way of doing it?
(B) Is this actually the Abstract Factory pattern, or have I completely misunderstood?
Thanks in advance!
It's a good implementation but it can be finetuned a bit if you use interfaces.
An abtract class with all virtual methods it's just a interface so don't use abstract classes, use interfaces.
interface IDataFileFactory{
public function createFile($id);
}
class FileAFactory implements IDataFileFactory
class FileBFactory implements IDataFileFactory
If you find repeating code in FileAFactory and in FileBFactory methods then it is time to refactor your classes and create inheritance.
interface IDataFileFactory{
public function createFile($id);
}
abstract class BaseFileFactory implements IDataFileFactory {
//some methods implementation with common features to avoid repeating code
//some abstract methods to be implemented for A and B FileFactories
//absolute abstract base class has no sense because in php you can use interfaces.
//...
}
class FileAFactory extends BaseFileFactory
class FileBFactory extends BaseFileFactory
Then use throug interface:
public function readFiles(IDataFileFactory $factory){
//create a file using factory
return IDataFile; //return Interface implemented by all DataFile types.
}
You can do the same thing with DataFile base class and so on.
I also recomend to not pass factories in parameters because a factory is out of context. Try to do not mix architecture implementation with data and info process workflow. You can create a container, in a scope accesible for your other classes, to resolve the factory.
The container can, for example, read configuration files to create concrete factory in application bootstrap; read from some value, choosed by the user in previous steps of the user case, stored in a class instance or accepting a parameter in runtime to resolve the factory. It's about implement some kind of simple dependency inyection.
Anyway, this is only my point of view and can be a hurge amount disagreements.
I hope it help.
I am using a plugin (in this case Authake) and I would like to override/extend some of the functionality, but I'm not sure how I would go about doing this. I've managed to figure out how to customize the view (I created a folder '/app/views/plugins/authake' but I'm wondering how to modify/override/extend the Models and Compoenents of the plugin.
I'm guessing you want to extend the functionality of a model or perhaps a behavior in the plugin?
For example, we could extended the functionality of a Sequence behavior that is part of a Sequence plugin like so:
Create a new file in app/models/behaviors and call it extended_sequence.php
In this file, we'll create an ExtendedSequenceBehavior class that extends SequenceBehavior and overrides the beforeFind method. It will end up looking something like:
<?php
/**
* Import the SequenceBehavior from the Sequence Plugin
*/
App::import('Behavior', 'Sequence.Sequence');
/**
* Extended Sequence Behavior
*/
class ExtendedSequenceBehavior extends SequenceBehavior
{
/**
* Overrides the beforeFind function
*/
public function beforeFind(&$model, $queryData)
{
/**
* Do something different here such as modify the query data
*/
/**
* You could still call the original function as well
*/
parent::beforeFind(&$model, $queryData);
}
}
?>
Note, that we have to import the Sequence behavior using Cake's App::import before we define the ExtendedBehavior class.
Update your model to use the extended class:
var $actsAs = array('ExtendedSequence');