doujia1988 2014-10-31 09:38
浏览 28
已采纳

在会话和后退按钮中存储表单

I'm trying to achieve the following scenario:

1. user display the page addBook.php
2. user starts filling the form
3. but when he wants to select the book Author from the Author combo box, the Author is not yet created in the database so the user clicks a link to add a new Author
5. user is redirected to addAuthor.php
6. the user fill the form and when he submits it, he goes back to addBook.php with all the previous data already present and the new Author selected.

The things is: I have scenarios where there is more than one level of recursion. (Example: Add Book => Add Author => Add Country)

How can I do that?

At step #3, the link submit the form so that I can save it in session.
To handle recursion, I can use a Stack and push the current from on the Stack each time I click a link. And pop the last form of the Stack when the user completes the action correctly or click a cancel button.

My problem is:

How can I handle the back button of the browser? If instead of clicking the "cancel" button, the user click on the back button, how could I kown that I need to pop the last element?

Do you known some common pattern to achieve that?

  • 写回答

2条回答 默认 最新

  • doulao3905 2014-11-03 09:34
    关注

    This is the solution I developed to answer my problem.

    As the problem was not a client side problem but truly a server side one. Following the php classes I used in my project:

    First the main class of the stack functionality. The inclusion need to be done before the session_start as the object will be stored in the session

    class Stack {
    
        private $stack;
        private $currentPosition;
        private $comeFromCancelledAction = false;
    
        public function __construct() {
            $this->clear();
        }
    
        /* ----------------------------------------------------- */
        /* PUBLICS METHODS                                       */
        /* ----------------------------------------------------- */
    
        /**
         * Clear the stack history
         */
        public function clear() {
            $this->stack = array();
            $this->currentPosition = -1;
        }
    
        /**
         * get the current position of the stack
         */
        public function getCurrentPosition() {
            return $this->currentPosition;
        }
    
        /**
         * Add a new element on the stack
         * Increment the current position
         *
         * @param $url the url to add on the stack
         * @param $data optionnal, the data that could be stored with this $url
         */
        public function add($url, &$data = array()) {
            if (count($this->stack) != $this->currentPosition) {
                // the currentPosition is not the top of the stack
                // need to slice the array to discard dirty urls
                $this->stack = array_slice($this->stack, 0, $this->currentPosition+1);
            }
    
            $this->currentPosition++;
            $this->stack[] = array('url' => $url, 'data' => $data, 'previousData' => null, 'linked_data' => null);
        }
    
        /**
         * Add the stack position parameter in the URL and do a redirect
         * Exit the current script.
         */
        public function redirect() {
            header('location:'.$this->addStackParam($this->getUrl($this->currentPosition)), 301);
            exit;
        }
    
        /**
         * get the URL of a given position
         * return null if the position is not valid
         */
        public function getUrl($position) {
            if (isset($this->stack[$position])) {
                return $this->stack[$position]['url'];
            } else {
                return null;
            }
        }
    
        /**
         * get the Data of a given position
         * return a reference of the data
         */
        public function &getData($position) {
            if (isset($this->stack[$position])) {
                return $this->stack[$position]['data'];
            } else {
                return null;
            }
        }
    
        /**
         * Update the context of the current position
         */
        public function storeCurrentData(&$data) {
            $this->stack[$this->currentPosition]['data'] = $data;
        }
    
        /**
         * store some data that need to be fixed in sub flow
         * (for example the id of the parent object)
         */
        public function storeLinkedData($data) {
            $this->stack[$this->currentPosition]['linked_data'] = $data;
        }
    
        /**
         * Update the context of the current position
         */
        public function storePreviousData(&$data) {
            $this->stack[$this->currentPosition]['previousData'] = $data;
        }
    
        /**
         * Compute all linked data for every positions before the current one and return an array
         * containing all keys / values
         * Should be called in sub flow to fixed some data.
         *
         * Example: if you have tree pages: dad.php, mum.php and child.php
         * when creating a "child" object from a "dad", the dad_id should be fixed
         * but when creating a "child" object from a "mum", the mum_id should be fixed and a combo for choosing a dad should be displayed
         */
        public function getLinkedData() {
            $totalLinkedData = array();
            for($i = 0; $i < $this->currentPosition; $i++) {
                $linkedData = $this->stack[$i]['linked_data'];
                if ($linkedData != null && count($linkedData) > 0) {
                    foreach($linkedData as $key => $value) {
                        $totalLinkedData[$key] = $value;
                    }
                }
            }
            return $totalLinkedData;
        }
    
        /**
         * Main method of the Stack class.
         * Should be called on each page before any output as this method should do redirects.
         *
         * @param $handler StackHandler object that will be called at each step of the stack process
         *                 Let the caller to be notified when something appens.
         * @return the data 
         */
        public function initialise(StackHandler $handler) {
            if (!isset($_GET['stack']) || !ctype_digit($_GET['stack'])) {
                // no stack info, acces the page directly
                $this->clear();
                $this->add($this->getCurrentUrl()); //add the ?stack=<position number>
                $this->storeLinkedData($handler->getLinkedData());
                $this->redirect(); //do a redirect to the same page
            } else {
                // $_GET['stack'] is set and is a number
                $position = $_GET['stack'];
    
                if ($this->currentPosition == $position) {
                    // ok the user stay on the same page
                    // or just comme from the redirection
    
                    if (!empty($_POST['action'])) {
                        // user submit a form and need to do an action
    
                        if ($_POST['action'] == 'cancel') {
                            $currentData = array_pop($this->stack);
                            $this->currentPosition--;
    
                            $handler->onCancel($currentData);
    
                            // redirect to the next page with ?stack=<current position + 1>
                            $this->redirect();
                        } else {
                            // store the action for future use
                            $this->stack[$this->currentPosition]['action'] = $_POST['action'];
    
                            $currentData = $this->getData($this->currentPosition);
                            list($currentData, $nextUrl) = $handler->onAction($currentData, $_POST['action']);
    
                            // store current form for future use
                            $this->storeCurrentData($currentData);
    
                            // add the new page on the stack
                            $this->add($nextUrl);
    
                            // redirect to the next page with ?stack=<current position + 1>
                            $this->redirect();
                        }
                    } else if (isset($this->stack[$this->currentPosition]['action'])) {
                        // no action, and an action exists for this position
    
                        $currentData = $this->getData($this->currentPosition);
                        $action = $this->stack[$this->currentPosition]['action'];
    
                        if ($this->comeFromCancelledAction) {
                            //we return from a cancelled action
                            $currentData = $handler->onReturningFromCancelledAction($action, $currentData);
                            $this->comeFromCancelledAction = false;
                        } else {
                            $previousData = $this->getPreviousData();
                            if ($previousData != null) {
                                //we return from a sucessful action
                                $currentData = $handler->onReturningFromSuccesAction($action, $currentData, $previousData);
                                $this->resetPreviousData();
                            }
                        }
                        $this->storeCurrentData( $currentData );
                    }
    
                    $currentData = $this->getData($this->currentPosition);
                    if ($currentData == null) {
                        $currentData = $handler->getInitialData();
                        $this->storeCurrentData( $currentData );
                    }
    
                    return $currentData;
    
                } else if ($this->getUrl($position) == $this->getCurrentUrl()) {
                    // seems that the user pressed the back or next button of the browser
    
                    // set the current position
                    $this->currentPosition = $position;
    
                    return $this->getData($position);
    
                } else {
                    // the position does not exist or the url is incorrect
    
                    // redirect to the last known position
                    $this->redirect();
                }
            }
        }
    
        /**
         * call this method after completing an action and need to redirect to the previous page.
         * If you need to give some data to the previous action, use $dataForPreviousAction
         */
        public function finishAction($dataForPreviousAction = null) {
            $pop = array_pop($this->stack);
            $this->currentPosition--;
    
            $this->storePreviousData($dataForPreviousAction);
    
            $this->redirect();
        }
    
        /* ----------------------------------------------------- */
        /* PRIVATE METHODS                                       */
        /* ----------------------------------------------------- */
    
        /**
         * get the previous data for the current position
         * used when a sub flow finish an action to give some data to the parent flow
         */
        private function &getPreviousData() {
            if (isset($this->stack[$this->currentPosition])) {
                return $this->stack[$this->currentPosition]['previousData'];
            } else {
                return null;
            }
        }
    
        /**
         * get the current url without the stack parameter
         * 
         * Attention: this method calls "basename" on PHP_SELF do strip the folder structure
         * and assume that every pages are in the same directory.
         *
         * The "stack" parameter is removed from the query string
         *
         * Example: for the page "http://myserver.com/path/to/a.php?id=1&stack=2"
         * PHP_SELF will be: /path/to/a.php
         * QUERY_STRING wille be: id=1&stack=2
         * This method will return: "a.php?id=1"
         */
        private function getCurrentUrl() {
            $basename = basename($_SERVER['PHP_SELF']);
            if ($_SERVER['QUERY_STRING'] != '') {
                return $basename.$this->removeQueryStringKey('?'.$_SERVER['QUERY_STRING'], 'stack');
            } else {
                return $basename;
            }
        }
    
        /**
         * add the "stack" parameter in an url
         */
        private function addStackParam($url) {
            return $url . (strpos($url, '?') === false ? '?' : '&') . 'stack=' . $this->currentPosition;
    
        }
    
        /**
         * Usefull private method to remove a key=value from a query string.
         */
        private function removeQueryStringKey($url, $key) {
            $url = preg_replace('/(?:&|(\?))'.$key.'=[^&]*(?(1)&|)?/i', "$1", $url);
            return $url != '?' ? $url : '';
        }
    
        /**
         * reset the previous data so that the data are not used twice
         */
        private function resetPreviousData() {
            $this->stack[$this->currentPosition]['previousData'] = null;
        }
    }
    

    Then define the abstract StackHandler class

    abstract class StackHandler {
    
        /**
         * return the initial data to store for this current page
         */
        public function &getInitialData() {
            return null;
        }
    
        /**
         * return an array containing the key/values that need to be fixed in sub flows
         */
        public function getLinkedData() {
            return null;
        }
    
        /**
         * user ask to go to a sub page
         */
        public function onAction(&$currentData, $action) {
            $currentData = $_POST;
            $nextUrl = $_POST['action'];
            return array($currentData, $nextUrl);
        }
    
        public function onCancel(&$currentData) {
    
        }
    
        public function onReturningFromCancelledAction($action, &$currentData) {
    
        }
    
        public function onReturningFromSuccesAction($action, &$currentData, $previousData) {
    
        }
    }
    

    Then add the following lines at the top of your pages. Adapt the handler it to fit your needs.

    // be sure that a stack object exist in the session
    if (!isset($_SESSION['stack'])) {
        $_SESSION['stack'] = new Stack();
    }
    
    $myDad = $_SESSION['stack']->initialise(new DadStackHandler());
    
    class DadStackHandler extends StackHandler {
    
        /**
         * return the initial data to store for this current page
         */
        public function &getInitialData() {
            if(! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])){
                // update
                $myDad = new Dad($_GET['id_dad']);
            } else {
                // creation
                $myDad = new Dad();
            }
            return $myDad;
        }
    
        /**
         * return an array containing the key/values that need to be fixed in sub flows
         */
        public function getLinkedData() {
            $linkedData = array();
            if (! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])) {
                $linkedData['id_dad'] = $_GET['id_dad'];
            }
            return $linkedData;
        }
    
        /**
         * user ask to go to a sub page
         */
        public function onAction(&$myDad, $action) {
            //in order not to loose user inputs, save them in the current data
            $myDad->name = $_POST['name'];
    
            $nextUrl = null;
    
            // find the next url based on the action name
            if ($action == 'child') {
                $nextUrl = 'child.php';
            }
    
            return array($myDad, $nextUrl);
        }
    
        public function onCancel(&$myDad) {
            // probably nothing to do, leave the current data untouched
            // or update current data
            return $myDad;
        }
    
        public function onReturningFromCancelledAction($action, &$myDad) {
            // probably nothing to do, leave the current data untouched
            // called when returning from child.php
            return $myDad;
        }
    
        public function onReturningFromSuccesAction($action, &$myDad, $newId) {
            // update the id of the foreign field if needed
            // or update the current data
    
            // not a good example as in real life child should be a list and not a foreign key
            // $myDad->childId = $newId; 
    
            $myDad->numberOfChildren++;
    
            return $myDad;
        }
    }
    
    
    ...
    if (user submit form and all input are correct) {
        if ($myDad->save()) {
            // the user finish an action, so we should redirect him to the previous one
            if ($_SESSION['stack']->getCurrentPosition() > 0) {
                $_SESSION['stack']->finishAction($myDad->idDad);
            } else {
                // default redirect, redirect to the same page in view more or redirect to a list page
            }
        }
    }
    

    I hope this could help others.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 AT89C51控制8位八段数码管显示时钟。
  • ¥15 真我手机蓝牙传输进度消息被关闭了,怎么打开?(关键词-消息通知)
  • ¥15 下图接收小电路,谁知道原理
  • ¥15 装 pytorch 的时候出了好多问题,遇到这种情况怎么处理?
  • ¥20 IOS游览器某宝手机网页版自动立即购买JavaScript脚本
  • ¥15 手机接入宽带网线,如何释放宽带全部速度
  • ¥30 关于#r语言#的问题:如何对R语言中mfgarch包中构建的garch-midas模型进行样本内长期波动率预测和样本外长期波动率预测
  • ¥15 ETLCloud 处理json多层级问题
  • ¥15 matlab中使用gurobi时报错
  • ¥15 这个主板怎么能扩出一两个sata口