If I use defineAuditFields in my models I get the error
->exception("Method is not defined for this object", "Logic")
is defineAuditFields() deprecated in 4.2.1?
Is there a new method?
defineAuditFields was an artifact from old MVC model. The new Agile Toolkit allows you to use controllers to do the same. Janis have outlined that in his migration guide: http://www.ambienttech.lv/blog/ saying you can now use controllers.
class Controller_Audit extends AbstractController {
function init(){
parent::init();
$this->owner->hasOne('User','created_by')->system(true);
$this->owner->hasOne('created_dts')->type('datetime')->system(true);
$this->owner->hasOne('modified_dts')->type('datetime')->system(true);
$this->owner->addHook('beforeInsert,beforeModify',$this);
}
function beforeInsert($m){
$m['created_by']=$this->api->auth->model->id;
$m['created_dts']=date('Y-m-d H:i:s');
}
function beforeModify($m){
$m['modified_dts']=date('Y-m-d H:i:s');
}
}
Certainly you can do more actions here. If you are in a need of soft-delete, then something like this would work well:
class Controller_SoftDelete extends AbstractController {
function init(){
parent::init();
$this->owner->hasOne('deleted')
->type('boolean')->enum(array('Y','N'))
->system(true);
$this->owner->addCondition('deleted',false);
$this->owner->addHook('beforeDelete',$this);
}
function beforeDelete($m,$q){
$q->set('deleted','Y')->update();
$q->where('1=2'); // avoid actual deletion
}
}
p.s. if my code here contains some minor mistakes, please [edit] them.
Related
How to call a model in another controller?
I explain myself, I created a controller and I try to call another model in this controller, but I have the error "Error: Class not found".
You can see the code of my controller "Adserver" trying to call the model "Zone"
<?php
declare(strict_types=1);
use Phalcon\Mvc\Model\Zone as Zone;
class AdserveurController extends ControllerBase
{
public function indexAction()
{
$id_zone= 1;
$zone = Zone::findFirstByid_zone($id_zone);
if(!zone){
$this->flashSession->error('erreur id');
return $this->response->redirect();
}
print_r ($id_zone);
$zone = Zone::findFirst($hauteur);
if(!zone){
$this->fashSession->error('erreur hauteur');
return $this->response->redirect();
}
$zone = Zone::findFirst($largeur);
if(!zone){
$this->fashSession->error('erreur hauteur');
return $this->response->redirect();
}
}
}
On top of my controller, I tried the "use Phalcon\Mvc\Model" and the error persists.
My phalco version is 4.0
Could someone help me on how to call two models in a separate controller?
Thanks.
I call multiple models from withing various controllers all the time and don't have to declare "use Phalcon\Mvc\Model\Zone as Zone;" at the top.
How is your Zone model file defined?
if other_model has relation with Zone (defined in model) then :
Zone->other_model->field;
else in head of file
"use Phalcon\Mvc\Model*other_model*;
Try to define the namespace in your model, like:
// app/models/Zone.php
<?php
namespace MyApp\Models;
use Phalcon\Mvc\Model;
class Zone extends Model
{
public function initialize()
{
$this->setSource('zone');
}
}
Then in your loader:
<?php
use Phalcon\Loader;
$loader->registerNamespaces(
[
'MyApp\Models' => 'app/models'
]
);
$loader->register();
Then in your controller:
<?php
use MyApp\Models\Zone;
Your current indexAction should work, but beware of what I commented to you regarding fields with underscores:
$zone = Zone::findFirstByid_zone($id_zone); //won't work
$zone = Zone::findFirstByIdZone($id_zone); //works
Since you commented that you are a beginner, I would recommend you to review the MVC examples for Phalcon projects: https://github.com/phalcon/mvc So you get acquainted with namespaces in your app.
Also, if you are working on a large application, it's better that you register namespaces than directories for performance reasons, as detailed in the Docs: https://docs.phalcon.io/4.0/en/loader
Ps. Reviewing indexAction there are some parameters that are not defined, like $hauteur and $largeur. Since $zone still is not working, these issues are still not visible --but they'll show up after zone is working.
I am using Laravel 5.1 and would like to access an array on the Model from the Trait when the Model before the model uses the appends array.
I would like to add certain items to the appends array if it exists from my trait. I don't want to edit the model in order to achieve this. Are traits actually usable in this scenario or should I use inheritance?
array_push($this->appends, 'saucedByCurrentUser');
Here is how my current setup works.
Trait
<?php namespace App;
trait AwesomeSauceTrait {
/**
* Collection of the sauce on this record
*/
public function awesomeSauced()
{
return $this->morphMany('App\AwesomeSauce', 'sauceable')->latest();
}
public function getSaucedByCurrentUserAttribute()
{
if(\Auth::guest()){
return false;
}
$i = $this->awesomeSauced()->whereUserId(\Auth::user()->id)->count();
if ($i > 0){
return true;
}
return false;
}
}
Model
<?php namespace App;
use App\AwesomeSauceTrait;
use Illuminate\Database\Eloquent\Model;
class FairlyBlandModel extends Model {
use AwesomeSauceTrait;
protected $appends = array('age','saucedByCurrentUser');
}
What I would like to do is something to achieve the same effect as extending a class. I have a few similar traits, so using inheritance gets somewhat ugly.
trait AwesomeSauceTrait {
function __construct() {
parent::__construct();
array_push($this->appends, 'saucedByCurrentUser');
}
}
I have seen some workarounds for this, but none of them seem better/cleaner than just adding the item to the array manually. Any ideas are appreciated.
Update
I discovered this way of accomplishing what I need for one trait, but it only works for one trait and I don't see an advantage of using this over inheritance.
trait
protected $awesomeSauceAppends = ['sauced_by_current_user'];
protected function getArrayableAppends()
{
array_merge($this->appends, $this->awesomeSauceAppends);
parent::getArrayableAppends();
}
How I am currently handling my Model, for what it is worth.
model
public function __construct()
{
array_merge($this->appends, $this->awesomeSauceAppends);
}
Traits are sometimes described as "compiler-assisted copy-and-paste"; the result of using a Trait can always be written out as a valid class in its own right. There is therefore no notion of parent in a Trait, because once the Trait has been applied, its methods are indistinguishable from those defined in the class itself, or imported from other Traits at the same time.
Similarly, as the PHP docs say:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved.
As such, they are not very suitable for situations where you want to mix in multiple variants of the same piece of behaviour, because there is no way for base functionality and mixed in functionality to talk to each other in a generic way.
In my understanding the problem you're actually trying to solve is this:
add custom Accessors and Mutators to an Eloquent model class
add additional items to the protected $appends array matching these methods
One approach would be to continue to use Traits, and use Reflection to dynamically discover which methods have been added. However, beware that Reflection has a reputation for being rather slow.
To do this, we first implement a constructor with a loop which we can hook into just by naming a method in a particular way. This can be placed into a Trait of its own (alternatively, you could sub-class the Eloquent Model class with your own enhanced version):
trait AppendingGlue {
public function __construct() {
// parent refers not to the class being mixed into, but its parent
parent::__construct();
// Find and execute all methods beginning 'extraConstruct'
$mirror = new ReflectionClass($this);
foreach ( $mirror->getMethods() as $method ) {
if ( strpos($method->getName(), 'extraConstruct') === 0 ) {
$method->invoke($this);
}
}
}
}
Then any number of Traits implementing differently named extraConstruct methods:
trait AwesomeSauce {
public function extraConstructAwesomeSauce() {
$this->appends[] = 'awesome_sauce';
}
public function doAwesomeSauceStuff() {
}
}
trait ChocolateSprinkles {
public function extraConstructChocolateSprinkles() {
$this->appends[] = 'chocolate_sprinkles';
}
public function doChocolateSprinklesStuff() {
}
}
Finally, we mix in all the traits into a plain model, and check the result:
class BaseModel {
protected $appends = array('base');
public function __construct() {
echo "Base constructor run OK.\n";
}
public function getAppends() {
return $this->appends;
}
}
class DecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
}
$dm = new DecoratedModel;
print_r($dm->getAppends());
We can set the initial content of $appends inside the decorated model itself, and it will replace the BaseModel definition, but not interrupt the other Traits:
class ReDecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
protected $appends = ['switched_base'];
}
However, if you over-ride the constructor at the same time as mixing in the AppendingGlue, you do need to do a bit of extra work, as discussed in this previous answer. It's similar to calling parent::__construct in an inheritance situation, but you have to alias the trait's constructor in order to access it:
class ReConstructedModel extends BaseModel {
use AppendingGlue { __construct as private appendingGlueConstructor; }
use AwesomeSauce, ChocolateSprinkles;
public function __construct() {
// Call the mixed-in constructor explicitly, like you would the parent
// Note that it will call the real parent as well, as though it was a grand-parent
$this->appendingGlueConstructor();
echo "New constructor executed!\n";
}
}
This can be avoided by inheriting from a class which either exists instead of the AppendingGlue trait, or already uses it:
class GluedModel extends BaseModel {
use AppendingGlue;
}
class ReConstructedGluedModel extends GluedModel {
use AwesomeSauce, ChocolateSprinkles;
public function __construct() {
// Standard call to the parent constructor
parent::__construct();
echo "New constructor executed!\n";
}
}
Here's a live demo of all of that put together.
I thought I'd add an update for 2019 since this was one of the first discussions that popped up when trying to do a similar thing. I'm using Laravel 5.7 and nowadays Laravel will do the reflection that IMSoP mentioned.
After the trait has been booted, Laravel will then call initializeTraitName() on the constructed object (where TraitName is the full name of the trait).
To add extra items to $appends from a trait, you could simply do this...
trait AwesomeSauceTrait {
public function initializeAwesomeSauceTrait()
{
$this->appends[] = 'sauced_by_current_user';
}
public function getSaucedByCurrentUserAttribute()
{
return 'whatever';
}
}
KISS:
I don't see any reason why you should use trait when your are simply appending attributes.
I would only recommend using trait without a constructor like you were doing, only if you model is getting pretty bulky and you wish to slim down things.
Please also note this not the correct way of appending attribute
protected $appends = array('age','saucedByCurrentUser');
You could do this:
protected $appends = array('age','sauced_by_current_user');
Appends attribute names should the snake_case of its method Name
Edited:
The idea behind appends is to dynamically add fields that doesn't exist in your database table to your model so after you can do like:
$model = FairlyBlandModel ::find(1);
dd($model->sauced_by_current_user);
I am having a problem loading a class. It was suggested to use Autoloader but I do not think this is necessary in my case because the class I need is in the same Bundle as the class that needs it. I dont fully understand the autoloader anyways.
So I have a class
<?php
namespace Nick\AlertBundle\Service;
class ApiService
{
public function AddFlightsAction($alert){
parseResponseData();
}
public function parseResponseData()
{
var_dump("Test");
}
}
So its a basic class, nothing special (I have removed a lot of the functions to cut down on the code). This is the class I need to use.
Now, I have a listener.
<?php
namespace Nick\AlertBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Nick\AlertBundle\Entity\AvailabilityAlert;
use Nick\AlertBundle\Service\ApiService;
class AvailabilityAlertListener
{
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof AvailabilityAlert) {
$uapi = new ApiService();
$uapi->AddFlightsAction($entity);
}
}
}
If I remove what is in addFlightsAction and do a var_dump instead, this works. If I keep it how it is (addFlightsAction calling another function with a var_dump) then I get the error
Attempted to call function parseResponseData from namespace
Nick\AlertBundle\Service (500 Internal Server Error)
Why would this be happening?
I don't think it's a problem of loading.
Change
public function AddFlightsAction($alert){
parseResponseData();
}
To
public function AddFlightsAction($alert){
$this->parseResponseData();
}
I recently watched this video and wanted to change my Laravel controllers so that they had their dependencies managed with Laravel's IoC container. The video talks about creating an interface for a Model and then implementing that interface for the specific data source used.
My question is: when implementing the interface with a class that extends Eloquent and binding that class to the controller so that it is accessible from $this->model, should I also create interfaces and implementations for the Eloquent models which may be returned when calling methods such as $this->model->find($id)? Should there be different classes for the Model and the ModelRepository?
Put it another way: how do I do new Model when my model is in $this->model.
Generally, yes, people doing that pattern (the repository pattern) have an interface which have some methods defined that your app will use:
interface SomethingInterface {
public function find($id);
public function all();
public function paged($offset, $limit);
}
Then you create an implementation of this. If you're using Eloquent, then you can make an Eloquent implementation
use Illuminate\Database\Model;
class EloquentSomething {
protected $something;
public function __construct(Model $something)
{
$this->something = $something;
}
public function find($id)
{
return $this->something->find($id);
}
public function all() { ... }
public function paged($offset, $limit) { ... }
}
Then you make a service provider to put it all together, and add it into app/config/app.php.
use Something; // Eloquent Model
use Namespace\Path\To\EloquentSomething;
use Illuminate\Support\ServiceProvider;
class RepoServiceProvider extends ServiceProvider {
public function register()
{
$app = $this->app;
$app->bind('Namespace/Path/To/SomethingInterface', function()
{
return new EloquentSomething( new Something );
});
}
}
Finally, your controller can use that interface as a type hint:
use Namespace/Path/To/SomethingInterface;
class SomethingController extends BaseController {
protected $something;
public function __construct(SomethingInterface $something)
{
$this->something = $something;
}
public function home() { return $this->something->paged(0, 10); }
}
That should be it. Apologies on any errors, this isn't tested, but is something I do a lot.
Downsides:
More code :D
Upsides:
Able to switch out implementations (instead of EloquentSomething, can use ArraySomething, MongoSomething, whatever), without changing your controller code or any code that uses an implementation of your interface.
Testable - you can mock your Eloquent class and test the repository, or mock your constructor dependency and test your controller
Re-usable - you can App::make() to get the concrete EloquentSomething anywhere in your app and re-use the Something repository anywhere in your code
Repository is a good place to add additional logic, like a layer of cacheing, or even validation rules. Stock mucking about in your controllers.
Finally:, since I likely typed all that out and STILL DIDN'T ANSWER YOUR QUESTION (wtf?!), you can get a new instance of the model using $this->model. Here's an example for creating a new Something:
// Interface:
public function create(array $data);
// EloquentSomething:
public function create(array $data)
{
$something = this->something->newInstance();
// Continue on with creation logic
}
Key is this method, newInstance().
I've used $newModel = $this->model and it's worked for me.
I use Cakephp 2.1 and I need to call a component method which resides in a plugin, from a view helper:
The component is here:
/app/Plugin/Abc/Controller/Component/AbcComponent.php
The helper is here:
/app/View/Helper/SimpleHelper.php
I tried inside helper:
App::import('Component', 'Abc.Abc');
$this->Abc = new Abc(); or $this->Abc = new AbcComponent;
or
$this->Abc = $this->Components->load('Abc.Abc');
inside the controllers this component works with no problem.
I know this isn't recommended (MVC design etc.) but if I don't use it this way I need to duplicate a lot of code. I need to make something like:
MyHelper extends Helper{
$simpleVar = Component->get_data();
}
I use CakePHP 2.4
This is how I successfully call Component from a Helper:
App::uses('AclComponent', 'Controller/Component');
class MyHelper extends AppHelper {
public function myFunction() {
$collection = new ComponentCollection();
$acl = new AclComponent($collection);
// From here you can use AclComponent in $acl
if ($acl->check($aro, $aco) {
// ...
}
}
}
Passing data from CakePHP component to a helper
This seems to be a very nice way to handle this.
I tried working the way you are before, and, although it seems to be a nice immediate solution, in the long run, it is better to just work with the component and helper as 2 separate entities in your controller.
lee
You can put logic in trait and use this from component and helper, if your porpouse is to use the same business logic in different places, to avoid duplication code.
By example
the trait (file app/Lib/NameOfTrait.php or app/PluginName/Lib/NameOfTrait.php)
trait NameOfTrait {
public function theTraitFunc($a, $b) {
// Code here
}
}
The Component:
App::uses('Component', 'Controller');
App::uses('NameOfTrait', 'PluginName.Lib');
class NameOfComponent extends Component {
use NameOfTrait;
private $member;
private $controller;
public function __construct(ComponentCollection $collection, $settings = array()) {
parent::__construct($collection, $settings);
$this->member = $settings['memberName'];
}
function startup(Controller $controller) {
$this->controller = $controller;
}
/**
* Wrap function call of trait function,
* I think the function doesn't have the same name,
* I don't try this but I think is obvious,
* to avoid the function to call itself
*/
public function theTraitFuncWrap($a) {
return $this->theTraitFunc($a, $this->member);
}
}
Do The same for the Helper.
I hope this help someone, bye :)