Has anyone tried or found an example of a class derived from CModel that replicates CActiveRecord functionality with WebServices instead of database connection???
If done with RESTFULL WebServices it would be great. If data is transmitted JSON encoded, wonderful!!...
I'd appretiate your help. Thanks.
I spend a lot of time looking for that as well, I came across this Yii extension on Github:
https://github.com/Haensel/ActiveResource
It allows you to have exactly what you are looking for, the readme isn't updated with the changes reflected in changes.md, so I recommend you read through this document as well.
EActiveResource for Yii
...is an extension for the Yii PHP framework allowing the user to create models that use RESTful services as persistent storage.
The implementation is inspired by Yii's CActiveRecord class and the Ruby on Rails implementation of ActiveResource (http://api.rubyonrails.org/classes/ActiveResource/Base.html).
HINT:
CAUTION: THIS IS STILL AN ALPHA RELEASE!
This project started as a draft and is still under development, so as long is there is no 1.0 release you may experience changes that could break your code. Look at the CHANGES.md file for further information
As there are thousands of different REST services out there that use a thousand different approaches it can be tricky to debug errors. Because of that I added extensive
tracing to all major functions, so you should always be able to see every request, which method it used and how the service responded. Just enable the tracing functionality of Yii
and look for the category "ext.EActiveResource"
INSTALL:
Add the extension to Yii by placing it in your application's extension folder (for example '/protected/extensions')
Edit your applications main.php config file and add 'application.extensions.EActiveResource.*' to your import definitions
Add the configuration for your resources to the main config
'activeresource'=>array(
'class'=>'EActiveResourceConnection',
'site'=>'http://api.aRESTservice.com',
'contentType'=>'application/json',
'acceptType'=>'application/json',
)),
'queryCacheId'=>'SomeCacheComponent')
4.) Now create a class extending EActiveResource like this (don't forget the model() function!):
QUICK OVERVIEW:
class Person extends EActiveResource
{
/* The id that uniquely identifies a person. This attribute is not defined as a property
* because we don't want to send it back to the service like a name, surname or gender etc.
*/
public $id;
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function rest()
{
return CMap::mergeArray(
parent::rest(),
array(
'resource'=>'people',
)
);
}
/* Let's define some properties and their datatypes
public function properties()
{
return array(
'name'=>array('type'=>'string'),
'surname'=>array('type'=>'string'),
'gender'=>array('type'=>'string'),
'age'=>array('type'=>'integer'),
'married'=>array('type'=>'boolean'),
'salary'=>array('type'=>'double'),
);
}
/* Define rules as usual */
public function rules()
{
return array(
array('name,surname,gender,age,married,salary','safe'),
array('age','numerical','integerOnly'=>true),
array('married','boolean'),
array('salary','numerical')
);
}
/* Add some custom labels for forms etc. */
public function attributeLabels()
{
return array(
'name'=>'First name',
'surname'=>'Last name',
'salary'=>'Your monthly salary',
);
}
}
Usage:
/* sends GET to http://api.example.com/person/1 and populates a single Person model*/
$person=Person::model()->findById(1);
/* sends GET to http://api.example.com/person and populates Person models with the response */
$persons=Person::model()->findAll();
/* create a resource
$person=new Person;
$person->name='A name';
$person->age=21;
$person->save(); //New resource, send POST request. Returns false if the model doesn't validate
/* Updating a resource (sending a PUT request)
$person=Person::model()->findById(1);
$person->name='Another name';
$person->save(); //Not at new resource, update it. Returns false if the model doesn't validate
//or short version
Person::model()->updateById(1,array('name'=>'Another name'));
/* DELETE a resource
$person=Person::model()->findById(1);
$person->destroy(); //DELETE to http://api.example.com/person/1
//or short version
Person::model()->deleteById(1);
Hope this helps you
Related
I am implementing Laravel 5.3 Notifications at the moment which is working very nice.
At the moment I am using 'email' as a notifications channel but I want to add 'database' too. I am using different databases/connections for languages and want to store the notifications in a central database / Connection.
How do I use a different database connection for notifications?
I already tried creating a Notifications model but that did not work:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Notifications extends Model
{
protected $connection = 'system';
}
Hackish solution. But tried and tested on a MongoDB connection.
What needs to be modified;
The Notifiable trait
The DatabaseNotification model
Optionally (nothing changes if you are using mysql) modify the HasNotifications trait
Modify the DatabaseNotificationCollection.Again this is useful for a non-mysql connection
Step One : Create a custom Notifiable Trait
Copy the contents from Illuminate\Notifications\Notifiable and create a new file in your custom path...say App\Overrides\Notifications\Notifiable.
Your file will feature two changes...the namespace and you have to load the RoutesNotifications trait since we are not copying it over.
<?php
namespace App\Overrides\Notifications;
use use Illuminate\Notifications\RoutesNotifications;
trait Notifiable{
//The rest of the code remains
}
Step Two : Create a custom DatabaseNotification model
Follow the same procedure as above and copy the contents of the Illuminate\Notifications\DatabaseNotification file to the custom path that we created above...App\Overrides\Notification\DatabaseNotification
This is a standard Eloquent model and the connection change actually happens here
<?php
namespace App\Overrides\Notification;
//Use this if on mongodb.otherwise use to Illuminate\Database\Eloquent\Model
use Jenssegers\Mongodb\Eloquent\Model;
use Illuminate\Notifications\DatabaseNotificationCollection;
class DatabaseNotification extends Model
{
protected $connection = 'YOUR_CONNECTION_NAME_GOES HERE';
}
As of this point this should work if you are on a mysql connection.
To try this out change the Notifiable trait on the user model to use App\Overrides\Notifications\Notifiable. The notifications will use the connection you specified.
Users of MongoDB will have to take extra steps since the most popular driver I know of does not yet support MorphMany relations which are put to use for Laravel notifications.
Since that is not the asked question we leave it at that :-)
On Laravel 5.7 based on #Bernard answer
User.php
<?php
namespace App;
// implement the override Notifiable trait
use App\Traits\Override\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
}
Notifiable.php
<?php
namespace App\Traits\Override;
use Illuminate\Notifications\RoutesNotifications;
trait Notifiable
{
use HasDatabaseNotifications, RoutesNotifications;
}
HasDatabaseNotifications.php
<?php
namespace App\Traits\Override;
use App\Models\Override\MultiConnectionDatabaseNotification;
trait HasDatabaseNotifications
{
/**
* Get the entity's notifications.
*
* #return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function notifications()
{
return $this->morphMany(MultiConnectionDatabaseNotification::class, 'notifiable')->orderBy('created_at', 'desc');
}
/**
* Get the entity's read notifications.
*
* #return \Illuminate\Database\Query\Builder
*/
public function readNotifications()
{
return $this->notifications()->whereNotNull('read_at');
}
/**
* Get the entity's unread notifications.
*
* #return \Illuminate\Database\Query\Builder
*/
public function unreadNotifications()
{
return $this->notifications()->whereNull('read_at');
}
}
MultiConnectionDatabaseNotification.php
<?php
namespace App\Models\Override;
use Illuminate\Notifications\DatabaseNotification as DatabaseNotification;
class MultiConnectionDatabaseNotification extends DatabaseNotification
{
// set your preferred connection here
protected $connection = 'oracle';
}
It's pretty simple, Just add protected $connection = 'YOUR CONNECTION NAME'; at Illuminate\Notifications\DatabaseNotification
That's all and it will work :)
You don't need to create new models if you are going to use one notification table with same connection.
My code will works if ur using different connection for USER model.
I'm whondering, wheather there is an easy way to pupolate doctrine entities from request objects. I'm building a RESTful API with fos/rest-bundle, so I dont need forms.
Do you know a good way to do this, in a very easy and short way?
// POST /api/products
public function postProductsAction(Request $request)
{
$product = new Product();
}
In addition, I'm whondering wheather its possible to inject instances of entities directly in the controller with post requests.
// PUT /api/product/1
// I need this functionality for post requests too
public function putProductAction(Product $product)
{
return $product; // { "id" : "1", "name" : "foo" }
}
Greetings,
--marc
What you need is the most common goal of every REST API. And the best way to do this is to use a serializer, in addition to forms (even if you would prefere to not use forms).
I advise you to read for example this tutorial writen by William Durand. It explains every points very well and uses the JMSSerializerBundle to convert entities through the API.
Scenerio:
Using Yii-rights + Yii-user module in my project. In Rights, I generated operations based on my controller action, under update I added a child UpdateOwn.
For UpdateOwn, the bizrule is suppose to be a simple comparison that the logged in user's ID is equal to $model->user_id field.
Problem:
I understand yii checkaccess allow you to pass in variables as parameters and comparing with your defined bizrule. But how does it work for Yii-rights module? How or what are the data/params passed in to be used in bizrule? How can I define or pass my own data/params?
Yii-rights is a wrapper for standart yii-rbac. In rights module you have web-interface for your RBAC. When you creating AuthItem (Operation in rights web interface) you can define your own bizrule.
Here is code for creating AuthItem:
$item = $this->_authorizer->createAuthItem($formModel->name, $type, $formModel->description, $formModel->bizRule, $formModel->data);
$item = $this->_authorizer->attachAuthItemBehavior($item);
_authorizer here is an example of RAuthorizer class. Then we go to RDbAuthManager, which extends CDbAuthManager, where we createAuthItem function:
public function createAuthItem($name,$type,$description='',$bizRule=null,$data=null)
{
$this->db->createCommand()
->insert($this->itemTable, array(
'name'=>$name,
'type'=>$type,
'description'=>$description,
'bizrule'=>$bizRule,
'data'=>serialize($data)
));
return new CAuthItem($this,$name,$type,$description,$bizRule,$data);
}
This is how created AuthItem, in rights. Personally i prefer to use web interface. It have alot of great fetures and much easier to handle then go to code each time.
Then when we perform checkAccess() on AuthItem we call execute bizRule:
public function executeBizRule($bizRule,$params,$data)
{
return $bizRule==='' || $bizRule===null || ($this->showErrors ? eval($bizRule)!=0 : #eval($bizRule)!=0);
}
This is how RBAC in yii work, and rights is just a cool wrapper for it. Rights doesn't change logic of how things must be done.
So in basic yii-rbac if you want to allow update only Own records you do:
$bizRule='return Yii::app()->user->id==$params["user"]->username;';
$task=$auth->createTask('updateOwnUser','update a your own account',$bizRule);
$task->addChild('updateUser');
Then you call it like this:
$user=$this->loadUser();
$params = array('user' => $user);
if(Yii::app()->user->checkAccess('updateOwnUser', $params){
..................
}
In rights it's already implemented with filters. Only thing what you need to do is add to your controller:
class MyController extends RController{
.............
public function filters()
{
return array(
'rights',
............
);
}
.............
}
So define your bizrule for item in web interface, change your controller code, and actually thats it. To know what variables to use in bizrule you can watch on RightsFilter.php code, where checkAccess() performed.
And on top of all of this i'll say about how checkAccess() does :
For each assigned auth item of the user, it first checks if the bizRule for the assignment returns true.
If true, it calls the item's checkAccess method. If the item's bizRule returns true,
2.1. If the item name is the same as the name passed in the original checkAccess() method, it returns true;
2.2. Otherwise, for every child item, it calls its checkAccess.
Hope this will clarify some aspects of RBAC and help in your task.
The yii-rights module has the following properties:
/**
* #property boolean whether to enable business rules.
*/
public $enableBizRule = true;
/**
* #property boolean whether to enable data for business rules.
*/
public $enableBizRuleData = false;
To set bizrule data via the web interface you have to set $enableBizRuleData = true in your application configuration.
Please note that the UI is limited and you can set data only for Auth-Items not for Auth-Assignments. Also the value for data has to be a serialized PHP variable.
As mentioned by #ineersa you can access $data in unserialized form in your bizRule.
It's also worth noting, that Yii checks first the bizRule for the Auth-Item and then additionally for the Auth-Assignment.
[edit] added example
Auth Item
bizRule
Check if the assignment has all the keys specified in the item data
return BizRule::compareKeys($params, $data, 'Editor');
data
a:1:{s:8:"language";b:1;}
Auth Assignment
Check if the application language matches the assignment data
bizRule
return BizRule::compareApplicationLanguage($params, $data);
data
a:1:{s:8:"language";s:5:"de_de";}
[edit] added code link
Here is the full Helper Code
My silverlight solution has 3 project files
Silverlight part(Client)
Web part(Server)
Entity model(I maintained the edmx along with Metadata in a seperate project)
Metadata file is a partial class with relavent dataannotation validations.
[MetadataTypeAttribute(typeof(User.UserMetadata))]
public partial class User
{
[CustomValidation(typeof(UsernameValidator), "IsUsernameAvailable")]
public string UserName { get; set; }
}
Now my question is where I need to keep this class UsernameValidator
If my Metadata class and edmx are on Server side(Web) then I know I need to create a .shared.cs class in my web project, then add the proper static method.
My IsUserAvailable method intern will call a domainservice method as part of asyc validation.
[Invoke]
public bool IsUsernameAvailable(string username)
{
return !Membership.FindUsersByName(username).Cast<MembershipUser>().Any();
}
If my metadata class is in the same project as my domain service is in then I can call domain service method from my UsernameValidator.Shared.cs class.
But here my entity models and Metadata are in seperate library.
Any idea will be appreciated
Jeff wonderfully explained the asyc validation here
http://jeffhandley.com/archive/2010/05/26/asyncvalidation-again.aspx
but that will work only when your model, metadata and Shared class, all are on server side.
There is a kind of hack to do this. It is not a clean way to do it it, but this is how it would probably work.
Because the .shared takes care of the code generation it doesn't complain about certain compile errors in the #if brackets of the code. So what you can do is create a Validator.Shared.cs in any project and just make sure it generates to the silverlight side.
Add the following code. and dont forget the namespaces.
#if SILVERLIGHT
using WebProject.Web.Services;
using System.ServiceModel.DomainServices.Client;
#endif
#if SILVERLIGHT
UserContext context = new UserContext();
InvokeOperation<bool> availability = context.DoesUserExist(username);
//code ommited. use what logic you want, maybe Jeffs post.
#endif
The compiler will ignore this code part because it does not meet the condition of the if statement. Meanwhile on the silverlight client side it tries to recompile the shared validator where it DOES meet the condition of the if-statement.
Like I said. This is NOT a clean way to do this. And you might have trouble with missing namespaces. You need to resolve them in the non-generated Validator.shared.cs to finally let it work in silverlight. If you do this right you can have the validation in silverlight with invoke operations. But not in your project with models and metadata like you would have with Jeff's post.
Edit: I found a cleaner and better way
you can create a partial class on the silverlight client side and doing the following
public partial class User
{
partial void OnUserNameChanging(string value)
{
//must be new to check for this validation rule
if(EntityState == EntityState.New)
{
var ctx = new UserContext();
ctx.IsValidUserName(value).Completed += (s, args) =>
{
InvokeOperation invop = (InvokeOperation) s;
bool isValid = (bool) invop.Value;
if(!isValid)
{
ValidationResult error = new ValidationResult(
"Username already exists",
new string[] {"UserName"});
ValidationErrors.Add(error;
}
};
}
}
}
This is a method generated by WCF RIA Services and can be easily partialled and you can add out-of-band validation like this. This is a much cleaner way to do this, but still this validation now only exists in the silverlight client side.
Hope this helps
Updated: 09/02/2009 - Revised question, provided better examples, added bounty.
Hi,
I'm building a PHP application using the data mapper pattern between the database and the entities (domain objects). My question is:
What is the best way to encapsulate a commonly performed task?
For example, one common task is retrieving one or more site entities from the site mapper, and their associated (home) page entities from the page mapper. At present, I would do that like this:
$siteMapper = new Site_Mapper();
$site = $siteMapper->findByid(1);
$pageMapper = new Page_Mapper();
$site->addPage($pageMapper->findHome($site->getId()));
Now that's a fairly trivial example, but it gets more complicated in reality, as each site also has an associated locale, and the page actually has multiple revisions (although for the purposes of this task I'd only be interested in the most recent one).
I'm going to need to do this (get the site and associated home page, locale etc.) in multiple places within my application, and I cant think of the best way/place to encapsulate this task, so that I don't have to repeat it all over the place. Ideally I'd like to end up with something like this:
$someObject = new SomeClass();
$site = $someObject->someMethod(1); // or
$sites = $someObject->someOtherMethod();
Where the resulting site entities already have their associated entities created and ready for use.
The same problem occurs when saving these objects back. Say I have a site entity and associated home page entity, and they've both been modified, I have to do something like this:
$siteMapper->save($site);
$pageMapper->save($site->getHomePage());
Again, trivial, but this example is simplified. Duplication of code still applies.
In my mind it makes sense to have some sort of central object that could take care of:
Retrieving a site (or sites) and all nessessary associated entities
Creating new site entities with new associated entities
Taking a site (or sites) and saving it and all associated entities (if they've changed)
So back to my question, what should this object be?
The existing mapper object?
Something based on the repository pattern?*
Something based on the unit of work patten?*
Something else?
* I don't fully understand either of these, as you can probably guess.
Is there a standard way to approach this problem, and could someone provide a short description of how they'd implement it? I'm not looking for anyone to provide a fully working implementation, just the theory.
Thanks,
Jack
Using the repository/service pattern, your Repository classes would provide a simple CRUD interface for each of your entities, then the Service classes would be an additional layer that performs additional logic like attaching entity dependencies. The rest of your app then only utilizes the Services. Your example might look like this:
$site = $siteService->getSiteById(1); // or
$sites = $siteService->getAllSites();
Then inside the SiteService class you would have something like this:
function getSiteById($id) {
$site = $siteRepository->getSiteById($id);
foreach ($pageRepository->getPagesBySiteId($site->id) as $page)
{
$site->pages[] = $page;
}
return $site;
}
I don't know PHP that well so please excuse if there is something wrong syntactically.
[Edit: this entry attempts to address the fact that it is oftentimes easier to write custom code to directly deal with a situation than it is to try to fit the problem into a pattern.]
Patterns are nice in concept, but they don't always "map". After years of high end PHP development, we have settled on a very direct way of handling such matters. Consider this:
File: Site.php
class Site
{
public static function Select($ID)
{
//Ensure current user has access to ID
//Lookup and return data
}
public static function Insert($aData)
{
//Validate $aData
//In the event of errors, raise a ValidationError($ErrorList)
//Do whatever it is you are doing
//Return new ID
}
public static function Update($ID, $aData)
{
//Validate $aData
//In the event of errors, raise a ValidationError($ErrorList)
//Update necessary fields
}
Then, in order to call it (from anywhere), just run:
$aData = Site::Select(123);
Site::Update(123, array('FirstName' => 'New First Name'));
$ID = Site::Insert(array(...))
One thing to keep in mind about OO programming and PHP... PHP does not keep "state" between requests, so creating an object instance just to have it immediately destroyed does not often make sense.
I'd probably start by extracting the common task to a helper method somewhere, then waiting to see what the design calls for. It feels like it's too early to tell.
What would you name this method ? The name usually hints at where the method belongs.
class Page {
public $id, $title, $url;
public function __construct($id=false) {
$this->id = $id;
}
public function save() {
// ...
}
}
class Site {
public $id = '';
public $pages = array();
function __construct($id) {
$this->id = $id;
foreach ($this->getPages() as $page_id) {
$this->pages[] = new Page($page_id);
}
}
private function getPages() {
// ...
}
public function addPage($url) {
$page = ($this->pages[] = new Page());
$page->url = $url;
return $page;
}
public function save() {
foreach ($this->pages as $page) {
$page->save();
}
// ..
}
}
$site = new Site($id);
$page = $site->addPage('/');
$page->title = 'Home';
$site->save();
Make your Site object an Aggregate Root to encapsulate the complex association and ensure consistency.
Then create a SiteRepository that has the responsibility of retrieving the Site aggregate and populating its children (including all Pages).
You will not need a separate PageRepository (assuming that you don't make Page a separate Aggregate Root), and your SiteRepository should have the responsibility of retrieving the Page objects as well (in your case by using your existing Mappers).
So:
$siteRepository = new SiteRepository($myDbConfig);
$site = $siteRepository->findById(1); // will have Page children attached
And then the findById method would be responsible for also finding all Page children of the Site. This will have a similar structure to the answer CodeMonkey1 gave, however I believe you will benefit more by using the Aggregate and Repository patterns, rather than creating a specific Service for this task. Any other retrieval/querying/updating of the Site aggregate, including any of its child objects, would be done through the same SiteRepository.
Edit: Here's a short DDD Guide to help you with the terminology, although I'd really recommend reading Evans if you want the whole picture.