Get has_one relation in SilverStripe - php

I have a DataObject called Applicant and it $has_one Member (this is the SilverStripe Member class).
private static $has_one = array (
'MemberApplicant' => 'Member'
);
When a member is logged in and visits the ApplicationPage I want to be able to populate the form based on the members Applicant data.
I can make this work but I feel like I should be able to access the data easier.
Here is what I do:
$memberID = Member::currentUserID();
$applicant = Applicant::get()->filter('MemberApplicantID', $memberID)->first();
$form->loadDataFrom($applicant);
Shouldn't I be able to instantiate a Member and then call its relative $MemberApplicant?

Shouldn't I be able to instantiate a Member and then call its relative $MemberApplicant?
Of course. I assume you have a 1:1 relation, then you have to define the counter part on Member using $belongs_to (see this diagram)
class Applicant extends DataObject
{
private static $has_one = [
'MemberApplicant' => 'Member'
];
...
class MemberApplicantExtenension extends DataExtension
{
private static $belongs_to = [
'Applicant' => 'Applicant'
];
...
Now add the DataExtension to the Member object in /mysite/_config/config.yml
Member:
extensions:
- MemberApplicantExtenension
and run a dev/build?flush.
Now you're able to get the related Applicant from the Member using built in ORM magic:
//Get the current Member
$member = Member::CurrentUser();
//get the $belongs_to
$applicant = $member->Applicant();

It sounds like you want to be able to avoid an "additional" ORM query by going through the current user's Member record, however this is not how SilverStripe (Or any system that uses SQL JOINs works).
When you add a $has_one to a class, you essentially add a foreign key to that class's table. In your case it will be called MemberApplicantID, which is a foreign key to the Member table's primary key - its ID field. Thus your ORM query needs to go through an Applicant instance first.
Caveat: The above is not entirely true, DataObject does allow you to define a private (SilverStripe config) static $belongs_to on your model classes which lets you query "The other way around". I've never actually done this before, but it does look like you'd declare this on a custom DataExtension that decorates Member and has a value of "Applicant". See DataObject::belongsToComponent().
You can also simplify your existing ORM query slightly without having to instantiate a DataList explicitly, but the route to your target data remains the same:
// SilverStripe >= 3.2
$applicant = DataObject::get_one('Applicant', array('MemberApplicantID = ?' => $memberID));
// SilverStripe < 3.2
$applicant = DataObject::get_one('Applicant', '"MemberApplicantID" = ' . $memberID);

Related

How can I transform / cast a PHP parent object to a child object?

Reason
I got a legacy system with a table containing slugs.
When those records match, it represents some kind of page with a layout ID.
Because these pages can have different resource needs it depends on the layout ID which tables can be joined with.
I use Laravel's Eloquent models.
What I would like is to have a child model that holds the layout specific relations.
class Page extends Model {
// relation 1, 2, 3 that are always present
}
class ArticlePage extends Page {
// relation 4 and 5, that are only present on an ArticlePage
}
However in my controller, in order to know which layout I need, I already have to query:
url: /{slug}
$page = Slug::where('slug', $slug)->page;
if ($page->layout_id === 6) {
//transform $page (Page) to an ArticlePage
}
Because of this I get an instance of Page, but I would like to transform or cast it to an instance of ArticlePage to load it's additional relations. How can I achieve this?
You'll need to look into Polymorphic relations in Laravel to achieve this. A Polymorphic Relation would allow you to retrieve a different model based on the type of field it is. In your Slug model you would need something like this:
public function page()
{
return $this->morphTo('page', 'layout_id', 'id');
}
In one of your service providers, e.g. AppServiceProvider you would need to provide a Morph Map to tell Laravel to map certain IDs to certain model classes. For example:
Relation::morphMap([
1 => Page::class,
// ...
6 => ArticlePage::class,
]);
Now, whenever you use the page relation, Laravel will check the type and give you the correct model back.
Note: I'm not 100% sure on the parameters etc. and I haven't tested but you should be able to work it out from the docs.
If your layout_id is on the Page model, the only solution I see is to add a method to your Page model that is able to convert your existing page into an ArticlePage, or other page type, based on its layout_id property. You should be able to try something like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Page extends Model
{
const LAYOUT_ARTICLE = 6;
protected $layoutMappings = [
// Add your other mappings here
self::LAYOUT_ARTICLE => ArticlePage::class
];
public function toLayoutPage()
{
$class = $this->layoutMappings[$this->layout_id];
if (class_exists($class)) {
return (new $class())
->newInstance([], true)
->setRawAttributes($this->getAttributes());
}
throw new \Exception('Invalid layout.');
}
}
What this does is look for a mapping based on your layout_id property, and then it creates a new class of the correct type, filling its attributes with those from the page you're creating from. This should be all you need, if you take a look at Laravel's Illuminate\Database\Eloquent::newFromBuilder() method, which Laravel calls when it creates new model instances, you can see what's going on and how I've gotten the code above. You should be able to just use it like this:
$page = Slug::where('slug', $slug)
->first()
->page
->toLayoutPage();
That will give you an instance of ArticlePage
As far as I know there is no built in function for this.
But you could do something like this.
$page = Slug::where('slug', $slug)->page;
if ($page->layout_id === 6) {
$page = ArticlePage::fromPage($page);
}
And then on the ArticlePage create the static method
public static function fromPage(Page $page)
{
$articlePage = new self();
foreach($page->getAttributes() as $key => $attribute) {
$articlePage->{$key} = $attribute;
}
return $articlePage
}
Depending on your use-case might be smart to create a static method that does this automatically on the relation page() for Slug.

Where to put model code in ORM ? (Eloquent)

I dont quite understand where my code goes for this orm.
class Brand_model extends MY_Model {
public function add_brand($name, $size)
{
//goal:
// $sql = "insert into brand (name, size_id) values (?,?)";
// $query = $this->db->query($sql, array($name, $size));
$brand = new self();
$brand->name=$name;
$brand->size=$size;
$brand->save();
}
This produces a new row in the database, in the appropriate table, but with no data inside of it. However I am sure those variables are filled. Any ideas?
My design pattern pre orm is to put almost everything in the model. That way if multiple controllers need the same data structure, i call a function once and it handles all the validation/etc.
THanks!
Please find the link for eloquent and codeigniter integration
http://mannyisles.com/using-eloquent-orm-inside-codeigniter.html
To use the ORM, first of all, you would need the connection set up. You can look into this answer. It tells you how to set up eloquent DB connection.
Once it is done, you need to create a model, which would extends the ORM, and do all your DB calls using that model.
You can create a file - let's say, FooModel.php like this:
<?php
namespace Models\FooModel;
use Illuminate\Database\Eloquent\Model;
class FooModel extends Model
{
protected $table = 'sample_table';
protected $fillable = array('comma', 'seperated', 'column', 'names');
protected $hidden = array('id');
public $timestamps = true;
}
?>
$fillable tells eloquent which columns you want to write into. you can ignore the auto incremented columns and the columns with default values.
$timestamps handle your created_at and updated_at columns, if you have any.
When you have to create the row, you can call the eloquent create() function. This function requires an associative array with column names as key and values as the corresponding value you wanna insert to that column.
$row = array('name' => 'Foo',
'email' => 'foo#bar.com'
);
You can then just call the create function, with $row as a parameter.
$response = FooModel::create($row);
If you do a var_dump($response); you can see the status of the create(). true for success or the error message.
For further info you can check out the Docs. It's really not that hard!
Cheers! :)

How to avoid boilerplate code in my models?

I am using CI, however this question applies to models and db persistence in general. I find myself creating methods in models like:-
private function create_joins() {
# Add some joins to the global db object using the active record
}
I do this so I can then perform common joins for a particular model without duplicating the code for creating the joins.
So a select method might be:-
public function get_by_id($id) {
$this->db->select('some_cols');
$this->db->from('some_table');
$this->create_joins();
$this->db->where(array('id' => $id));
etc...
}
Which is fine, but I am wondering if this is the sort of thing that an ORM like datamapper can abstract away?
You should try Doctrine, which is one of the most advanced ORM in PHP:
Using Doctrine, you won't even have to write methods such as get_by_id($id) in your model : they are handled by Doctrine itself.
So you would be able to write:
$entityManager->find("Bug", $id);
An alternative is to use php-activerecord via sparks
An example of Associations
-
class User extends ActiveRecord\Model{
//set tablename
//I would advice to keep Model singular and table names plural
//then this next step is not needed
static $table_name = 'table_one';
//set relationships
static $belongs_to array(
array('Group')
);
static $has_many = array(
array('Comment'),
array('Order')
);
static $has_one = array(
array('Additional_Info')
);
//one key feature is callbacks
//this helps keep controllers clean by letting you pass
//the post data(after validation) in to modify(serialize,json_encode,calculate vat etc)
static $before_create = array('json_encode_my_tags');
public function json_encode_my_tags(){
//$this->tags === $this->input->post('tags');
$tags = explode(',', str_replace(' ', '', $this->tags));
return $this->tags = json_encode($tags, TRUE);
}
}
//calling a model and using eager-loading for associations
$model = User::find(array(
'conditions' => array('id=?', $id) // PK Primary key
'include' => array('comment', 'order', 'additional_info') // eager-loading associations
));
$model->key;
$model->comment->key;
$model->additional_info->key;
$model->order->key;

Custom logic for updating a row

I have this table class:
class Songs extends Zend_Db_Table_Abstract
{
protected $_name = 'songs';
protected $_primary = 'song_id';
protected $_rowClass = 'Song';
}
And a class that extends the class above with some custom logic.
class Song extends Zend_Db_Table_Row_Abstract
{
protected function _insert()
{
print_r($this);
// $this does exist
}
protected function _update()
{
print_r($this);
//$this does not existing when updating a row, why not?
}
}
My problem is that when I'm inserting a new row I can use $this in my custom logic.
$row->save(); // $this exists in _insert()
But it doesn't exist when I'm trying to update a row.
$myRow->update($data, $where); // $this does not exists in _update()
Why does $this not exist when I want to do some custom logic before updating a row?
To update a row, you don't use:
$myRow->update($data, $where);
You use:
$myRow->save();
But trying to use update() on a row object should throw an exception.
So I'm guessing you're actually calling the update() function on the table object, and not the row object.
$songs = new Songs();
//...
$songs->update($data, $where);
At that point the row object is never even used, the query is simply generated from the $data array and the $where clause.
If you want to use the custom _update() method you would need to do something like:
$songs = new Songs();
$song = $songs->find($id)
//change some data
$song->save();
Of course is also perfectly valid to add custom logic at the table level, and should be noted while calling an update or insert from the table object does not use the row object, calling save() on the row object proxies the table object.
For example, from the Zend_Db_Table_Row _doInsert() function:
$this->_insert();
//...
$primaryKey = $this->_getTable()->insert($data);
So if you have custom logic that you want to use every time you update a row (whether you update from the table object or the row object), it should be put into the table object.
From the Zend_Db_Table_Row docs:
If you need to do custom logic in a specific table, and the custom logic must occur for every operation on that table, it may make more sense to implement your custom code in the insert(), update() and delete() methods of your Table class. However, sometimes it may be necessary to do custom logic in the Row class.

findByExample in Doctrine

Is there a method in Doctrine like Hibernate's findByExample method?
thanks
You can use the findBy method, which is inherited and is present in all repositories.
Example:
$criteria = array('name' => 'someValue', 'status' => 'enabled');
$result = $em->getRepository('SomeEntity')->findBy($criteria);
You can create findByExample method in one of your repositories using a definition like this:
class MyRepository extends Doctrine\ORM\EntityRepository {
public function findByExample(MyEntity $entity) {
return $this->findBy($entity->toArray());
}
}
In order for this to work, you will have to create your own base class for the entities, implementing the toArray method.
MyEntity can also be an interface, which your specific entities will have to implement the toArray method again.
To make this available in all your repositories, ensure that you are extending your base repository class - in this example, the MyRepository one.
P.S I assume you are talking about Doctrine 2.x
Yes.
Let's say you have a model called Users. You have the following two classes
abstract class Base_User extends Doctrine_Record
{
//define table, columns, etc
}
class User extends Base_User
{
}
in some other object you can do
$user = new User;
//This will return a Doctrine Collection of all users with first name = Travis
$user->getTable()->findByFirstName("Travis");
//The above code is actually an alias for this function call
$user->getTable()->findBy("first_name", "Travis");
//This will return a Doctrine Record for the user with id = 24
$user->getTable()->find(24);
//This will return a Doctrine Collection for all users with name=Raphael and
//type = developer
$user->getTable()
->findByDql("User.name= ? AND User.type = ?", array("Raphael", "developer"));
$users = $userTable->findByIsAdminAndIsModeratorOrIsSuperAdmin(true, true, true);
See http://www.doctrine-project.org/projects/orm/1.2/docs/manual/dql-doctrine-query-language/en

Categories