I have one specific issue. I have two entities:
class MyPlaylist {
...
/**
* #var Array
* #ORM\OneToMany(targetEntity="MyPlaylistContent", mappedBy="myPlaylist", orphanRemoval=true)
* #ORM\OrderBy({"position" = "DESC"})
*/
private $myPlaylistItems;
and
class MyPlaylistContent {
....
/**
* #ORM\ManyToOne(targetEntity="MyPlaylist", inversedBy="myPlaylistItems")
*/
private $myPlaylist;
Now I have this in my service
....
$myPlaylist = new MyPlaylist();
$myPlaylist->setUser($user);
$myPlaylist->setActive(true);
// add tracks
foreach ($playlist->getMyPlaylistItems() as $item) {
$entity = new MyPlaylistContent();
$entity->setTrack($item->getTrack());
$entity->setMyPlaylist($myPlaylist);
$this->em->persist($entity);
}
$this->em->persist($myPlaylist);
$this->em->flush();
\Doctrine\Common\Util\Debug::dump($myPlaylist);
return $myPlaylist;
so, I return a new playlist. If I look at the database, all works fine. I have both entities and in MyPlaylistContent - 3 tracks. But
\Doctrine\Common\Util\Debug::dump($myPlaylist); shows next
["active"]=> bool(true) ["myPlaylistItems"]=> array(0) { }
On the page, the app shows the empty playlist (no tracks). If I refresh the page, I can see all tracks.
The point is, if you open the page, the controller will call the service, build the content and return the list as a response.
It looks as the same example, but it does not work for me
http://symfony.com/doc/current/book/doctrine.html#saving-related-entities
What is wrong here? Why don't I get tracks for the current entity?
You forget to add MyPlaylistContent to MyPlaylist.
Use this snippet into foreach
$myPlaylist->addMyPlaylistContent($myPlaylistContent);
Of course change name or implement method accordingly
First note: this is because objects are "normal" php objects, they have nothing to do with doctrine so, relationships are only a doctrine concept. EntityManager in doctrine will handle this kind of processes, not php itself. If you take a look to your classes methods you will probably notice that no "connection" (assignments) are made between those objects. If you would like, you can modify MyPlaylistContent to add itself to MyPlaylist once assigned.
Something like
class MyPlaylistContent
{
[...]
public function setMyPlaylist(MyPlaylist $mp)
{
$this->myPlaylist = $myPlaylist;
$mp->addMyPlaylistContent($this);
return $this;
}
Second note: hope your names are more consistents of these ones :)
Related
I'm new to OOP and MVC with PHP, and I'm currently learning by making my own custom framework from scratch, for testing purposes. I have set up my controllers, models and views and everything works fine.
My app has the following architecture :
It’s a small blog that follows the rules of the MVC pattern. To summarize, it works like this :
The called Controller will fetch the data using the right models
Models return objects of the class \Classes\{MyObject}
Controller call the right template to render the view, and passes it the data and objects to display
The problem
In some views, I need to display related data. For example, in the article view, I need to display the author's first name. In the database, an article contains only the author’s ID, not his first name : this is the same thing in my class \Classes\Article.
What I've tried
To display the author’s first name in my view, I've updated the model Find method to use a LEFT JOIN in the SQL query. Then, I've updated my \Classes\Article class to have a user_firstname property :
class Article
{
private $pk_id;
private $title;
private $excerpt;
private $content;
private $created_at;
private $fk_user_id;
private $updated_at;
private $user_firstname; // <-- I've added this property to retrieve author's firstname
// (...)
}
What I did works well, but my teacher tells me it’s not the right way to do it because the author’s firstname is not part of the definition of an article.
In this case, my teacher tells me to use a DTO (Data Transfert Object) between Article and User classes.
Questions
What is the right way to set up a DTO in this case?
Do I need to create a new ArticleUserDTO class in a new namespace ?
How to use it ?
I think I understood the problem : the Article class should only contain what defines an article. But I can’t understand the logic of setting up a DTO. I’ve done some research on it, I understand the usefulness of the DTO but I can’t set up into my app.
Setting up a DTO in my app was easy ! As mentioned by #daremachine, the diagram below helped me to understand what a DTO is for.
Diagram source : martinfowler.com
We can see DTOs as an object assembler, in which we place all the elements we need on the view side.
For example, in the post view of an article, I needed to display other items, such as the author and posted comments. So I have created a Post class that groups all these items.
Setting up a DTO
In my \Classes\ namespace, I've created a new Post class. First, we define the properties we will need. Then we add the getters and setters for each of them. Finally, we set up the constructor, which will call each of the classes we need in the view.
namespace Classes;
use DateTime;
class Post
{
private int $pk_id;
private string $title;
private string $excerpt;
private string $content;
private DateTime $created_at;
private DateTime $updated_at;
private int $author_id;
private string $author_firstname;
private array $comments;
public function __construct(Article $article, User $author, array $comments)
{
$this->setPkId($article->getId());
$this->setTitle($article->getTitle());
$this->setExcerpt($article->getExcerpt());
$this->setContent($article->getContent());
$this->setCreatedAt($article->getCreatedAt());
$this->setUpdatedAt($article->getUpdatedAt());
$this->setAuthorId($article->getAuthorId());
$this->setAuthorFirstname($author->getFirstname());
$this->setComments($comments);
}
/**
* #param int $pk_id
*/
public function setPkId(int $pk_id): void
{
$this->pk_id = $pk_id;
}
/**
* #return int
*/
public function getPkId(): int
{
return $this->pk_id;
}
// (etc)
}
We now need to update the ArticleController, which should no longer pass the Article, Comment and User objects, but only the new Post object.
namespace Controllers;
class ArticleController extends Controller
{
// (...)
/**
* Get an article and display it
*
* #return void
*/
public function show(): void
{
// (...)
// Find Article :
$article = $this->articleModel->find($article_id);
if (!$article) {
Http::error404();
}
// Find Comments :
$commentaires = $this->commentModel->findAllByArticle($article_id);
// Find User (author)
$user = $this->userModel->find($article->getAuthorId());
// Data Transfert Object instance :
$post = new Post($article, $user, $commentaires);
$pageTitle = $post->getTitle();
// Pass DTO to view :
Renderer::render('articles/show', compact('pageTitle', 'post'));
}
}
We just need to update our view to use the new Post object and it's done ! Thanks to #daremachine for his help :)
It's the first time I run into this problem. I want to create a doctrine object and pass it along without having to flush it.
Right after it's creation, I can display some value in the object, but I can't access nested object:
$em->persist($filter);
print_r($filter->getDescription() . "\n");
print_r(count($filter->getAssetClasses()));
die;
I get:
filter description -- 0
(I should have 19 assetClass)
If I flush $filter, i still have the same issue (why oh why !)
The solution is to refresh it:
$em->persist($filter);
$em->flush();
$em->refresh($filter);
print_r($filter->getDescription() . " -- ");
print_r(count($filter->getAssetClasses()));
die;
I get:
filter description -- 19
unfortunately, you can't refresh without flushing.
On my entities, I've got the following:
in class Filter:
public function __construct()
{
$this->filterAssetClasses = new ArrayCollection();
$this->assetClasses = new ArrayCollection();
}
/**
* #var Collection
*
* #ORM\OneToMany(targetEntity="FilterAssetClass", mappedBy="filterAssetClasses", cascade={"persist"})
*/
private $filterAssetClasses;
public function addFilterAssetClass(\App\CoreBundle\Entity\FilterAssetClass $filterAssetClass)
{
$this->filterAssetClasses[] = $filterAssetClass;
$filterAssetClass->setFilter($this);
return $this;
}
in class FilterAssetClass:
/**
* #var Filter
*
* #ORM\ManyToOne(targetEntity="App\CoreBundle\Entity\Filter", inversedBy="filterAssetClasses")
*/
private $filter;
/**
* #var Filter
*
* #ORM\ManyToOne(targetEntity="AssetClass")
*/
private $assetClass;
public function setFilter(\App\CoreBundle\Entity\Filter $filter)
{
$this->filter = $filter;
return $this;
}
Someone else did write the code for the entities, and i'm a bit lost. I'm not a Doctrine expert, so if someone could point me in the good direction, that would be awesome.
Julien
but I can't access nested object
Did you set those assetClasses in the first place?
When you work with objects in memory (before persist), you can add and set all nested objects, and use those while still in memory.
My guess is that you believe that you need to store objects to database in order for them to get their IDs assigned.
IMHO, that is a bad practice and often causes problems. You can use ramsey/uuid library instead, and set IDs in Entity constructor:
public function __construct() {
$this->id = Uuid::uuid4();
}
A database should be used only as a means for storing data. No business logic should be there.
I would recommend this video on Doctrine good practices, and about the above mentioned stuff.
Your problem is not related to doctrine nor the persist/flush/refresh sequence; the problem you describe is only a symptom of bad code. As others have suggested, you should not be relying on the database to get at your data model. You should be able to get what you are after entirely without using the database; the database only stores the data when you are done with it.
Your Filter class should include some code that manages this:
// Filter
public function __contsruct()
{
$this->filterAssetClasses = new ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="FilterAssetClass", mappedBy="filterAssetClasses", cascade={"persist"})
*/
private $filterAssetClasses;
public function addFilterAssetClass(FilterAssetClass $class)
{
// assuming you don't want duplicates...
if ($this->filterAssetClasses->contains($class) {
return;
}
$this->filterAssetClasses[] = $class;
// you also need to set the owning side of this relationship
// for later persistence in the db
// Of course you'll need to create the referenced function in your
// FilterAssetClass entity
$class->addFilter($this);
}
You may have all of this already, but you didn't show enough of your code to know. Note that you should probably not have a function setFilterAssetClass() in your Filter entity.
I've got this model;
Itinerary, Venue, ItineraryVenue.
I needed many to many relation between itineraries and venues but also I wanted to store some specific data about the relation (say notes, own photo, etc.), so I decided to introduce a new entity named ItineraryVenue.
So Itinerary has collection of ItineraryVenues which in turn, refer to Venues.
My problem is that I can't remove ItineraryVenue from a Itinerary object.
$itinerary->itineraryVenues->removeElement($itineraryVenue);
$em->flush();
removes element from the php collection, but doesn't remove this $itineraryVenue from database.
I've managed to force Doctrine2 to remove $itineraryVenue, but only when I annotate the Itinerary::$itineraryVenues with orphanRemoval=true.
Since orphan removal treats Venue as a private property it also removes Venue entity, I don't want that.
Is there an relation configuration option or is removing "by hand" the olny way to make it work as I want?
Hard to believe it, it's a common relation pattern.
Entities definitions:
class Itinerary
{
/**
* #ORM\OneToMany(targetEntity="ItineraryVenue", mappedBy="itinerary", cascade={"persist", "remove"})
*/
private $itineraryVenues;
function __construct()
{
$this->itineraryVenues = new ArrayCollection();
}
}
class ItineraryVenue
{
/**
* #ORM\ManyToOne(targetEntity="Itinerary", inversedBy="itineraryVenues")
*/
private $itinerary;
/**
* #ORM\ManyToOne(targetEntity="Venue")
*/
private $venue;
function __construct()
{
}
}
class Venue
{
}
You are doing things right: orphanRemoval - is what you need. So, you should override default Itinerary::removeItineraryVenue like
public function removeItineraryVenue(\AppBundle\Entity\ItineraryVenue $itineraryVenue)
{
$itineraryVenue->setItinerary(null);
$this->itineraryVenues->removeElement($itineraryVenue);
}
The full working example is here https://github.com/kaduev13/removing-onetomany-elements-doctrine2.
I have entities in Doctrine Symfony2: User, Channel, Video and Comment; user can report one of them. I designed Report entity with these fields:
userId
status
reportTime
description
how can I reference to reported Entity ?? because all reported fields are similar for all entities I want to use just one table for Report and add these fields to Report Entity:
referenceEntityName(a string and may be one of these: User, Channel, Video, Comment)
Channel(ManytoOne relation to Channel entity)
Video(ManytoOne relation to Video entity)
Comment(ManytoOne relation to Comment entity)
User(ManytoOne relation to User entity)
Is this best practice or I should create separate tables for each kind of report ??
Edit:
based on #Alex answer, I improved Report class and add these methods:
setEntity($entity){
if ($obj instanceof Video){
$this->referenceEntityName = 'Video';
$this->setVideo();
}
elseif($obj instanceof Comment){
$this->referenceEntityName == 'Comment'
$this->setComment();
}
//...
}
getEntity(){
if($this->referenceEntityName == 'Video'){
$this->getVideo()
}// ifelse statements for other entities ...
}
I till have 4 relation that just one of them is used for each instance, isn't it a bit messy!?
and again is this best practice or I should do something else?
what if I want to use FormBuilder class, isn't there any problem??
In a simple solution, whereby for example you only had Users (and not Videos, Comments and Channels), the solution would be simple; each User can have many Reports, and each Report must belong to only one User. This is a one-to-many relationship - one User has many Reports. In Symfony 2 and Doctrine, this would be modelled as such:
// src/Acme/DemoBundle/Entity/User.php
// ...
use Doctrine\Common\Collections\ArrayCollection;
class User
{
// ...
/**
* #ORM\OneToMany(targetEntity="Report", mappedBy="user")
*/
protected $reports;
public function __construct()
{
$this->reports = new ArrayCollection();
}
// ...
}
and
// src/Acme/DemoBundle/Entity/Report.php
// ...
class Report
{
// ...
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="reports")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
// ...
}
In this instance, to create a Report and associate it with a User, we would:
// get the User the Report will belong to
$user = $em->getRepository('AcmeDemoBundle:User')->find(1);
// create the Report
$report = new Report();
// add the User to the Report
$report->setUser($user);
// then persist it, etc ...
Note, the setUser() method is available because the console command was run to generate them automatically. This is highly recommended as it created the necessary type hinting for you. For pre Symfony 2.5 installations, the command is:
php app/console doctrine:generate:entities Acme
>= 2.5 installations, the command is:
php bin/console doctrine:generate:entities Acme
Your requirements complicate this simple example somewhat, as Reports can also belong to Comments and Videos etc. For the sake of the example, let's call these things Entities. A bad approach would be to simply add 3 new properties to the Report, one for each of the new Entities, and then add 3 new setter methods for the Entities. This is bad for 2 reasons: a Report will only ever belong to one of the Entities, and therefore 3 of the properties and setter methods will never be used for each Report entity. Secondly, if you add a new Entity to your business model, or remove one, you need to edit your Report entity, and also the database schema.
A better method is to simply have one property and set method in your Report, that can be applied to all of your Entities. So instead of calling setUser, we could call a setEntity, and have it accept any of the 4. With this approach in mind, let's look back at the first example, and take note of the type hinting in the function signature that would have been produced for the setUser method:
public function setUser(Acme\DemoBundle\Entity\User $user)
See that it requires to be of type Acme\DemoBundle\Entity\User. How do we overcome this, and have it accept any of the 4 Entities? The solution is to have all Entities be derived from a parent class. Then make the function type hint at the base class:
public function setUser(Acme\DemoBundle\Entity\Base $entity)
The base class will contain all common elements, notably a 'name', and as array collection of Reports:
// src/Acme/DemoBundle/Entity/Base.php
// ...
use Doctrine\Common\Collections\ArrayCollection;
class Base
{
// ...
/**
* #ORM\Column(name="name", type="text")
*/
protected $name
/**
* #ORM\OneToMany(targetEntity="Report", mappedBy="baseEntity")
*/
protected $reports;
public function __construct()
{
$this->reports = new ArrayCollection();
}
// ...
}
and then for each child, for example a User and a Video:
// src/Acme/DemoBundle/Entity/User.php
// ...
use AcmeDemoBundle\Entity\Base;
class User extends Base
{
/**
* #ORM\Column(name="firstname", type="text")
*/
protected $firstName;
// ...
}
and the Video
// src/Acme/DemoBundle/Entity/Video.php
// ...
use AcmeDemoBundle\Entity\Base;
class Video extends Base
{
/**
* #ORM\Column(name="title", type="text")
*/
protected $title;
// ...
and change our Report Entity:
// src/Acme/DemoBundle/Entity/Report.php
// ...
class Report
{
// ...
/**
* #ORM\ManyToOne(targetEntity="Base", inversedBy="reports")
* #ORM\JoinColumn(name="base_id", referencedColumnName="id")
*/
protected $baseEntity;
// ...
}
Remember to run the doctrine command to generate the setBaseEntity method. When you do, notice that it will now accept any class derived of Base
Then, to put on a Report on a Video for example, we get the Video, create a Report, and add the Video to the Report:
$video = // get the video you want
$report = new Report();
$report->setBaseEntity($video);
To retrieve all Reports belonging to a Comment, we get the Comment, and get the Reports:
$video = // get the video you want
$reports = $video->getReports();
foreach($reports as $report){
$reportText = $report->getText(); // assuming the Report has a `text` field
}
Update:
The inheritance relationship between these Entities can be modelled in the database with Doctrine using Single Table Inheritance:
/**
* #ORM\Entity
* #ORM\Table(name="base_entities")
* #ORM\InheritanceType("SINGLE_TYPE")
* #ORM\Discriminator(name="entity_type", type="string")
* #ORM\DiscriminatorMap({"user" = "User", "comment" = "Comment", "video" = "Video", "channel" = "Channel"})
*/
I have a very simple entity(WpmMenu) that holds menu items connected to one another in a self-referencing relationship (adjecent list it's called)?
so in my entity I have:
protected $id
protected $parent_id
protected $level
protected $name
with all the getters/setters the relationships are:
/**
* #ORM\OneToMany(targetEntity="WpmMenu", mappedBy="parent")
*/
protected $children;
/**
* #ORM\ManyToOne(targetEntity="WpmMenu", inversedBy="children", fetch="LAZY")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", onUpdate="CASCADE", onDelete="CASCADE")
*/
protected $parent;
public function __construct() {
$this->children = new ArrayCollection();
}
And everything works fine. When I render the menu tree, I get the root element from the repository, get its children, and then loop through each child, get its children and do this recursively until I have rendered each item.
What happens (and for what I am seeking a solution)is this:
At the moment I have 5 level=1 items and each of these items have 3 level=2 items attached (and in the future I will be using level=3 items as well). To get all elements of my menu tree Doctrine executes:
1 query for the root element +
1 query to get the 5 children(level=1) of the root element +
5 queries to get the 3 children(level=2) of each of the level 1 items +
15 queries (5x3) to get the children(level=3) of each level 2 items
TOTAL: 22 queries
So, I need to find a solution for this and ideally I would like to have 1 query only.
So this is what I am trying to do:
In my entities repository(WpmMenuRepository) I use queryBuilder and get a flat array of all menu items ordered by level. Get the root element(WpmMenu) and add "manually" its children from the loaded array of elements. Then do this recursively on children. Doing this way I could have the same tree but with a single query.
So this is what I have:
WpmMenuRepository:
public function setupTree() {
$qb = $this->createQueryBuilder("res");
/** #var Array */
$res = $qb->select("res")->orderBy('res.level', 'DESC')->addOrderBy('res.name','DESC')->getQuery()->getResult();
/** #var WpmMenu */
$treeRoot = array_pop($res);
$treeRoot->setupTreeFromFlatCollection($res);
return($treeRoot);
}
and in my WpmMenu entity I have:
function setupTreeFromFlatCollection(Array $flattenedDoctrineCollection){
//ADDING IMMEDIATE CHILDREN
for ($i=count($flattenedDoctrineCollection)-1 ; $i>=0; $i--) {
/** #var WpmMenu */
$docRec = $flattenedDoctrineCollection[$i];
if (($docRec->getLevel()-1) == $this->getLevel()) {
if ($docRec->getParentId() == $this->getId()) {
$docRec->setParent($this);
$this->addChild($docRec);
array_splice($flattenedDoctrineCollection, $i, 1);
}
}
}
//CALLING CHILDREN RECURSIVELY TO ADD REST
foreach ($this->children as &$child) {
if ($child->getLevel() > 0) {
if (count($flattenedDoctrineCollection) > 0) {
$flattenedDoctrineCollection = $child->setupTreeFromFlatCollection($flattenedDoctrineCollection);
} else {
break;
}
}
}
return($flattenedDoctrineCollection);
}
And this is what happens:
Everything works out fine, BUT I end up with each menu items present twice. ;) Instead of 22 queries now I have 23. So I actually worsened the case.
What really happens, I think, is that even if I add the children added "manually", the WpmMenu entity is NOT considered in-sync with the database and as soon as I do the foreach loop on its children the loading is triggered in ORM loading and adding the same children that were added already "manually".
Q: Is there a way to block/disable this behaviour and tell these entities they they ARE in sync with the db so no additional querying is needed?
With immense relief (and a lots of learning about Doctrine Hydration and UnitOfWork) I found the answer to this question. And as with lots of things once you find the answer you realize that you can achieve this with a few lines of code. I am still testing this for unknown side-effects but it seems to be working correctly.
I had quite a lot of difficulties to identify what the problem was - once I did it was much easier to search for an answer.
So the problem is this: Since this is a self-referencing entity where the entire tree is loaded as a flat array of elements and then they are "fed manually" to the $children array of each element by the setupTreeFromFlatCollection method - when the getChildren() method is called on any of the entities in the tree (including the root element), Doctrine (NOT knowing about this 'manual' approach) sees the element as "NOT INITIALIZED" and so executes an SQL to fetch all its related children from the database.
So I dissected the ObjectHydrator class (\Doctrine\ORM\Internal\Hydration\ObjectHydrator) and I followed (sort of) the dehydration process and I got to a $reflFieldValue->setInitialized(true); #line:369 which is a method on the \Doctrine\ORM\PersistentCollection class setting the $initialized property on the class true/false. So I tried and IT WORKS!!!
Doing a ->setInitialized(true) on each of the entities returned by the getResult() method of the queryBuilder (using the HYDRATE_OBJECT === ObjectHydrator) and then calling ->getChildren() on the entities now do NOT trigger any further SQLs!!!
Integrating it in the code of WpmMenuRepository, it becomes:
public function setupTree() {
$qb = $this->createQueryBuilder("res");
/** #var $res Array */
$res = $qb->select("res")->orderBy('res.level', 'DESC')->addOrderBy('res.name','DESC')->getQuery()->getResult();
/** #var $prop ReflectionProperty */
$prop = $this->getClassMetadata()->reflFields["children"];
foreach($res as &$entity) {
$prop->getValue($entity)->setInitialized(true);//getValue will return a \Doctrine\ORM\PersistentCollection
}
/** #var $treeRoot WpmMenu */
$treeRoot = array_pop($res);
$treeRoot->setupTreeFromFlatCollection($res);
return($treeRoot);
}
And that's all!
Add the annotation to your association to enable eager loading. This should allow you to load the entire tree with only 1 query, and avoid having to reconstruct it from a flat array.
Example:
/**
* #ManyToMany(targetEntity="User", mappedBy="groups", fetch="EAGER")
*/
The annotation is this one but with the value changed
https://doctrine-orm.readthedocs.org/en/latest/tutorials/extra-lazy-associations.html?highlight=fetch
You can't solve this problem if using adjacent list. Been there, done that. The only way is to use nested-set and then you would be able to fetch everything you need in one single query.
I did that when I was using Doctrine1. In nested-set you have root, level, left and right columns which you can use to limit/expand fetched objects. It does require somewhat complex subqueries but it is doable.
D1 documentation for nested-set is pretty good, I suggest to check it and you will understand the idea better.
This is more like a completion and more cleaner solution, but is based on the accepted answer...
The only thing needed is a custom repository that is going to query the flat tree structure, and then, by iterating this array it will, first mark the children collection as initialized and then will hydratate it with the addChild setter present in the parent entity..
<?php
namespace Domain\Repositories;
use Doctrine\ORM\EntityRepository;
class PageRepository extends EntityRepository
{
public function getPageHierachyBySiteId($siteId)
{
$roots = [];
$flatStructure = $this->_em->createQuery('SELECT p FROM Domain\Page p WHERE p.site = :id ORDER BY p.order')->setParameter('id', $siteId)->getResult();
$prop = $this->getClassMetadata()->reflFields['children'];
foreach($flatStructure as &$entity) {
$prop->getValue($entity)->setInitialized(true); //getValue will return a \Doctrine\ORM\PersistentCollection
if ($entity->getParent() != null) {
$entity->getParent()->addChild($entity);
} else {
$roots[] = $entity;
}
}
return $roots;
}
}
edit: the getParent() method will not trigger additional queries as long as the relationship is made to the primary key, in my case, the $parent attribute is a direct relationship to the PK, so the UnitOfWork will return the cached entity and not query the database.. If your property doesn't relates by the PK, it WILL generate additional queries.