Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

ViewableData $objCache should be disabled by children classes #2854

Open
g4b0 opened this Issue · 5 comments

4 participants

@g4b0

The problem:

A normal eCommerce cart implementation:

class Cart extends ViewableData {
  private Products; /* ArrayList of Cartable objects */

  [...]
}

class Cartable extends ViewableData {
  public Quantity;

  [...]
}

Obviuously a Cart object is stored into the session, and the Products ArrayList get filled by Cartable objects. Once the Cart is displayed through a SS template, if the user increase the quantity of a Cartable object it works fine under the hood, but the initial quantity is showed to the user.

The problem is here: https://github.com/silverstripe/silverstripe-framework/blob/3.1/view/ViewableData.php#L357

Once the $Quantity is displayed the first time it's cached in ViewableData::objCache and there's no way to purge it.

Maybe a protected boolean can solve this?

@g4b0 g4b0 referenced this issue from a commit
Commit has since been removed from the repository and is no longer available.
@g4b0 g4b0 referenced this issue from a commit
Commit has since been removed from the repository and is no longer available.
@simonwelsh simonwelsh added the 3.1 label
@g4b0 g4b0 referenced this issue from a commit in g4b0/silverstripe-framework
@g4b0 g4b0 Issue #2854: Permit ViewableData children to avoid caching (useful fo…
…r frontend Session work)
4b95352
@hafriedlander

Hi @g4b0 are you just serialising the whole Cart object to store in the Session, or are you changing Quantity after displaying it in a template? I'd be interested in seeing the flow there, because normally rendering a template is the very last thing you do in a request, and you definitely shouldn't be triggering modifications during template rendering.

I'll also ping @sminnee since I think he made the original decision to have variables in templates be cached? Sam, can you remember if we did this just for performance, or was there another reason?

@tractorcow
Collaborator

He could be doing $html = $this->renderWith('Template'); and then doing something with the result before rendering the rest of the page. It would be good to see the use case though. :)

@tractorcow
Collaborator

A better use case is that he wants to send an email (which may be passed through a template) before the object is used in a page template for rendering.

@g4b0

Hi,
I have an Extension that extends Page_Controller and every Controller I need:


class EcExtension extends Extension {

    private $User;
    private $Cart;

    /**
     * Se l'utente è loggato instanzia l'oggetto dall'array in sessione.
     */
    public function __construct() {
        parent::__construct();
        if ($this->EcLoggedIn()) {
            $this->User = EcUser::get();
            $this->Cart = EcCart::get();
        }
    }

    /**
     * Determina se l'utente Ec è loggato o meno
     * @return boolean
     */
    public function EcLoggedIn() {
        return EcGest::EcLoggedIn();
    }

    /**
     * Utente
     * @return EcUser
     */
    public function EcUser() {
        return $this->User;
    }

    /**
     * Carrello
     * @return EcCart
     */
    public function EcCart() {
        return $this->Cart;
    }

    [.. other stuff ..]
}

My EcCart code is the following

class EcCart extends ViewableData {

    /* Carrello */
    private $Cart;

    /* Utente */
    private $User;

    /* Modify SS aggressive caching, see https://github.com/silverstripe/silverstripe-framework/pull/2855 */
    protected $cached = false;

    public function __construct(\EcUser $u) {
        parent::__construct();

        [.. various stuff ..]

        $this->Cart = new ArrayList();
        $this->User = $u;

        [.. various stuff ..]
    }

    /* *************************************************************************
     * SESSIONS
     * ************************************************************************* */

    /**
     * Salvo il carrello in sessione
     * @param type $name
     */
    public function save($name = 'EcCart') {
        Session::set($name, $this);
    }

    /**
     * Elimino il carrello dalla sessione
     * @param type $name
     */
    public function destroy($name = 'EcCart') {
        Session::clear($name);
    }

    /**
     * Preleva il carrello dalla sessione, verificandone l'integrità
     * @param string $name
     * @return EcUser
     */
    public static function get($name = 'EcCart') {
        $retVal = Session::get($name);
        if (get_class($retVal) == 'EcCart') {
            return $retVal;
        } else {
            throw new Exception("La variabile di session [$name] non contiene un oggetto di tipo [EcCart]");
        }
    }

}

Finally into the extended controller I can access to the cart through $this->EcCart() and before rendering it I user $this->EcCart()->save().

I never use renderWith, I just print $EcCart into the template. For example in the cart overview:

<% if $EcCart.getSoftware %>
<div class="panel panel-default">
    <div class="panel-heading">
        <h3>Software</h3>
    </div>
    <div class="panel-body">
        <table border="0" cellpadding="8" cellspacing="0" class="table table-hover table-software-cart">
            <thead>
                <th align="left" valign="top">Quantità</th>
                <th align="left" valign="top">Software</th>
                <th align="left" valign="top">Descrizione</th>
                <th align="right" valign="top" class="text-right">Prezzo unitario</th>
            </thead>
            <% loop $EcCart.getSoftware %>
                <% include CartRow %>
            <% end_loop %>
        </table>
    </div>
</div>
<% end_if %>

getSoftware() loops over EcSoftware products, that inherit from EcCartable. CartRow is the following:

<tr id="Form_EcOrdine_software_{$LineaID}_{$ID}">

    <td align="left" valign="top">

        <% if $Quantity == 1 && $canRemove == true || $Quantity > 1 %>
            <a><span class="decrease-quantity">-</span></a>
        <% else %>
            <a><span class="decrease-quantity disabled">-</span></a>
        <% end_if %>

            <span id="Form_EcOrdine_software_{$LineaID}_{$ID}_qty" class="cart-item-quantity">$Quantity</span>


        <% if $Quantity < 10 && $canIncrease == true %>
            <a><span class="increase-quantity">+</span></a>
        <% else %>
            <a><span class="increase-quantity disabled">+</span></a>
        <% end_if %>
    </td>

    <td align="left" valign="top">

        <small style="color: $Linea.colore">$Linea.nome_www</small><br>
        <% if $Link %><a href="$Link">$Descrizione</a><% else %>$Descrizione<% end_if %>
            <br>Vers. $Versione
    </td>

    <td align="left" valign="top">
        <% if $Tipo = A %>
            Aggiornamento da Vers. $getVersionePosseduta
        <% else %>
            Nuovo acquisto
        <% end_if %>
    </td>

    <td align="right" valign="top">
        <% if $PrezzoListino != $Prezzo %>
        <strike><span id="prezzo_listino_{$Linea.ec_id}_{$ID}">$getPrezzoListino(true)</span> €</strike> <br>
        <% end_if %>
        <span id="prezzo_{$Linea.ec_id}_{$ID}">$getPrezzo(true)</span> €
    </td>
</tr>

I hope my code was clear, anyway you can view a simplified version of the above behaviour in the simple UnitTest attached to my pull request: https://github.com/silverstripe/silverstripe-framework/pull/2855/files#diff-fe8cd42c5bd6de52f295ca93c322c79bR160

Into the unit test the obj changes during a single flow, while in my ecommerce application it canges during the whole session, but always the first renderd version is served without my patch applied.

@g4b0

Hey @tractorcow @hafriedlander @sminnee any news about my PR? I'm upgrading some of my sites that are affected by this bug and is a quite annoying task to manually apply the patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.