ActiveRecord en Method Chaining

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Wouter J

Wouter J

29/02/2012 16:54:57
Quote Anchor link
Ik probeer een handige ActiveRecord klasse in PHP te maken, gebaseerd op die van Ruby on Rails.

Maar nu loop ik vast bij de method chaining. In rails kun je zoiets doen:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
# haal alle posts op met author = Wouter J
Post.where(:author => 'Wouter J')

Maar ook:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
# Haal 10 berichten, geordend bij postDate, van de author Wouter J
Post.where(:author => 'Wouter J').order(:postDate).limit(10)


De where method moet dus een result terug geven als het aan het eind staat, maar $this als er daarna functies gebruikt worden.

Ik denk dat het in normale PHP niet op te lossen is, maar heeft iemand er misschien een workaround voor?

PS: Een kijkje nemen in de Rails source code is ook al in me op gekomen. Maar daar wordt je niet veel wijzer van: https://github.com/rails/rails/tree/master/activerecord
Gewijzigd op 29/02/2012 17:08:35 door Wouter J
 
PHP hulp

PHP hulp

26/12/2024 01:30:16
 
Erwin H

Erwin H

29/02/2012 17:41:23
Quote Anchor link
Wat als je de resultaten altijd in een property opslaat en het object teruggeeft? Vervolgens maak je een functie die alleen de resultaten uit het property haalt en teruggeeft. Dan kan je altijd door met chaining, en aan het einde kan je bijvoorbeeld getResults() aanroepen. Je hebt dan dus wel een extra functie aanroep aan het einde nodig, maar daarvoor zou je zo lang door kunnen gaan als je zelf wilt:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

class Chain_Test{
  private $results = 0;

  public function add( $value ){
    $this->results += $value;
    return $this;
  }


  public function subtract( $value ){
    $this->results -= $value;
    return $this;
  }
  

  public function divide( $value ){
    $this->results /= $value;
    return $this;
  }
  

  public function multiply( $value ){
    $this->results *= $value;
    return $this;
  }


  public function getResults(){
    return $this->results;
  }
}


$object = new Chain_Test;

echo $object->add(10)->subtract(1)->multiply(2)->getResults();

?>

Net getest en het werkt.
Gewijzigd op 29/02/2012 17:41:45 door Erwin H
 
Wouter J

Wouter J

29/02/2012 17:48:23
Quote Anchor link
@Erwin, dat is inderdaad een workaround waar ik ook al een beetje aan zat te denken. Niet helemaal volgens de OO regels, maar dat is ActiveRecord natuurlijk ook niet.

Ga voorlopig zo werken, maar mocht iemand nog een geweldige ingeving hebben dan zeggen ze het maar.

Offtopic:
Waarom is PHP niet zo mooi gescript als Ruby :S
 
Erwin H

Erwin H

29/02/2012 17:53:01
Quote Anchor link
Tja, OO regels zijn er om gebroken te worden natuurlijk.....
 
Pim -

Pim -

29/02/2012 21:52:37
Quote Anchor link
Dat kan best hoor. Al zou ik het iets anders doen. Je geeft de AR abstract klasse een functie get(). Deze retourneerd een query of statement-object. Met de method chaining kan je deze dan configureren en met getIterator() van IteratorAggregate kan je dan de query uitvoeren en over de resultaten itereren.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
abstract class ActiveRecord
{
    // Bevat mapping data
    protected $mapping;

    public static function get()
    {

        return new ActiveRecordStatement($this->mapping, $this->db);
    }
}


class ActiveRecordStatement implements IteratorAggregate
{
    protected $mapping;
    protected $db;

    protected $where;

    public function __construct(array $mapping, Database $db)
    {

        //...
    }

    public function where(array $where)
    {

        $this->where = $where;
        return $this;
    }


    public function getIterator()
    {

        $items = $this->db->query($this->buildQuery());
        return new ArrayIterator($items);
    }


    protected function buildQuery()
    {

        // Bouw de query op adv de configuratie
    }
}


// Dus
class Post extends ActiveRecord
{
    $this->mapping = array(...);
}

foreach(Post::get()->where(['author' => 'Pim']) as $post) { // PHP 5.4 ;)
    print $post;
}

?>

Duidelijk?
 
Wouter J

Wouter J

29/02/2012 23:43:04
Quote Anchor link
Ik begrijp het voorbeeld beter dan de uitleg... ;) Ziet er wel gaaf uit!

Wat ik begrijp van de IteratorAggregate is dat als je de klasse oproept in een foreach hij de method getIterator() aanroept, die vervolgens een ArrayIterator teruggeeft? Hierdoor kun je een object ombouwen tot array?
Die static get method heb je alleen gemaakt zodat je geen instance van Post hoeft aan te maken, toch?

Vooral de 'uitleg' van Fabian en Symfony source code helpen me een beetje in het doorkrijgen van Iterators... Wel leuk, weer wat nieuws geleerd!
 
Ozzie PHP

Ozzie PHP

01/03/2012 00:37:57
Quote Anchor link
Pim - op 29/02/2012 21:52:37:
Je geeft de AR abstract klasse een functie get(). Deze retourneerd een query of statement-object. Met de method chaining kan je deze dan configureren en met getIterator() van IteratorAggregate kan je dan de query uitvoeren en over de resultaten itereren.
....
Duidelijk?

Euhhh :-/ ...ja hoor, hééél duidelijk ;)
 
Wouter J

Wouter J

01/03/2012 12:47:09
Quote Anchor link
Nog even een vraagje:

Dit is nu alleen voor het krijgen van records. Moet ik nu ook aparte Statement klasse maken voor write, create en delete? En is de ActiveRecord klasse dus eigenlijk niks anders dan een klasse die 4 andere klassen bij elkaar houd en voor 1 algemeen aanspreekpunt zorgt?
Moeten die Statement klasse nou allemaal een eigen db property krijgen, of zou ik die in de ActiveRecord klasse zetten en de Statement klassen deze laten extenden?

Pff, ik ga eerst maar wat meer lezen over AR voordat ik hier aan kan beginnen...
 
Mark Kazemier

Mark Kazemier

04/03/2012 14:56:35
Quote Anchor link
Volgens mij ben je meer op zoek naar het Query Object Pattern. Dit pattern omschrijft hoe je een SQL query kan abstraheren in klassen.
 
Pim -

Pim -

05/03/2012 18:30:57
Quote Anchor link
@Wouter,
Writes, deletes en updates zijn vaak heel simpel, omdat je precies weet welk object je wil aanpassen. Dat zou ik dan in de AR laag doen. Dus $post->delete().
Ik zou de DB in een statisch veld van de AR klasse doen. Dan beschikt elk object erover en kan je met r9 in het voorbeeld dat meegeven aan de statement klasse.

@Mark,
Dat is idd nog nodig en heb ik niet in het voorbeeld gestopt, leek me niet zo nodig. Mijn voorbeeld laat vooral zien hoe je de communicatie met de AR laag en de DB laag regelt. Hoe je dan de SQL opbouwt, mag je zelf bedenken (Doctine DBAL SQL builder is mss wel de moeite waard).
 
Mark Kazemier

Mark Kazemier

06/03/2012 10:14:33
Quote Anchor link
@Pim:

Ik zou mijn domeinmodel juist niet afhankelijk willen hebben van de persistence layer. Wat in jouw voorbeeld wel is. De Post klasse moet opeens weten hoe hij zichzelf opslaat. Het liefst houdt je alleen business logic in je Post klasse en maak je een aparte klasse om de Post op te slaan.

Voorbeeldje:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
<?php
$post
= new Post();
$post->setBody('Lorem ipsum...');

$postRepository->persist($post);
?>


En voor het ophalen:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
<?php
$post
= $postRepository->findById(1);

$posts = $postRepository->findAll()->where(array('author'=>'Markkaz'))->orderby(array('post_datum'=>'desc'));
?>
 
Pim -

Pim -

06/03/2012 13:37:01
Quote Anchor link
Ja, natuurlijk. Maar we hebben het hier over Active Record, waarbij die lagen inherent gekoppeld zijn. Een DataMapper oid is natuurlijk mooier, maar daar gaat het topic niet over.
 



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.