public function setParts(array $parts)
  {
    /** Filter input array */
    $parts = array_intersect_key($parts, $this->params);

    /** Force port to be numeric.
    *
    * If it would fail to convert (converts to zero), we will strip it.
    */
    if(isset($parts['port']))
      $parts['port'] = (int)$parts['port'];

    /** Convert query string to ksort()'ed array */
    if(isset($parts['query']))
    {
      $query = $this->_parse_str($parts['query']);
      $this->_rksort($query);
      $parts['query'] = $query;
    }

    /** Reset empty replacement parts to null
    *
    * This simplifies later checks for existence of URL parts.
    */
    array_walk(
      $parts,
      function(&$part, $key)
      {
        if(is_string($part))
        { /** Use strlen() instead of empty() to account for possible "0" values
          *
          * Valid case for (f.e.) user/pass parts.
          */
          $part = strlen($part) ? $part : null;
        }
        else
        {
          $part = $part ?: null;
        }
      }
    );

    $self = clone $this;
    // Keep URL parts in the same order at all times.
    $self->params = array_replace($this->params, $parts);
    return $self;
  }