public function performIndex() { $this->prepareControl(); if ($this->isListing) { /* * Provide default action on set of selected model's items. */ $this->browser->setPagerVolatility("none"); if (is_callable($this->browserCustomizer)) { call_user_func($this->browserCustomizer, $this, $this->browser); } else { // try generate some commonly useful databrowser $controller = $this; $this->browser->addColumn('label', \de\toxa\txf\_L('Label'), false, function ($value, $propName, $record, $id) use($controller) { return $controller->getModel()->getMethod('select')->invoke(null, $controller->source, $id)->describe(); })->setRowCommander(function ($id, $data) use($controller) { $items = array('view' => markup::link(sprintf($controller->getUrls()->view, $id), \de\toxa\txf\_L('view item'), 'controller-list-action-view'), 'edit' => markup::link(sprintf($controller->getUrls()->edit, $id), \de\toxa\txf\_L('edit item'), 'controller-list-action-edit'), 'delete' => markup::link(sprintf($controller->getUrls()->delete, $id), \de\toxa\txf\_L('delete item'), 'controller-list-action-delete')); if (!user::current()->isAuthenticated()) { unset($items['edit'], $items['delete']); } return implode(' ', array_filter($items)); }); if (user::current()->isAuthenticated()) { $controller->setPanelControl('add', markup::link($controller->getUrls()->add, \de\toxa\txf\_L('add item'), 'controller-list-action-add')); } } if (user::current()->isAuthenticated()) { if (!$this->hasPanelControl('add')) { $this->setPanelControl('add', markup::link(context::selfURL(false, array_merge(array_pad(array(), $this->modelClass->getMethod('idSize')->invoke(null), 0), array('edit'))), \de\toxa\txf\_L('Add'))); } } $this->browser->processInput(); return $this->browser->getCode(); } /** * Provide default action on single instance of selected model. */ $this->editor->mayEdit(false)->mayDelete(false); if (is_callable($this->viewCustomizer)) { call_user_func($this->viewCustomizer, $this, false); } else { $data = $this->editor->item()->published(); // reduce relations foreach ($data as $key => $value) { if (is_array($value)) { $data[$key] = implode(', ', $value); } } return html::arrayToCard($data); } if (user::current()->isAuthenticated()) { if (!$this->hasPanelControl('edit')) { $this->setPanelControl('edit', markup::link(context::selfURL(false, array_merge($this->item->id(), array('edit'))), \de\toxa\txf\_L('Edit'))); } } if ($this->editor->processInput($this->editorValidator)) { txf::redirectTo($this->getUrls()->list); } return $this->editor->render(); }
/** * Associates editor instance with single item of related model. * * Providing null is available for convenience, too. In that case editor is * actually kept unassociated with a particular item of model, but returns * true here nevertheless. It's intended to call selectItem( null ) in case * of trying to edit model instance that does not exist, yet. This way * callers won't have to test for existing item themselves. * * It is possible to provide item instance instead of item's ID here. In * that case editor is switching to use provided item's data source further * one. * * Selecting item is available once, only. This method is throwing exception * on trying to select another item. * * @param mixed $id ID or instance of item to associate with editor * @return bool true on success, false on error * @throws \LogicException on trying to re-associate editor */ public function selectItem($id) { if ($this->item) { throw new \LogicException(\de\toxa\txf\_L('Editor is already operating on model instance.')); } if ($id !== null) { if (is_object($id) && $this->class->isInstance($id)) { $this->item = $id; } else { $this->item = $this->class->getMethod('select')->invoke(null, $this->datasource, $id); } if (!$this->item) { return false; } $this->datasource = $this->item->source(); if (!$this->datasource) { throw new \RuntimeException('item is not associated with data source'); } foreach ($this->fields as $field) { /** @var model_editor_field $field */ $field->type()->onSelectingItem($this, $this->item, $field); } } return true; }
/** * Retrieves relation according to declaration selected by given name. * * Relations may be declared in static property $relations of a model. * * @example - have user relate to address via immediately related person (user.person_id => person.id - person.address_id => contact.id) user::$relations = array( 'contact_address' => array( // <- declaring name of relation 'referencing' => array( // <- declaring user referring to some 'model' => 'person', // <- ... person. ... 'with' => 'person_id', // <- declaring user.person_id to contain foreign key of ... 'on' => 'id', // <- ... person.id thus: user.person_id => person.id 'referencing' => array( // <- declaring person is then referring to another model ... 'model' => 'address', // <- ... address ... 'alias' => 'contact', // <- ... calling it "contact" here ... 'with' => 'address_id', // <- ... with person.address_id containing foreign key of ... 'on' => 'id', // <- ... contact.id thus: person.address_id => contact.id ) ) ), ); - same relation declared in reverse direction (contact.id <= person.address_id - person.id <= user.person_id) address::$relations = array( 'user' => array( // <- declaring name of relation 'alias' => 'contact', // <- declaring alias of initial node in relation being contact 'referencedBy' => array( // <- declaring address (called contact) referred to by some 'model' => 'person', // <- ... person. ... 'with' => 'address_id', // <- ... using its property address_id to contain foreign key of ... 'on' => 'id', // <- ... contact.id thus: contact.id <= person.address_id 'referencedBy' => array( // <- declaring person is then referred to by another model ... 'model' => 'user', // <- ... user ... 'with' => 'person_id', // <- ... using its property person_id to contain foreign key of ... 'on' => 'id', // <- ... person.id thus: person.id <= user.person_id ) ) ), ); - example of an m:n-relation (customer.id <= ordered.customer_id - ordered.product_id => goods.id) customer::$relations = array( 'ordered_goods' => array( 'referencedBy' => array( 'model' => 'ordered', 'with' => 'customer_id', 'on' => 'id', 'referencing' => array( 'model' => 'product', 'alias' => 'goods', 'with' => 'product_id', 'on' => 'id', ) ) ), ); - same m:n-relation with multi-dimensional reference (customer.id <= ordered.customer_id - [ordered.type,ordered.product_id] => [goods.type,goods.id]) customer::$relations = array( 'ordered_goods' => array( 'referencedBy' => array( 'model' => 'ordered', 'with' => 'customer_id', 'on' => 'id', 'referencing' => array( 'model' => 'product', 'alias' => 'goods', 'with' => array( 'type', 'product_id' ), 'on' => array( 'type', 'id' ), ) ) ), ); - again, same m:n-relation with multi-dimensional reference, this time relying on implicitly inserted virtual model (named explicitly) (customer.id <= ordered.customer_id - [ordered.type,ordered.product_id] => [goods.type,goods.id]) customer::$relations = array( 'ordered_goods' => array( 'referencedBy' => array( 'dataset' => 'ordered', 'with' => 'customer_id', 'on' => 'id', 'referencing' => array( 'model' => 'product', 'alias' => 'goods', 'with' => array( 'type', 'product_id' ), 'on' => array( 'type', 'id' ), ) ) ), ); - again, same m:n-relation with multi-dimensional reference, this time relying on implicitly inserted virtual model (not named explicitly) (customer.id <= customer_goods.customer_id - [customer_product.type,customer_product.product_id] => [goods.type,goods.id]) The inner set's name is derived by combining names of neighbouring sets. customer::$relations = array( 'ordered_goods' => array( 'referencedBy' => array( 'with' => 'customer_id', 'on' => 'id', 'referencing' => array( 'model' => 'product', 'alias' => 'goods', 'with' => array( 'type', 'product_id' ), 'on' => array( 'type', 'id' ), ) ) ), ); - finally, same m:n-relation with multi-dimensional reference, this time relying on implicitly inserted virtual model (not named explicitly) and deriving property names from referenced properties' names (customer.id <= customer_product.customer_id - [customer_product.product_type,customer_product.product_id] => [goods.type,goods.id]) The inner set's name is derived by combining names of neighbouring sets. customer::$relations = array( 'ordered_goods' => array( 'referencedBy' => array( 'on' => 'id', 'referencing' => array( 'model' => 'product', 'alias' => 'goods', 'on' => array( 'type', 'id' ), ) ) ), ); * * @param string $name name of model's relation to retrieve * @param model $bindOnInstance instance of current model to bind relation on * @return model_relation */ public static function relation($name, model $bindOnInstance = null) { $name = data::isKeyword($name); if (!$name) { throw new \InvalidArgumentException('invalid relation name'); } $fullName = static::getReflection()->getName() . '::' . $name; if (array_key_exists($fullName, self::$_relationCache)) { // read existing relation from runtime cache $relation = self::$_relationCache[$fullName]; } else { if (!array_key_exists($name, static::$relations)) { throw new \InvalidArgumentException(sprintf('no such relation: %s', $name)); } try { $relation = static::_compileRelation(null, static::$relations[$name]); if (!$relation->isComplete()) { throw new \InvalidArgumentException('incomplete relation definition: %s'); } $relation->setName($name); // write this relation (unbound) into runtime cache self::$_relationCache[$fullName] = $relation; } catch (\InvalidArgumentException $e) { throw new \InvalidArgumentException(sprintf('%s: %s', $e->getMessage(), $name), $e->getCode(), $e); } } // clone cached relation $relation = clone $relation; // bind on provided instance if that is a subclass of current one if ($bindOnInstance) { $expectedClass = new \ReflectionClass(get_called_class()); $givenClass = $bindOnInstance->getReflection(); if ($givenClass->getName() !== $expectedClass->getName() && !$givenClass->isSubclassOf($expectedClass)) { throw new \InvalidArgumentException('provided instance is not compatible'); } $relation->bindNodeOnItem(0, $bindOnInstance); } return $relation; }
/** * Detects if provided model is compatible with current one. * * A model is considered _compatible_ if it is same one as current one or * derived from current one. * * @param model|\ReflectionClass|string|model_relation_model $testedModel * @return bool true if provided model is compatible with current one */ public function isCompatibleModel($testedModel) { if ($this->isVirtual()) { // there is no support for derivation on virtual models return $this->isSameModel($testedModel); } $testedModel = model::normalizeModel($testedModel); return $testedModel->getName() == $this->reflection->getName() || $testedModel->isSubclassOf($this->reflection); }