code: 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;
    }
docs: Create a new instance of the class by replacing parts in the current instance
  
  Note: This is a replacement, not merge; especially in case of a `query` part.
  
  Note: The `query` part is always decoded into an array.
  
  @param array $parts A set of parts to replace. Uses the same names parse_url uses.
  @return \AnrDaemon\Net\Url A new class instance with corresponding parts replaced.